空腹おやじのログと備忘録

VBA(主にExcel)でいろいろな実験的な事とか、Linuxのコマンドとか設定とかについて忘れないように、あれこれと・・・

【VBA】Twitter お題「ねこちゃんの数を数える」を解いてみた

Twitter で、ほえほえ氏から提示されたお題「ねこちゃんの数を数える」を解いてみた。

ざっくり言えば、「親猫」、「子猫」、「猫又」・・・といった文字が並んでいて、
それぞれ何匹いるのかを数えるというもの。

take 1からtake 7まであって、
それぞれに条件がついています。

Take 条件 文字列
1 親猫と子猫を数えて(数と種類を報告)ください。 親猫子猫子猫子猫子猫子猫子猫親猫親猫子猫子猫
2 簡潔に書くために同じ種類の猫が連続する場合は「猫」一文字で代用することにしました。
「親猫猫子猫猫」は、親猫が2匹、子猫が2匹です。
親猫猫猫子猫猫猫猫猫猫親猫子猫猫
3 せっかく簡潔に書く記法を開発したのに適当に書くやつが現れました。そのせいで繰り返しの代わりに元の記法もOKになりました。
親猫が3匹の場合下のいづれもOKとします。
親猫親猫親猫
親猫猫親猫
親猫猫猫
子猫猫子猫猫親猫猫子猫親猫子猫親猫子猫猫子猫
4 なんと列に「猫又」が混じってしまいました。親猫、子猫、猫又の数を数えてください。 子猫猫子猫猫親猫猫又猫猫猫又親猫猫猫猫又猫
5 いままでは親猫子猫猫又でしたがいろいろな種類の猫がが混じってしまいました。
ただし猫の名前は2文字で最後に「猫」がつくのは同じです。
例で白猫黒猫がいますが実際はもっとたくさんの種類がいます。(青猫、緑猫とか)
子猫猫白猫黒猫猫猫猫親猫猫又猫猫猫又親猫猫猫猫又猫
6 なんと不条理な上司!登場。あろうことか、「猫又が二匹現れたらその時点で猫の種類と数を報告して作業を終了せよ」といわれました。どうしよう。。。
上司によると猫又2匹とは「累計して2匹」現れたらとのことで、連続2匹ということではない模様です。
"子猫猫又親猫猫又"
"子猫親猫猫又猫"
はともに最後の猫又で「累計2匹」なのでそこで報告して作業終了です。
子猫猫白猫黒猫緑猫青猫猫猫猫親猫猫又猫猫猫又青猫親猫猫猫猫又猫
7 最後の試練。上司が逆ギレしています。どうやら先程の指示は上司の意図ではなかったようです。彼によると

「猫又が不吉なのは『きっちり2匹だけ並んだ』ときだけだ!」

とのことです。累計でもないしなんと猫又が3匹並んでもそれはOK(カウント継続)だそうです。数えられるかな、、、、

コード

手っ取り早いので、正規表現で・・・

メイン処理

参照設定は、いつものやつ。
Microsoft VBScript Regular Expressions 5.5

メイン処理で使っているパターンは、
親猫を数える場合を例に取ると

(?:親)(猫*?)(?=(?:猫又|[^猫]|$))

に続く猫又を分けなければいけないので
肯定先読みを使用している。

"親猫又"
がある場合には、
猫又
を優先している。

親猫を優先したい場合には、
"(猫*?)"を"(猫+?)"に変えればいい(はず)

(?:親)(猫+?)(?=(?:猫又|[^猫]|$))

の部分が変わるだけで、
Take 1 から、Take 7 まで全て同じパターンで処理している。

Public Function52(ByVal sCats As String, ByRef sCatType() As String, ByRef lCatCount() As Long) As Long

    Dim re          As RegExp
    Dim mc          As MatchCollection
    Dim lCount      As Long
    Dim lNekomata   As Long
    Dim sResult     As String
    Dim sTypes()    As String
    Dim lElems      As Long
    Dim i       As Long
    Dim j       As Long

    ReDim sCatType(lCount)
    ReDim lCatCount(lCount)

    '猫又
    lNekomata = (Len(sCats) - Len(Replace$(sCats, "猫又", ""))) \ 2

    If lNekomata > 0 Then
        ReDim Preserve sCatType(lCount)
        sCatType(lCount) = "猫又"

        ReDim Preserve lCatCount(lCount)
        lCatCount(lCount) = lNekomata

        lCount = lCount + 1
    End If52 = lCount

    '猫と又を消して、猫の種類の文字のみにする
    sResult = Replace$(sCats, "猫", "")
    sResult = Replace$(sResult, "又", "")

    ReDim sTypes(0)

    lElems = -1

    '"猫"の前に付く文字を抜き出す("又"は含まれない)
    Do Until Len(sResult) = 0
        lElems = lElems + 1

        ReDim Preserve sTypes(lElems)

        sTypes(lElems) = Left$(sResult, 1)

        sResult = Replace$(sResult, sTypes(lElems), "")
    Loop

    If lElems < 0 Then
        Exit Function
    End If

    Set re = New RegExp

    With re
        .Global = True

        For i = LBound(sTypes) To UBound(sTypes)
            .Pattern = "(?:" & sTypes(i) & ")(猫*?)(?=(?:猫又|[^猫]|$))"

            Set mc = .Execute(sCats)

            sResult = ""

            For j = 0 To mc.Count - 1
                sResult = sResult & mc.Item(j).SubMatches(0)
            Next j

