配列もCollectionも使わないデータ構造(Queue)

配列もCollectionも使わないデータ構造(Queue)

前回、前々回

akashi-keirin.hatenablog.com

akashi-keirin.hatenablog.com

のStackに続いて、Queueも作った。

コードを簡単にするために、今回もString型データ専門。

値なら何でもオッケーにしたければ、Variantにしてください。

StringQueueクラスとStringQueueItemクラス

用意するのは標題の二つのクラス。とりあえずコードを掲載。

クラスモジュール StringQueue
Option Explicit

'Field Variables'
Private frontItem As StringQueueItem
Private rearItem As StringQueueItem
Private count_ As Long

'Properties'
Public Property Get ItemExists() As Boolean
  ItemExists = Not ((frontItem Is Nothing) And (rearItem Is Nothing))
End Property

Public Property Get Count() As Long
  Count = count_
End Property

Public Property Get Item(ByVal Index As Long) As StringQueueItem
  Dim ret As StringQueueItem
  Set ret = Nothing
  'IndexがおかしかったらNothingを返す'
  If Index < 1 Then GoTo Finalizer
  If Index > Me.Count Then GoTo Finalizer
  'アイテムがあったら返す'
  If ItemExists Then
    Set ret = frontItem
    'アイテムが一つだけなら先頭のアイテムを返す'
    If Me.Count = 1 Then GoTo Finalizer
    Dim i As Long
    For i = 2 To Index
      Set ret = ret.NextItem
    Next
  End If
Finalizer:
  Set Item = ret
End Property

Public Property Get Front() As StringQueueItem
  Dim ret As StringQueueItem
  Set ret = Nothing
  If ItemExists Then Set ret = frontItem
  Set Front = ret
End Property

Public Property Get Rear() As StringQueueItem
  Dim ret As StringQueueItem
  Set ret = Nothing
  If Me.ItemExists Then Set ret = rearItem
  Set Rear = ret
End Property

'Constructor'
Private Sub Class_Initialize()
  count_ = 0
End Sub

'Methods'
Public Sub addItem(ByVal newValue As String)
  '新しいStringQueueItemオブジェクトを作る'
  Dim newStringQueueItem As New StringQueueItem
  'StringQueueItemオブジェクトのValueプロパティをセット'
  newStringQueueItem.Value = newValue
  If Me.ItemExists Then
    '追加前のStringQueueにアイテムがあったとき'
    Set rearItem.NextItem = newStringQueueItem
    Set rearItem = newStringQueueItem
  Else
    'アイテム追加前のStringQueueが空だったとき'
    Set frontItem = newStringQueueItem
    Set rearItem = newStringQueueItem
  End If
  count_ = count_ + 1
End Sub

Public Function removeItem() As StringQueueItem
  Dim ret As StringQueueItem
  Set ret = Nothing
  'アイテムがあったらアイテムをセット'
  If Me.ItemExists Then
    Set ret = frontItem
    'アイテム除去後の後始末'
    If frontItem Is rearItem Then
      'アイテムが一つだけだったら、StringQueueは空になる'
      Set frontItem = Nothing
      Set rearItem = Nothing
    Else
      '先頭のアイテムを入れ換える'
      Set frontItem = frontItem.NextItem
    End If
    count_ = count_ - 1
  End If
  Set removeItem = ret
End Function

StringStackクラスの場合は、一番上のアイテムへの参照であるTopプロパティだけで良かった(データの出し入れが一箇所だけなので。)が、StringQueueクラスの場合は、データの入口と出口が異なるので、データ集合の先頭アイテムへの参照であるFrontプロパティと、データ集合の最後尾のアイテムへの参照であるRearプロパティが必要となる。

新しくアイテムを追加する(つまり、最後尾にアイテムを追加する)ためのaddItemメソッドと、先頭のアイテムを取得するとともに削除するremoveItemメソッドの中身を見てもらえば、どのような処理をしているのかがおわかりかと思う(わかりにくければ、コメント欄とか、Twitter、ノンプロ研Slackなんかに質問プリーズ。)。

