昨日は、連続する数字の結合について、設問編と称し、結果の動画だけを掲載した。
infoment.hatenablog.com
今日は、回答編を紹介する。
本来ならば、「解答」とすべきところ。しかし恐らく解答は無限にあると思われ、今回は「回答」とした。
※解説については、コメントを参照ください。
Function MargeNumber(ParamArray target_range()) As String ' 重複除去に辞書(連想配列)を用いる。 Dim Dict As Object Set Dict = CreateObject("Scripting.Dictionary") ' 範囲指定が不連続であることを考慮して、二重ループとしている。 Dim myRng As Variant Dim r As Variant For Each myRng In target_range For Each r In myRng ' 重複除去が目的であるため、キー情報不問。 ' 今回は「1」を充てている(何でもOK)。 If r.Value <> vbNullString Then Dict(r.Value) = 1 End If Next Next ' 重複除去後のキーを配列で受け取ったうえで、昇順ソート。 Dim source_array As Variant source_array = Dict.Keys source_array = SortArray(source_array) If UBound(source_array) = 0 Then MargeNumber = source_array(0) Exit Function ElseIf UBound(source_array) = -1 Then MargeNumber = vbNullString Exit Function End If Dim iMin As Long: iMin = LBound(source_array) Dim iMax As Long: iMax = UBound(source_array) ' 桁の確認。最初と最後の桁が異なる場合、「~」でつなぐ。 If Len(source_array(iMin)) <> Len(source_array(iMax)) Then MargeNumber = source_array(iMin) & "~" & source_array(iMax) Exit Function End If ' 共通部位桁数。 Dim CommonDigitNumber As Long ' 個別部位桁数。 Dim RemainDigitNumber As Long Dim i As Long For i = 1 To Len(source_array(iMin)) If Mid(source_array(iMin), i, 1) <> Mid(source_array(iMax), i, 1) Then CommonDigitNumber = i - 1 RemainDigitNumber = Len(source_array(iMin)) - CommonDigitNumber Exit For End If Next ' 個別部位を配列化。 Dim arr() As Variant ReDim arr(iMin To iMax) Dim arr2() As Variant ReDim arr2(iMin To iMax) For i = iMin To iMax arr(i) = Right(source_array(i), RemainDigitNumber) arr2(i) = Right(source_array(i), RemainDigitNumber) Next Dim TempCode As String Select Case UBound(source_array) ' 要素が2個しかない場合は、「,」でつなぐ。 Case 1 MargeNumber = source_array(iMin) & ", " & arr(iMax) ' 要素が3個以上の場合。 Case Else ' 2個前の要素との差が2の場合、3つの値は連続している。 ' 従って、一つ前の要素を空白に置き換えて省略とする。 ' 例)数字が1,2,3と並んでいる場合、1と3の差は2。 ' 従って、1,2,3は必ず連続しているといえるため、 ' 2を空白に置き換えて1,,3とする。 ' ※この時、元の配列を置き換えてしまうと、その後の数字を ' 確認の際、支障をきたす。そのため、元データ配列「arr」 ' に加え、置き換え後の配列「arr2」を準備している。 For i = iMin + 2 To iMax If CLng(arr(i - 2)) + 2 = CLng(arr(i)) Then arr2(i - 1) = vbNullString End If Next TempCode = Join(arr2, ",") ' 2個以上「,」が連続しているということは、その前後の数が ' 連続していることを表す。従って、2個以上連続する「,」を ' まとめて「~」に置き換えている。 Dim myReg As Object Set myReg = CreateObject("VBScript.RegExp") myReg.Pattern = ",{2,}" myReg.Global = True If myReg.test(TempCode) Then TempCode = myReg.Replace(TempCode, "~") End If ' 見やすさのため、「,」の後ろに半角スペースを付したものと置換。 ' ※この辺りは好みなので、使用者の任意でも可。 MargeNumber = Left(source_array(iMin), CommonDigitNumber) & Replace(TempCode, ",", ", ") End Select End Function Public Function SortArray(ByVal arr As Variant, _ Optional sort_order As Excel.XlSortOrder = xlAscending) As Variant Dim aryList As Object Dim s As Variant Set aryList = CreateObject("System.Collections.ArrayList") For Each s In arr Call aryList.Add(s) Next Select Case sort_order Case xlAscending ' 昇順でソート。 Call aryList.Sort Case xlDescending ' 昇順でソートののち、降順へ反転。 Call aryList.Sort Call aryList.Reverse End Select SortArray = aryList.ToArray End Function
↓ テスト結果はこちら。
一見上手くいったようだが、この関数には欠点がある。それは、桁数が異なる連続する数字の場合、間が抜けていてもカンマ表示されないのだ。
今後更に作りこみを行い、進捗があれば改めて報告する。
さて、みなさんが思った方法と同じだったろうか、違ったろうか。
このような特定テーマで、Excel VBAコンテストを行ったら、皆さんの色々なアプローチや手法が勉強できて、とても面白いと思う。
参考まで。