VB.NETで双方向リストを作る

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

 .NET Frameworkには双方向リストに相当する様なコレクションがないようです。 そこで自作してみることにしました。 しかし、簡易型であるので、ICollectionなどのインターフェイスは実装していません。 唯一IEnumerableのみを実装しています。
 ソース中にDebuggerStepThroughという属性を使用していますが、これはデバッグの際にこの属性を使用したクラス・メソッドにステップ・インさせないための属性です。
BidirectionalListクラスとそれに関連する型
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
'-------------------------------------------------------------------------------------
' 名前 : Node
' 概要 : リストのノード
'-------------------------------------------------------------------------------------
Public Class Node

    Private m_Next As Node
    Private m_Prev As Node
    Private m_Value As Integer

    ' 次のノード
    Public Property [Next]() As Node

        Get

            Return m_Next

        End Get

        Set(ByVal Value As Node)

            m_Next = Value
            If Not m_Next Is Nothing Then m_Next.SetPrev(Me)

        End Set

    End Property

    ' 前のノード
    Public Property Prev() As Node

        Get

            Return m_Prev

        End Get

        Set(ByVal Value As Node)

            m_Prev = Value
            If Not m_Prev Is Nothing Then m_Prev.SetNext(Me)

        End Set

    End Property

    ' ノードの保持する値
    Public Property Value() As Integer

        Get

            Return m_Value

        End Get

        Set(ByVal Value As Integer)

            m_Value = Value

        End Set

    End Property

    ' コンストラクタ
    <DebuggerStepThrough()> _
    Public Sub New(ByVal val As Integer)

        Me.New(val, Nothing, Nothing)

    End Sub

    ' コンストラクタ
    <DebuggerStepThrough()> _
    Public Sub New(ByVal val As Integer, ByVal [next] As Node, ByVal prev As Node)

        Me.Value = val
        Me.Next = [next]
        Me.Prev = prev

    End Sub

    ' 次のノードを設定
    <DebuggerStepThrough()> _
    Public Sub SetNext(ByVal [next] As Node)

        m_Next = [next]

    End Sub

    ' 前のノードを設定
    <DebuggerStepThrough()> _
    Public Sub SetPrev(ByVal prev As Node)

        m_Prev = prev

    End Sub

    ' 文字列へ変換
    <DebuggerStepThrough()> _
    Public Overrides Function ToString() As String

        Return m_Value.ToString()

    End Function

End Class


'-------------------------------------------------------------------------------------
' 名前 : ListTraverseDirection
' 概要 : トラバース方向
'-------------------------------------------------------------------------------------
Public Enum ListTraverseDirection

    HeadToTail ' 先頭から末尾へ
    TailToHead ' 末尾から先頭へ

End Enum


