今の言語は必ずIterator(For Each)があってとても簡単にループが作成できて便利なのだが、For Each を使って集計処理をすると以下のようなロジックになる場合が多い。
For Each を使用した集計処理
Dim col As Collection Dim v As Variant Dim strWork As String Dim lngCnt As Long Set col = New Collection col.Add "あ" col.Add "い" col.Add "い" col.Add "う" col.Add "え" col.Add "え" col.Add "お" col.Add "お" strWork = "" lngCnt = 0 For Each v In col If v <> strWork Then '初回 If strWork <> "" Then Debug.Print strWork & lngCnt End If strWork = v lngCnt = 0 End If lngCnt = lngCnt + 1 Next '集計結果を表示する前にループを抜けてしまう。 If v <> strWork Then Debug.Print strWork & lngCnt End If
結果
あ1 い2 う1 え2 お2
結果はもちろん合ってはいるのだけれど、初期化を行う場所や結果を表示する場所が2重になってしまっている。こんなことやっているとちょっと格好が悪い。昔(CobolやVBの時代)はこんなことはなかったはずだが、Iteratorを使うとこうなってしまうのは仕方がないなとは思う。
かといって、コレクションをカーソル風に読むのはわりと面倒。というわけで、コレクションを カーソル風に読むクラスを作成してみた。
カーソル風コレクション読み込みクラス
'------------------------------------------------------ ' カーソル風コレクション読み込みクラス '------------------------------------------------------ Option Explicit Private mCol As Collection Private mIndex As Long Private Sub Class_Initialize() mIndex = 0 End Sub Private Sub Class_Terminate() Set mCol = Nothing End Sub '------------------------------------------------------ ' コンストラクタ '------------------------------------------------------ Public Sub Init(col As Collection) Set mCol = col Me.MoveFirst End Sub '------------------------------------------------------ ' 終了判定 '------------------------------------------------------ Property Get Eof() As Boolean If mCol Is Nothing Then Eof = True Exit Sub End If Eof = mCol.Count < mIndex End Property '------------------------------------------------------ ' 最初の行に移動 '------------------------------------------------------ Public Sub MoveFirst() mIndex = 1 End Sub '------------------------------------------------------ ' 次行取得 '------------------------------------------------------ Public Sub MoveNext() mIndex = mIndex + 1 End Sub '------------------------------------------------------ ' セル取得 '------------------------------------------------------ Public Property Get item() As Variant If Me.Eof Then 'EOF後は最後の値を返す If IsObject(mCol(mCol.Count)) Then Set item = mCol(mCol.Count) Else item = mCol(mCol.Count) End If Else If IsObject(mCol(mIndex)) Then Set item = mCol(mIndex) Else item = mCol(mIndex) End If End If End Property
MoveNext を用いた集計処理
上記のクラスを使えば、記述内容がすっきりする。
Dim col As Collection Dim v As Variant Dim strWork As String Dim lngCnt As Long Set col = New Collection col.Add "あ" col.Add "い" col.Add "い" col.Add "う" col.Add "え" col.Add "え" col.Add "お" col.Add "お" Dim cc As CollectionCursor Set cc = New CollectionCursor cc.Init col Do Until cc.Eof '初期化 strWork = cc.item lngCnt = 0 '集計処理 Do Until cc.Eof Or strWork <> cc.item lngCnt = lngCnt + 1 cc.MoveNext Loop '集計結果 Debug.Print strWork & lngCnt Loop
あ1 い2 う1 え2 お2
見てわかる通り、初期化を行う場所、集計する場所、修正結果を表示する場所が一目瞭然であると思う。ループの中で次のコレクションに移動できないとこういったロジックを書くことができない。