二次元配列で列をフィルター(正規表現)

昨日は、二次元配列の指定列内で、特定の文字がある行を残す、または消すことに挑戦した。
infoment.hatenablog.com

ここまで来ると次は正規表現を用い、もう少しだけ複雑な抽出をしてみたくなる。挑戦してみよう。
f:id:Infoment:20190907142233p:plain

といっても、内容は昨日と殆ど変わらない。正規表現を使用する時点で完全一致は有り得ず、昨日のような「完全一致 または 部分一致」の選択肢はない。
むしろ、如何に正しくパターンを作成できるかが肝となってくる。

そのため内容は殆ど同じであるものの、プロシージャとしては完全に別物とした。

クラスモジュール(ArrayEditClass)
' 行フィルター抽出の正規表現版
Public Function RowRegExpFilter(filt_pattern As Variant, _
                          column_index As Long, _
                Optional rf_header As Excel.XlYesNoGuess = xlYes, _
                Optional rf_result As RemainOrDelete = RemainOrDelete.rdDelete)

    ' 仮置用:残す場合。
    Dim TempArray_Remain As Variant
    ReDim TempArray_Remain(rMin To rMax, cMin To cMax)
    
    ' 仮置用:消す場合。
    Dim TempArray_Delete As Variant
    ReDim TempArray_Delete(rMin To rMax, cMin To cMax)
    
    ' 一行目をヘッダーと見なす場合(xlYes)、強制的に配列の一行目に組み込む。
    Dim StartRowIndex As Long
        If rf_header = xlYes Then
            For c = cMin To cMax
                TempArray_Remain(rMin, c) = source_array(rMin, c)
                TempArray_Delete(rMin, c) = source_array(rMin, c)
            Next
            StartRowIndex = rMin + 1
        Else
            StartRowIndex = rMin
        End If
        
    ' 正規表現の設定。
    Dim myReg As Object
    Set myReg = CreateObject("VBScript.RegExp")
        myReg.IgnoreCase = False
        myReg.Pattern = filt_pattern
    
    ' フィルター。
    Dim iR As Long
    Dim iD As Long
        iR = StartRowIndex
        iD = StartRowIndex
        For r = StartRowIndex To rMax
            
            ' 消した結果の配列。
            If myReg.test(source_array(r, column_index)) = False Then
                For c = cMin To cMax
                    TempArray_Delete(iD, c) = source_array(r, c)
                Next
                iD = iD + 1
            
            ' 残した結果の配列。
            Else
                For c = cMin To cMax
                    TempArray_Remain(iR, c) = source_array(r, c)
                Next
                iR = iR + 1
            End If
        Next
    
    ' 消すか残すか、指定された側をセット。
    Dim TempArray_Result1 As Variant
    Dim TempArray_Result2 As Variant
        Select Case rf_result
            Case RemainOrDelete.rdDelete
                TempArray_Result1 = TempArray_Delete
                i = iD - 1
            Case RemainOrDelete.rdRemain
                TempArray_Result1 = TempArray_Remain
                i = iR - 1
        End Select
    
    ' 末尾にあまった空白を消すために、ピッタリサイズの配列へ転記。
    ReDim TempArray_Result2(rMin To i, cMin To cMax)
        For r = rMin To i
            For c = cMin To cMax
                TempArray_Result2(r, c) = TempArray_Result1(r, c)
            Next
        Next
        
        RowRegExpFilter = TempArray_Result2
End Function

最近何度か追加したので、クラスモジュールの全文もこちらに載せておく。

クラスモジュール(SeaquenceClass)
Public Function TargetArray(SourceArray As Variant) As ArrayEditClass
    Set TargetArray = New ArrayEditClass
        TargetArray.source_array = SourceArray
End Function

ArrayEditClassは全文が長いので、折りたたんで ↓ 紹介。


それでは、テストしてみよう。
元データには、毎度おなじみ「なんちゃって個人情報」。レコード数は5000件だ。
f:id:Infoment:20190907144400p:plain

この中から、北陸三県(北から順に新潟・富山・石川)のレコードのみ抽出して、別のシートに書き出してみる。

Sub test()
    Dim SQC As SeaquenceClass
    Set SQC = New SeaquenceClass
    
    Dim arr_1() As Variant
    Dim arr_2() As Variant
    
        arr_1 = Range("A1").CurrentRegion.Value
        arr_2 = SQC.TargetArray(arr_1).RowRegExpFilter("(新潟|富山|石川)", 9, xlYes, rdRemain)
        
        Sheets.Add After:=ActiveSheet
        Range("A1").Resize(UBound(arr_2), UBound(arr_2, 2)) = arr_2
End Sub

結果は ↓ こちら。
f:id:Infoment:20190907150101g:plain

編集前後の画面を確認できるよう、各々5秒ずつキープしているため分かり難いが、ほぼ一瞬で抽出を終えている。
結構いい感じかも。明日に続きます。

参考まで。