ワークシート関数をFunctionプロシージャで自作してみる遊びです。
今回は【COUNT関数】を自作してみました。
ソースコード
' COUNT関数 和風スパ版 Function spCOUNT(ParamArray 値()) As Double If IsMissing(値) Then spCOUNT = 0: Exit Function ' 本家と違い引数なしを許す ' 各引数をループ Dim 各引数 As Variant For Each 各引数 In 値 ' Rangeの場合 If TypeName(各引数) = "Range" Then spCOUNT = spCOUNT + COUNTセル範囲(各引数) ' 配列の場合 ElseIf IsArray(各引数) Then spCOUNT = spCOUNT + COUNT配列(各引数) ' 値の場合 ElseIf spISNUMBER(各引数) Then spCOUNT = spCOUNT + 1 End If Next End Function ' Rangeの合計値 Private Function COUNTセル範囲(ByVal セル範囲 As Range) As Double Dim セル As Range For Each セル In セル範囲.Cells ' 数値と判定できるセルをカウント If spISNUMBER(セル.Value) Then COUNTセル範囲 = COUNTセル範囲 + 1 End If Next End Function ' 配列の合計値 Private Function COUNT配列(ByVal 配列) As Double Dim 要素 For Each 要素 In 配列 ' 本家と違いジャグ配列(配列の中に配列)にも対応 If IsArray(要素) Then COUNT配列 = COUNT配列 + COUNT配列(要素) ' 数値と判定できる要素をカウント ElseIf spISNUMBER(要素) Then COUNT配列 = COUNT配列 + 1 End If Next End Function ' ISNUMBER関数 和風スパ版 Function spISNUMBER(ByVal テストの対象 As Variant) As Boolean Select Case TypeName(テストの対象) Case "Long", "Integer", "Double", "Single", "Currency", "Date" spISNUMBER = True Case "Range" If テストの対象.CountLarge = 1 Then spISNUMBER = spISNUMBER(テストの対象.Value) End If End Select End Function
解説
愚直にFor Each文でEmptyでない要素をカウントしていくコードです。
本家のCOUNT関数もセル、配列どちらを渡されても動くため、
内部で二つのプロシージャに分岐して実装しています。
他のワークシート関数再現を行うときにも注意しましたが、
IsArray関数はRangeに対してもTrueを返してしまいます。
このため、配列かセル範囲かを判定するには、
先にTypeNameがRangeであるかを判定する必要があります。
詳しくはこちらの記事を参照ください。
さて、本家COUNT関数は"1000"のような文字列数値をカウントしないのですが、
これをVBAで実装するのが意外と面倒です。
というのもVBAは暗黙の型変換を行ってくれる言語のため、
Long型の変数 = "1,000"
このコードも問題なく1000という数値が代入されますし、
If IsNumeric("1,000") Then
この判定もTrueと判定さてしまいます。
純粋な数値であるかを判定する場合は、通常は
If WorksheetFunction.IsNumber("1,000") Then ' ←これはFalse
このようにISNUMBER関数を使用します。
普段はもちろんこの関数を使えばいいのですが、
「シート関数を自作する」問題でWorksheetFunctionを使うのはちょっと。。。
ということで、自作ISNUMBER関数を作って対応しました。
' ISNUMBER関数 和風スパ版 Function spISNUMBER(ByVal テストの対象 As Variant) As Boolean Select Case TypeName(テストの対象) Case "Long", "Integer", "Double", "Single", "Currency", "Date" spISNUMBER = True Case "Range" If テストの対象.CountLarge = 1 Then spISNUMBER = spISNUMBER(テストの対象.Value) End If End Select End Function
中身は「TypeName関数」を使って数値の判定を行う関数です。
TypeName関数がデータ型を正確に判定する関数であり、
Debug.Print TypeName("1000") ' ← これはString
この判定をしっかり行ってくれるため採用しました。
Rangeだった時に.Valueで再帰するコードも一緒にかけるのがいいですね。
本家COUNT関数が日付もカウントする関数ですので、
「Date」を判定に加えるのを忘れないようにしましょう。
本家との違い(改良点)
今回は本家に忠実に作成しているので意図的に変更はしていないのですが、
本家のCOUNT関数が対応していない「ジャグ配列(配列の配列)」には対応しました。
ただ↓のコードを書くだけで実装できますからね。
If IsArray(要素) Then COUNT配列 = COUNT配列 + COUNT配列(要素)
こんなに綺麗に同じFunction名が3つ並ぶとなんとなく気持ちいいです。
以上で自作COUNT関数の解説を終わります。
実用的かというと全くそんなことはありませんが、
遊びながら楽しくVBAを学べるいい題材だと思います。
Functionの作り方や、ForEach文の書き方を練習したい人は、
是非とも挑戦してみてください。