和風スパゲティのレシピ

日本語でコーディングするExcelVBA

ワークシート関数を自作する【COUNT編】

ワークシート関数を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文の書き方を練習したい人は、
是非とも挑戦してみてください。