CONTINUE_LOOP:
            If sCats Like "*" & sTypes(i) & "猫*" Then
                '"親子猫"のような場合、"親猫"は無いので出力しない
                If Not sCats Like "*" & sTypes(i) & "猫又*" Then
                    '"子猫又"のような場合、"子猫"を出力しない

                    ReDim Preserve sCatType(lCount)
                    sCatType(lCount) = sTypes(i) & "猫"

                    ReDim Preserve lCatCount(lCount)
                    lCatCount(lCount) = Len(sResult) - Len(Replace$(sResult, "猫", ""))

                    lCount = lCount + 1
                End If
            End If
        Next i
    End With52 = lCount

End Function
Take 6、Take 7用補助処理
Public Function get猫6Source(ByVal sCats As String) As String

    Dim re      As RegExp
    Dim mc      As MatchCollection
    Dim sSource As String
    Dim lResult As Long
    Dim i       As Long

    Set re = New RegExp

    With re
        .Pattern = "^(.*?猫又){2}"
        .Global = False

        Set mc = .Execute(sCats)
        
        If mc.Count > 0 Then
            sSource = mc.Item(0)
        Else
            sSource = sCats
        End If
    End With

    get猫6Source = sSource

End Function

Public Function get猫7Source(ByVal sCats As String) As String

    Dim re      As RegExp
    Dim mc      As MatchCollection
    Dim sSource As String

    Set re = New RegExp

    With re
        .Pattern = "^.*?[^又]猫又猫又(?!猫又)"
        .Global = True

        Set mc = .Execute(sCats)
        
        If mc.Count > 0 Then
            sSource = mc.Item(0)
        Else
            sSource = sCats
        End If
    End With

    get猫7Source = sSource