'-------------------------------------------------------------------------------------
' 名前 : BidirectionalList
' 概要 : 双方向リスト本体
'-------------------------------------------------------------------------------------
Public Class BidirectionalList

    ' 実装するインターフェイス
    Implements IEnumerable

    ' フィールド
    Private m_Count As Integer
    Private m_Head As Node
    Private m_Tail As Node
    Private m_TraverseDirection As ListTraverseDirection

    ' 先頭ノード
    Public ReadOnly Property Head() As Node

        Get

            Return m_Head

        End Get

    End Property

    ' 末尾ノード
    Public ReadOnly Property Tail() As Node

        Get

            Return m_Tail

        End Get

    End Property

    ' アイテム数
    Public ReadOnly Property Count() As Integer

        Get

            Return m_Count

        End Get

    End Property

    ' トラバース方向
    Public Property TraverseDirection() As ListTraverseDirection

        Get

            Return m_TraverseDirection

        End Get

        Set(ByVal Value As ListTraverseDirection)

            m_TraverseDirection = Value

        End Set

    End Property

    '-------------------------------------------------------------------------------------
    ' 名前 : Values
    ' 概要 : 全てのノードから該当するインデックスのノードの値を取得・設定する
    '        デフォルトプロパティ(インデクサとして機能)
    '-------------------------------------------------------------------------------------
    Default Public Property Values(ByVal index As Integer) As Integer

        Get

            Dim node As Node = Me.Nodes(index)

            If Not node Is Nothing Then

                Return node.Value

            Else

                Return 0

            End If

        End Get

        Set(ByVal Value As Integer)

            Dim node As Node = Me.Nodes(index)

            If Not node Is Nothing Then node.Value = Value

        End Set

    End Property

    '-------------------------------------------------------------------------------------
    ' 名前 : Nodes
    ' 概要 : 全てのノードから該当するインデックスのノードを取得する
    '-------------------------------------------------------------------------------------
    Public ReadOnly Property Nodes(ByVal index As Integer) As Node

        Get

            Dim node As Node

            For Each node In Me

                If index = 0 Then Return node

                index -= 1

            Next

            Return Nothing

        End Get

    End Property

    '-------------------------------------------------------------------------------------
    ' 名前 : BidirectionalListEnumerator
    ' 概要 : BidirectionalList専用列挙子
    '-------------------------------------------------------------------------------------
    <DebuggerStepThrough()> _
    Private Class BidirectionalListEnumerator
        Implements IEnumerator

        Private m_Head As Node
        Private m_Tail As Node
        Private m_Current As Node
        Private m_TraverseDirection As ListTraverseDirection

        Public Sub New(ByVal list As BidirectionalList, ByVal direction As ListTraverseDirection)

            m_Tail = list.Tail
            m_Head = list.Head

            m_TraverseDirection = direction

            Me.Reset()

        End Sub

        Public ReadOnly Property Current() As Object Implements IEnumerator.Current

            Get

                Return m_Current

            End Get

        End Property

        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext

            If m_TraverseDirection = ListTraverseDirection.HeadToTail Then

                ' 先頭から末尾へのトラバース
                m_Current = m_Current.Next

                Return (Not m_Current Is m_Tail)

            Else

                ' 末尾から先頭へのトラバース
                m_Current = m_Current.Prev

                Return (Not m_Current Is m_Head)

            End If

        End Function

        Public Sub Reset() Implements IEnumerator.Reset

            If m_TraverseDirection = ListTraverseDirection.HeadToTail Then

                ' 先頭から末尾へのトラバース
                m_Current = m_Head

            Else

                ' 末尾から先頭へのトラバース
                m_Current = m_Tail

            End If

        End Sub

    End Class


    '-------------------------------------------------------------------------------------
    ' 名前 : New
    ' 概要 : コンストラクタ
    '-------------------------------------------------------------------------------------
    <DebuggerStepThrough()> _
    Public Sub New()

        m_TraverseDirection = ListTraverseDirection.HeadToTail

        m_Head = New Node(0)
        m_Tail = New Node(0)

        m_Head.Next = m_Tail
        m_Tail.Prev = m_Head

        m_Count = 0

    End Sub

    '-------------------------------------------------------------------------------------
    ' 名前 : AddHead
    ' 概要 : アイテムを先頭に追加
    '-------------------------------------------------------------------------------------
    Public Sub AddHead(ByVal value As Integer)

        Dim node As New Node(value)

        node.Next = m_Head.Next
        m_Head.Next = node

        m_Count += 1

    End Sub

    '-------------------------------------------------------------------------------------
    ' 名前 : AddTail
    ' 概要 : アイテムを末尾に追加
    '-------------------------------------------------------------------------------------
    Public Sub AddTail(ByVal value As Integer)

        Dim node As New Node(value)

        node.Prev = m_Tail.Prev
        m_Tail.Prev = node

        m_Count += 1

    End Sub

    '-------------------------------------------------------------------------------------
    ' 名前 : Insert
    ' 概要 : アイテムの挿入
    '-------------------------------------------------------------------------------------
    Public Sub Insert(ByVal index As Integer, ByVal value As Integer)

        Dim node As Node = Me.Nodes(index)

        If Not node Is Nothing Then

            Dim nodeNew As New Node(value)

            nodeNew.Prev = node.Prev
            nodeNew.Next = node
            node.Prev = nodeNew

            m_Count += 1

        End If

    End Sub


    '-------------------------------------------------------------------------------------
    ' 名前 : Remove
    ' 概要 : アイテムの削除
    '-------------------------------------------------------------------------------------
    Public Sub Remove(ByVal value As Integer)

        Dim node As Node

        For Each node In Me

            If node.Value = value Then

                ' リスト構造のつなぎ換え
                Dim n As Node = node.Next
                Dim p As Node = node.Prev

                If Not n Is Nothing Then n.SetPrev(p)
                If Not p Is Nothing Then p.SetNext(n)

                ' リストから分離
                node.SetNext(Nothing)
                node.SetPrev(Nothing)

                m_Count -= 1

                Exit For

            End If

        Next

    End Sub

    '-------------------------------------------------------------------------------------
    ' 名前 : Clear
    ' 概要 : アイテムのクリア
    '-------------------------------------------------------------------------------------
    Public Sub Clear()

        m_Head.Next = m_Tail
        m_Tail.Prev = m_Head

        m_Count = 0

    End Sub

    '-------------------------------------------------------------------------------------
    ' 名前 : GetEnumerator
    ' 概要 : 列挙子を取得
    '-------------------------------------------------------------------------------------
    Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator

        ' トラバース方向を指定して列挙子を作成
        Return New BidirectionalListEnumerator(Me, m_TraverseDirection)

    End Function