クラスモジュール StringQueueItem
Option Explicit

'Field Variables'
Private value_ As String
Private nextItem_ As StringQueueItem

'Properties'
Public Property Let Value(ByVal argValue As String)
  value_ = argValue
End Property
Public Property Get Value() As String
  Value = value_
End Property

Public Property Set NextItem(ByVal argItem As StringQueueItem)
  Set nextItem_ = argItem
End Property
Public Property Get NextItem() As StringQueueItem
  Set NextItem = nextItem_
End Property

こちらの方は至ってシンプル。というか、前回のStringStackItemクラスとほとんど同じ。まあ、Stackにしても、Queueにしても一列棒状にアイテムがつながっている、というデータ構造なので、次のアイテムへの参照(NextItemプロパティ。StringQueueItem型。)を持たせておけば、それだけで表現できるのであった。

使ってみる

次のコードで使ってみる。

リスト1 標準モジュール
Private Sub testStringQueue()
  Private strQueue As New StringQueue
  With strQueue
    Call .addItem("1番サード岩鬼")    '……(1)'
    Debug.Print .Front.Value & " ~ "; .Rear.Value
    Debug.Print "===================="
    Call .addItem("2番セカンド殿馬")
    Debug.Print .Front.Value & " ~ "; .Rear.Value
    Debug.Print "===================="
    Call .addItem("3番レフト微笑")
    Debug.Print .Front.Value & " ~ "; .Rear.Value
    Debug.Print "===================="
    Call .addItem("4番キャッチャー山田")
    Debug.Print .Front.Value & " ~ "; .Rear.Value
    Debug.Print "===================="
    Dim i As Long    '……(2)'
    For i = 1 To 4
      Debug.Print "前から" & CStr(StrConv(i, vbWide)) & _
                  "番目のアイテムは、" & _
                  .Item(i).Value & "です。"
    Next
    Debug.Print "===================="    '……(3)'
    Do While .ItemExists    '……(4)'
      Debug.Print .removeItem.Value & " を削除しました。"
      Debug.Print "現在の保有アイテム数は、" & _
                  .Count & " 個です。"
    Loop
  End With
End Sub

(1)の

With strQueue
  Call .addItem("1番サード岩鬼")
  Debug.Print .Front.Value & " ~ "; .Rear.Value
  Debug.Print "===================="
  Call .addItem("2番セカンド殿馬")
  Debug.Print .Front.Value & " ~ "; .Rear.Value
  Debug.Print "===================="
  Call .addItem("3番レフト微笑")
  Debug.Print .Front.Value & " ~ "; .Rear.Value
  Debug.Print "===================="
  Call .addItem("4番キャッチャー山田")
  Debug.Print .Front.Value & " ~ "; .Rear.Value
End With

では、addItemメソッドを用いてアイテムを追加しつつ、そのたびに先頭のアイテムの値と最後尾のアイテムの値を出力。あと、アイテムごとに区切り線も追加している。

四つのアイテムを追加し終えると、(2)の

With strQueue
  Dim i As Long
  For i = 1 To 4
    Debug.Print "前から" & CStr(StrConv(i, vbWide)) & _
                "番目のアイテムは、" & _
                .Item(i).Value & "です。"
  Next
End With

で、先頭のアイテムから順に値を出力。

(3)の

Debug.Print "===================="

で区切りを入れて、あとは(4)の

With strQueue
  Do While .ItemExists
    Debug.Print .removeItem.Value & " を削除しました。"
    Debug.Print "現在の保有アイテム数は、" & _
                .Count & " 個です。"
  Loop
End With

で、アイテムのある限り先頭アイテムの値を出力し、そのたびに残りのアイテム数を出力する。

実行結果

リスト1の実行結果は

f:id:akashi_keirin:20191117155150j:plain

このとおり。

おわりに

実に面白い。

この要領で、たとえば、OrderdLinkedListも作ることができる。

速度面でどうなのかはわからないが。