End Function
テスト
Public Sub 猫てすと()

    Dim sCatType()  As String
    Dim lCatCount() As Long

    Dim sCats       As String
    Dim lResult     As Long

    '----- take1 -----
    Erase sCatType, lCatCount

    sCats = "親猫子猫子猫子猫子猫子猫子猫親猫親猫子猫子猫"
    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 2
    Debug.Assert sCatType(0) = "親猫"
    Debug.Assert lCatCount(0) = 3
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 8

    '----- take2 -----
    Erase sCatType, lCatCount

    sCats = "親猫猫猫子猫猫猫猫猫猫親猫子猫猫"
    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 2
    Debug.Assert sCatType(0) = "親猫"
    Debug.Assert lCatCount(0) = 4
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 8

    '----- take3 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫子猫猫親猫猫子猫親猫子猫親猫子猫猫子猫"
    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 2
    Debug.Assert sCatType(0) = "子猫"
    Debug.Assert lCatCount(0) = 9
    Debug.Assert sCatType(1) = "親猫"
    Debug.Assert lCatCount(1) = 4

    '----- take4 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫子猫猫親猫猫又猫猫猫又親猫猫猫猫又猫"
    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 3
    Debug.Assert sCatType(0) = "猫又"
    Debug.Assert lCatCount(0) = 3
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 4
    Debug.Assert sCatType(2) = "親猫"
    Debug.Assert lCatCount(2) = 4

    '----- take5 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫白猫黒猫猫猫猫親猫猫又猫猫猫又親猫猫猫猫又猫"
    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 5
    Debug.Assert sCatType(0) = "猫又"
    Debug.Assert lCatCount(0) = 3
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 2
    Debug.Assert sCatType(2) = "白猫"
    Debug.Assert lCatCount(2) = 1
    Debug.Assert sCatType(3) = "黒猫"
    Debug.Assert lCatCount(3) = 4
    Debug.Assert sCatType(4) = "親猫"
    Debug.Assert lCatCount(4) = 4

    '----- take6-1 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫白猫黒猫緑猫青猫猫猫猫親猫猫又猫猫猫又青猫親猫猫猫猫又猫"
    sCats = get猫6Source(sCats)

    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 7
    Debug.Assert sCatType(0) = "猫又"
    Debug.Assert lCatCount(0) = 2
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 2
    Debug.Assert sCatType(2) = "白猫"
    Debug.Assert lCatCount(2) = 1
    Debug.Assert sCatType(3) = "黒猫"
    Debug.Assert lCatCount(3) = 1
    Debug.Assert sCatType(4) = "緑猫"
    Debug.Assert lCatCount(4) = 1
    Debug.Assert sCatType(5) = "青猫"
    Debug.Assert lCatCount(5) = 4
    Debug.Assert sCatType(6) = "親猫"
    Debug.Assert lCatCount(6) = 1

    '----- take6-12 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫白猫黒猫緑猫青猫猫猫猫親猫猫又猫猫青猫親猫猫猫猫"
    sCats = get猫6Source(sCats)

    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 7
    Debug.Assert sCatType(0) = "猫又"
    Debug.Assert lCatCount(0) = 1
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 2
    Debug.Assert sCatType(2) = "白猫"
    Debug.Assert lCatCount(2) = 1
    Debug.Assert sCatType(3) = "黒猫"
    Debug.Assert lCatCount(3) = 1
    Debug.Assert sCatType(4) = "緑猫"
    Debug.Assert lCatCount(4) = 1
    Debug.Assert sCatType(5) = "青猫"
    Debug.Assert lCatCount(5) = 5
    Debug.Assert sCatType(6) = "親猫"
    Debug.Assert lCatCount(6) = 5

    '----- take7-1 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫白猫黒猫緑猫青猫猫猫猫親猫猫又猫猫猫又青猫親猫猫猫猫又猫"
    sCats = get猫7Source(sCats)

    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 7
    Debug.Assert sCatType(0) = "猫又"
    Debug.Assert lCatCount(0) = 3
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 2
    Debug.Assert sCatType(2) = "白猫"
    Debug.Assert lCatCount(2) = 1
    Debug.Assert sCatType(3) = "黒猫"
    Debug.Assert lCatCount(3) = 1
    Debug.Assert sCatType(4) = "緑猫"
    Debug.Assert lCatCount(4) = 1
    Debug.Assert sCatType(5) = "青猫"
    Debug.Assert lCatCount(5) = 5
    Debug.Assert sCatType(6) = "親猫"
    Debug.Assert lCatCount(6) = 4

    '----- take7-2 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫白猫黒猫緑猫青猫猫猫猫親猫猫又猫又青猫親猫猫猫猫又猫"
    sCats = get猫7Source(sCats)

    lResult =52(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 7
    Debug.Assert sCatType(0) = "猫又"
    Debug.Assert lCatCount(0) = 2
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 2
    Debug.Assert sCatType(2) = "白猫"
    Debug.Assert lCatCount(2) = 1
    Debug.Assert sCatType(3) = "黒猫"
    Debug.Assert lCatCount(3) = 1
    Debug.Assert sCatType(4) = "緑猫"
    Debug.Assert lCatCount(4) = 1
    Debug.Assert sCatType(5) = "青猫"
    Debug.Assert lCatCount(5) = 4
    Debug.Assert sCatType(6) = "親猫"
    Debug.Assert lCatCount(6) = 1

    Debug.Print "Done."

End Sub

Debug.Assert で済ませたので、イミディエイトウィンドウに

Done.

と出て、終わり。

ちなみに、イミディエイトウィンドウに出力すると、こんな感じ。
f:id:Z1000S:20200225224939p:plain

以上。



改2」は、バージョンです。ツッコまないでくださいwww

正規表現を使わないバージョン(2020/3/1追加)
  • Take 1~5 限定です。
  • 正規表現バージョンより、精度が低いです。
  • お題で提示されているデータは、クリアしますが、元になる猫データが変わると、要件をクリアできない場合があります
  • 正規表現バージョンとは、出力順が違います。

コード

Public Function 猫猫(ByVal sCats As String, ByRef sCatType() As String, ByRef lCatCount() As Long) As Long

    Dim dicCount    As Dictionary
    Dim lLength     As Long
    Dim lStartPos   As Long
    Dim lOffset     As Long
    Dim sCurrentType    As String
    Dim sType       As String
    Dim lCount      As Long

    lLength = Len(sCats)

    If lLength < 2 Then
        Exit Function
    End If

    Set dicCount = New Dictionary

    'グループ開始位置
    lStartPos = 1
    lOffset = 0

    '現在の猫の種類
    sCurrentType = ""
    sType = Mid$(sCats, lStartPos, 1)

    Do While True
        Select Case sType
        Case "猫"
            If Mid$(sCats, lStartPos + lOffset + 1, 1) <> "又" Then
                '猫又ではない
                lCount = lCount + 1
                lOffset = lOffset + 1
            Else
                '猫又
                lStartPos = lStartPos + lOffset + 2
                lOffset = 0

                If dicCount.Exists("又") = True Then
                    dicCount.Item("又") = dicCount.Item("又") + 1
                Else
                    dicCount.Add "又", 1
                End If

                If Len(sCurrentType) > 0 Then
                    If dicCount.Exists(sCurrentType) = True Then
                        dicCount.Item(sCurrentType) = dicCount.Item(sCurrentType) + lCount
                    End If
                End If

                sCurrentType = ""
            End If
        Case Else
            If sCurrentType <> sType Then
                '猫の種類が変わった
                lStartPos = lStartPos + lOffset + 1
                lOffset = 0

                If dicCount.Exists(sCurrentType) = True Then
                    'カウントアップ
                    dicCount.Item(sCurrentType) = dicCount.Item(sCurrentType) + lCount
                End If

                If Len(sType) > 0 Then
                    If dicCount.Exists(sType) = False Then
                        '新しい猫の種類を追加
                        dicCount.Add sType, 0
                    End If
                End If

                '現在の猫の種類を更新
                sCurrentType = sType

                'カウントクリア
                lCount = 0
            Else
                lOffset = lOffset + 1
            End If
        End Select

        sType = Mid$(sCats, lStartPos + lOffset, 1)

        '終了判定(lStartPos + lOffset > lLength で、sType = ""となる)
        If Len(sType) = 0 Then
            If Len(sCurrentType) > 0 Then
                '有効データならカウントアップ
                dicCount.Item(sCurrentType) = dicCount.Item(sCurrentType) + lCount
            End If

            Exit Do
        End If
    Loop

'--------------------------------------------------
    Dim v As Variant
    Dim lElems  As Long

    ReDim sCatType(dicCount.Count - 1)
    ReDim lCatCount(dicCount.Count - 1)

    For Each v In dicCount
        If Len(v) > 0 Then
            If dicCount.Item(v) > 0 Then
                If v <> "又" Then
                    sCatType(lElems) = v & "猫"
                Else
                    sCatType(lElems) = "猫" & v
                End If
                lCatCount(lElems) = dicCount.Item(v)

                lElems = lElems + 1
            End If
        End If
    Next v

    If dicCount.Count <> lElems Then
        '未登録データが有れば、排除
        ReDim Preserve sCatType(lElems - 1)
        ReDim Preserve lCatCount(lElems - 1)
    End If

    猫猫 = lElems

End Function

テスト

Public Sub 猫猫テスト()

    Dim sCatType()  As String
    Dim lCatCount() As Long

    Dim sCats       As String
    Dim lResult     As Long

    '----- take1 -----
    Erase sCatType, lCatCount

    sCats = "親猫子猫子猫子猫子猫子猫子猫親猫親猫子猫子猫"
    lResult = 猫猫(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 2
    Debug.Assert sCatType(0) = "親猫"
    Debug.Assert lCatCount(0) = 3
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 8

    '----- take2 -----
    Erase sCatType, lCatCount

    sCats = "親猫猫猫子猫猫猫猫猫猫親猫子猫猫"
    lResult = 猫猫(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 2
    Debug.Assert sCatType(0) = "親猫"
    Debug.Assert lCatCount(0) = 4
    Debug.Assert sCatType(1) = "子猫"
    Debug.Assert lCatCount(1) = 8

    '----- take3 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫子猫猫親猫猫子猫親猫子猫親猫子猫猫子猫"
    lResult = 猫猫(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 2
    Debug.Assert sCatType(0) = "子猫"
    Debug.Assert lCatCount(0) = 9
    Debug.Assert sCatType(1) = "親猫"
    Debug.Assert lCatCount(1) = 4

    '----- take4 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫子猫猫親猫猫又猫猫猫又親猫猫猫猫又猫"
    lResult = 猫猫(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 3
    Debug.Assert sCatType(0) = "子猫"
    Debug.Assert lCatCount(0) = 4
    Debug.Assert sCatType(1) = "親猫"
    Debug.Assert lCatCount(1) = 4
    Debug.Assert sCatType(2) = "猫又"
    Debug.Assert lCatCount(2) = 3

    '----- take5 -----
    Erase sCatType, lCatCount

    sCats = "子猫猫白猫黒猫猫猫猫親猫猫又猫猫猫又親猫猫猫猫又猫"
    lResult = 猫猫(sCats, sCatType, lCatCount)

    Debug.Assert lResult = 5
    Debug.Assert sCatType(0) = "子猫"
    Debug.Assert lCatCount(0) = 2
    Debug.Assert sCatType(1) = "白猫"
    Debug.Assert lCatCount(1) = 1
    Debug.Assert sCatType(2) = "黒猫"
    Debug.Assert lCatCount(2) = 4
    Debug.Assert sCatType(3) = "親猫"
    Debug.Assert lCatCount(3) = 4
    Debug.Assert sCatType(4) = "猫又"
    Debug.Assert lCatCount(4) = 3

    Debug.Print "Done."

End Sub