End Class

 双方向リストの仕組みなどの説明はしないことにして、この双方向リストBidirectionalListクラスはNodeクラスに値を保持し、また、各々のノードが保持する次ないし前のノードへの参照によって列挙を行います。 このサンプルではNodeクラスが保持する値はInteger型の値一つとします。 列挙の実装はBidirectionalListEnumeratorクラスに記述し、For Each構文が適用できるようにします。 また、列挙の際におけるリストのトラバース方向はListTraverseDirection列挙型で、先頭から末尾、末尾から先頭のいずれかを指定します。
 またBidirectionalListクラス自体は、先頭からの追加、末尾からの追加、挿入、削除、全削除の機能を持ちます。 また、先頭ノード、末尾ノードへの参照、リスト内のアイテム数や、全ノード、全値へのアクセスをプロパティによって提供します。
 これを実際に使用したものが次のコードです。
BidirectionalListクラスの使用
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
'-------------------------------------------------------------------------------------
' 名前 : Main
' 概要 : アプリケーションのエントリーポイントを提供するクラス
'-------------------------------------------------------------------------------------
Public Class Main

    Public Shared Function Main(ByVal args() As String) As Integer

        ' 双方向リストを作成
        Dim list As New BidirectionalList()

        ' アイテムを追加
        Dim i As Integer

        ' 先頭から追加
        For i = 4 To 0 Step -1

            list.AddHead(i)

        Next

        ' 末尾から追加
        For i = 5 To 9

            list.AddTail(i)

        Next

        ' リスト内を列挙
        EnumerateListNode(list)

        ' 数個のアイテムを削除
        Console.WriteLine("Remove items")

        list.Remove(1)
        list.Remove(4)
        list.Remove(6)
        list.Remove(8)
        list.Remove(9)

        ' リスト内を列挙
        EnumerateListNode(list)

        ' 任意の位置に数個のアイテムを挿入
        Console.WriteLine("Insert items")

        list.Insert(3, 0)
        list.Insert(5, 0)

        ' リスト内を列挙
        EnumerateListNode(list)

        ' アイテム数を表示
        Console.WriteLine("Number of items: " + list.Count.ToString())

        ' インデクサによる変更
        For i = 0 To list.Count - 1

            list(i) = list.Count - i

        Next

        ' リスト内を列挙
        EnumerateListNode(list)

        Console.Write(Console.Out.NewLine)

        ' トラバース方向を順方向に
        list.TraverseDirection = ListTraverseDirection.HeadToTail

        Console.WriteLine("Traverse direction: " + list.TraverseDirection.ToString())

        EnumerateListNode(list)

        ' トラバース方向を逆方向に
        list.TraverseDirection = ListTraverseDirection.TailToHead

        Console.WriteLine("Traverse direction: " + list.TraverseDirection.ToString())

        EnumerateListNode(list)

        Return 0

    End Function

    '-------------------------------------------------------------------------------------
    ' 名前 : EnumerateListNode
    ' 概要 : リストの全ノードを列挙する
    '-------------------------------------------------------------------------------------
    Public Shared Sub EnumerateListNode(ByVal list As BidirectionalList)

        Dim node As Node

        For Each node In list

            Console.Write("{0} ", node.Value())

        Next

        Console.Write(Console.Out.NewLine)

    End Sub

End Class
実行結果
0 1 2 3 4 5 6 7 8 9
Remove items
0 2 3 5 7
Insert items
0 2 3 0 5 0 7
Number of items: 7
7 6 5 4 3 2 1

Traverse direction: HeadToTail
7 6 5 4 3 2 1
Traverse direction: TailToHead
1 2 3 4 5 6 7
Press any key to continue