1
+ import Node from './DoublyNode.ts' ;
2
+
3
+ export default class DoublyLinkedList < T > {
4
+ private head : Node < T > | null ;
5
+ private tail : Node < T > | null ;
6
+ private length : number ;
7
+
8
+ constructor ( ) {
9
+ this . head = null ;
10
+ this . tail = null ;
11
+ this . length = 0 ;
12
+ }
13
+
14
+ public getLength ( ) : number {
15
+ return this . length ;
16
+ }
17
+
18
+ public isEmpty ( ) : boolean {
19
+ return this . length === 0 ;
20
+ }
21
+
22
+ public append ( value : T , demo ?: boolean ) : boolean {
23
+ const newNode = new Node ( value ) ;
24
+
25
+ if ( ! this . tail ) {
26
+ this . head = newNode ;
27
+ this . tail = newNode ;
28
+ }
29
+ else {
30
+ newNode . setPrevious ( this . tail ) ;
31
+ this . tail . setNext ( newNode ) ;
32
+ this . tail = this . tail . getNext ( ) ;
33
+ }
34
+ ++ this . length ;
35
+
36
+ if ( demo ) {
37
+ console . log ( '--------- Appending' , value , 'at index' , this . length - 1 ) ;
38
+ console . log ( this . toString ( ) ) ;
39
+ }
40
+
41
+ return true ;
42
+ }
43
+
44
+ public prepend ( value : T , demo ?: boolean ) : boolean {
45
+ const newNode = new Node ( value ) ;
46
+
47
+ if ( ! this . head ) {
48
+ this . head = newNode ;
49
+ this . tail = newNode ;
50
+ } else {
51
+ newNode . setNext ( this . head ) ;
52
+ this . head . setPrevious ( newNode ) ;
53
+ this . head = newNode ;
54
+ }
55
+ ++ this . length ;
56
+
57
+ if ( demo ) {
58
+ console . log ( '--------- Prepending' , value , '---------' ) ;
59
+ console . log ( this . toString ( ) ) ;
60
+ }
61
+
62
+ return true ;
63
+ }
64
+
65
+ public insert ( value : T , atIndex : number , demo ?: boolean ) : boolean | null {
66
+ if ( atIndex < 0 || atIndex > this . length ) return null ;
67
+ if ( atIndex === 0 ) {
68
+ this . prepend ( value , demo ) ;
69
+ return true ;
70
+ }
71
+ if ( atIndex === this . length ) {
72
+ this . append ( value , demo ) ;
73
+ return true ;
74
+ }
75
+
76
+ const newNode = new Node ( value ) ;
77
+
78
+ const leader = this . _traverseToNode ( atIndex - 1 ) ;
79
+ if ( ! leader ) return false ;
80
+
81
+ const follower = leader . getNext ( ) ;
82
+
83
+ newNode . setPrevious ( leader ) ;
84
+ newNode . setNext ( follower ) ;
85
+ leader . setNext ( newNode ) ;
86
+ follower . setPrevious ( newNode ) ;
87
+
88
+ ++ this . length ;
89
+
90
+ if ( demo ) {
91
+ console . log ( '--------- Inserting' , value , 'at index' , atIndex ) ;
92
+ console . log ( this . toString ( ) ) ;
93
+ }
94
+
95
+ return true ;
96
+ }
97
+
98
+ public getValueAtIndex ( index : number ) : T | null {
99
+ if ( index > this . length - 1 || index < 0 ) return null ; // Validate input
100
+ if ( this . length === 0 || ! this . head || ! this . tail ) return null ; // Verify that list is not empty
101
+ if ( index === this . length - 1 ) return this . tail . getValue ( ) ; // Optimization when retrieving last element
102
+
103
+ let targetNode = this . _traverseToNode ( index ) ;
104
+ return targetNode ?. getValue ( ) || null ;
105
+ }
106
+
107
+ /**
108
+ * Find the index of the desired element if it exists.
109
+ * WARNING: Usable only for trees of primitive types, i.e. number, string
110
+ * @param value Value to search for, i.e. desired element
111
+ * @returns Numerical index of element position
112
+ */
113
+ public searchFor ( value : T ) : number | null {
114
+ if ( this . length === 0 || ! this . head ) return null ;
115
+
116
+ let currentNode = this . head ;
117
+
118
+ for ( let i = 0 ; ! ! currentNode ; ++ i ) {
119
+ if ( currentNode . getValue ( ) === value ) return i ; // Value matches, so return index
120
+ currentNode = currentNode . getNext ( ) ; // Otherwise, continue searching
121
+ }
122
+ return null ;
123
+ }
124
+
125
+ /**
126
+ * Remove all nodes from list. Constant Time O(1)
127
+ */
128
+ public empty ( nestedCall ?: boolean ) : boolean {
129
+ this . head = null ;
130
+ this . tail = null ;
131
+ this . length = nestedCall ? 1 : 0 ;
132
+ return true ;
133
+ }
134
+
135
+ public removeElementAtIndex ( index : number , demo ?: boolean ) : T | null {
136
+ if ( index > this . length - 1 || index < 0 ) return null ;
137
+ if ( this . length === 0 || ! this . head ) return null ;
138
+
139
+ let value = null ;
140
+
141
+ if ( index === 0 ) {
142
+ value = this . head . getValue ( ) ;
143
+
144
+ if ( this . length > 1 ) {
145
+ const newHead = this . head . getNext ( ) ;
146
+ newHead . setPrevious ( null ) ;
147
+ this . head . setNext ( null ) ;
148
+ this . head = newHead ;
149
+ } else {
150
+ this . empty ( true ) ;
151
+ }
152
+ }
153
+ else {
154
+ let leader = this . _traverseToNode ( index - 1 ) ;
155
+
156
+ const deletedNode = leader ?. getNext ( ) ;
157
+ value = deletedNode . getValue ( ) ;
158
+
159
+ leader ?. setNext ( leader . getNext ( ) . getNext ( ) ) ;
160
+ leader ?. getNext ( ) . setPrevious ( leader ) ;
161
+ deletedNode . setNext ( null ) ;
162
+ deletedNode . setPrevious ( null ) ;
163
+ }
164
+ -- this . length ;
165
+
166
+ if ( demo ) {
167
+ console . log ( 'Removing element at index' , index + ':' , JSON . stringify ( value ) ) ;
168
+ }
169
+
170
+ return value ;
171
+ }
172
+
173
+ private _traverseToNode ( index : number ) : Node < T > | null {
174
+ if ( ! this . head || ! this . tail ) return null ;
175
+
176
+ let currentNode : Node < T > ;
177
+
178
+ if ( index < this . length / 2 ) {
179
+ currentNode = this . head ;
180
+
181
+ for ( let i = 0 ; i < index ; ++ i ) {
182
+ currentNode = currentNode . getNext ( ) ;
183
+ }
184
+ } else {
185
+ currentNode = this . tail ;
186
+
187
+ for ( let i = this . length - 1 ; i > index ; -- i ) {
188
+ currentNode = currentNode . getPrevious ( ) ;
189
+ }
190
+ }
191
+
192
+ return currentNode ;
193
+ }
194
+
195
+ public toString ( nodesPerGroup ?: number ) : string {
196
+ if ( this . length === 0 || ! this . head ) {
197
+ return "" ;
198
+ }
199
+
200
+ nodesPerGroup = nodesPerGroup ? nodesPerGroup : 6 ;
201
+
202
+ const LABEL_HEAD = 'HEAD' ;
203
+ const LABEL_TAIL = 'TAIL' ;
204
+
205
+ let listAsString : string = `--- Node Count: ${ this . length } \n` ;
206
+ let currentNode : Node < any > = this . head ;
207
+
208
+ for ( let i = 0 ; i < this . length ; ++ i ) {
209
+ listAsString += `${ Array ( i % nodesPerGroup ) . fill ( '\t' ) . join ( '' ) } ${ i === 0 ? LABEL_HEAD : i } ${ currentNode } ${ i === this . length - 1 ? LABEL_TAIL : '' } \n` ;
210
+
211
+ if ( currentNode . hasNext ( ) ) {
212
+ currentNode = currentNode . getNext ( ) ;
213
+ }
214
+ }
215
+
216
+ return listAsString ;
217
+ }
218
+ }
219
+
220
+ function printLinkedList ( linkedList : DoublyLinkedList < any > ) : void {
221
+ if ( linkedList . isEmpty ( ) ) {
222
+ console . log ( 'Empty linked list -_-' ) ;
223
+ return ;
224
+ }
225
+ console . log ( linkedList . toString ( ) ) ;
226
+ }
227
+
228
+ function printSearchFor ( value : any , linkedList : DoublyLinkedList < any > ) : void {
229
+ console . log ( JSON . stringify ( value ) , 'found at index:' , linkedList . searchFor ( value ) ) ;
230
+ }
231
+
232
+ function printGetValueAtIndex ( index : number , linkedList : DoublyLinkedList < any > ) {
233
+ console . log ( 'Element at index' , index + ':' , linkedList . getValueAtIndex ( index ) ) ;
234
+ }
235
+
236
+
237
+ //---------------------------------------------------------------------
238
+ // ---------- MAIN PROGRAM ----------
239
+ //---------------------------------------------------------------------
240
+ if ( import . meta. main ) {
241
+
242
+ const ATLA = new DoublyLinkedList < string > ( ) ;
243
+
244
+ ATLA . append ( 'Sokka' ) ;
245
+ ATLA . append ( 'Katara' ) ;
246
+ ATLA . prepend ( 'Appa' ) ;
247
+
248
+ printLinkedList ( ATLA ) ;
249
+
250
+ ATLA . insert ( 'Aang' , 2 , true ) ;
251
+ ATLA . insert ( 'Zuko' , 1 ) ;
252
+ ATLA . insert ( 'Iroh' , 5 , true ) ;
253
+ ATLA . insert ( 'Azula' , 0 , true ) ;
254
+
255
+ printSearchFor ( 'Iroh' , ATLA ) ;
256
+ printSearchFor ( 'Sok' , ATLA ) ;
257
+ printSearchFor ( 'Sokka' , ATLA ) ;
258
+ printSearchFor ( 'Appa' , ATLA ) ;
259
+ printSearchFor ( 'Zuko' , ATLA ) ;
260
+ printGetValueAtIndex ( 1 , ATLA ) ;
261
+ printGetValueAtIndex ( 6 , ATLA ) ;
262
+ console . log ( '----------------------------------' ) ;
263
+
264
+ ATLA . removeElementAtIndex ( 1 , true ) ;
265
+ printLinkedList ( ATLA ) ;
266
+ ATLA . removeElementAtIndex ( 0 , true ) ;
267
+ printLinkedList ( ATLA ) ;
268
+ ATLA . removeElementAtIndex ( 2 , true ) ;
269
+ printLinkedList ( ATLA ) ;
270
+ ATLA . removeElementAtIndex ( 2 , true ) ;
271
+ printLinkedList ( ATLA ) ;
272
+ ATLA . removeElementAtIndex ( 1 , true ) ;
273
+ ATLA . removeElementAtIndex ( 0 , true ) ;
274
+ ATLA . removeElementAtIndex ( 1 , true ) ;
275
+ ATLA . removeElementAtIndex ( 0 , true ) ;
276
+ console . log ( '----------------------------------' ) ;
277
+
278
+ printLinkedList ( ATLA ) ;
279
+ console . log ( ) ;
280
+
281
+ ATLA . insert ( 'Katara' , 0 , true ) ;
282
+ ATLA . append ( 'Aang' ) ;
283
+ printLinkedList ( ATLA ) ;
284
+
285
+ // RUN: deno run Data-Structures/Linked-Lists/DoublyLinkedList.ts
286
+ }
287
+
288
+
289
+ // --------------------------- Terminal Output: ---------------------------
290
+ // --- Node Count: 3
291
+ // HEAD { value: "Appa", next: true, previous: false }
292
+ // 1 { value: "Sokka", next: true, previous: true }
293
+ // 2 { value: "Katara", next: false, previous: true } TAIL
294
+ //
295
+ // --------- Inserting Aang at index 2
296
+ // --- Node Count: 4
297
+ // HEAD { value: "Appa", next: true, previous: false }
298
+ // 1 { value: "Sokka", next: true, previous: true }
299
+ // 2 { value: "Aang", next: true, previous: true }
300
+ // 3 { value: "Katara", next: false, previous: true } TAIL
301
+ //
302
+ // --------- Appending Iroh at index 5
303
+ // --- Node Count: 6
304
+ // HEAD { value: "Appa", next: true, previous: false }
305
+ // 1 { value: "Zuko", next: true, previous: true }
306
+ // 2 { value: "Sokka", next: true, previous: true }
307
+ // 3 { value: "Aang", next: true, previous: true }
308
+ // 4 { value: "Katara", next: true, previous: true }
309
+ // 5 { value: "Iroh", next: false, previous: true } TAIL
310
+ //
311
+ // --------- Prepending Azula ---------
312
+ // --- Node Count: 7
313
+ // HEAD { value: "Azula", next: true, previous: false }
314
+ // 1 { value: "Appa", next: true, previous: true }
315
+ // 2 { value: "Zuko", next: true, previous: true }
316
+ // 3 { value: "Sokka", next: true, previous: true }
317
+ // 4 { value: "Aang", next: true, previous: true }
318
+ // 5 { value: "Katara", next: true, previous: true }
319
+ // 6 { value: "Iroh", next: false, previous: true } TAIL
320
+ //
321
+ // "Iroh" found at index: 6
322
+ // "Sok" found at index: null
323
+ // "Sokka" found at index: 3
324
+ // "Appa" found at index: 1
325
+ // "Zuko" found at index: 2
326
+ // Element at index 1: Appa
327
+ // Element at index 6: Iroh
328
+ // ----------------------------------
329
+ // Removing element at index 1: "Appa"
330
+ // --- Node Count: 6
331
+ // HEAD { value: "Azula", next: true, previous: false }
332
+ // 1 { value: "Zuko", next: true, previous: true }
333
+ // 2 { value: "Sokka", next: true, previous: true }
334
+ // 3 { value: "Aang", next: true, previous: true }
335
+ // 4 { value: "Katara", next: true, previous: true }
336
+ // 5 { value: "Iroh", next: false, previous: true } TAIL
337
+ //
338
+ // Removing element at index 0: "Azula"
339
+ // --- Node Count: 5
340
+ // HEAD { value: "Zuko", next: true, previous: false }
341
+ // 1 { value: "Sokka", next: true, previous: true }
342
+ // 2 { value: "Aang", next: true, previous: true }
343
+ // 3 { value: "Katara", next: true, previous: true }
344
+ // 4 { value: "Iroh", next: false, previous: true } TAIL
345
+ //
346
+ // Removing element at index 2: "Aang"
347
+ // --- Node Count: 4
348
+ // HEAD { value: "Zuko", next: true, previous: false }
349
+ // 1 { value: "Sokka", next: true, previous: true }
350
+ // 2 { value: "Katara", next: true, previous: true }
351
+ // 3 { value: "Iroh", next: false, previous: true } TAIL
352
+ //
353
+ // Removing element at index 2: "Katara"
354
+ // --- Node Count: 3
355
+ // HEAD { value: "Zuko", next: true, previous: false }
356
+ // 1 { value: "Sokka", next: true, previous: true }
357
+ // 2 { value: "Iroh", next: false, previous: true } TAIL
358
+ //
359
+ // Removing element at index 1: "Sokka"
360
+ // Removing element at index 0: "Zuko"
361
+ // Removing element at index 0: "Iroh"
362
+ // ----------------------------------
363
+ // Empty linked list -_-
364
+ //
365
+ // --------- Prepending Katara ---------
366
+ // --- Node Count: 1
367
+ // HEAD { value: "Katara", next: false, previous: false } TAIL
368
+ //
369
+ // --- Node Count: 2
370
+ // HEAD { value: "Katara", next: true, previous: false }
371
+ // 1 { value: "Aang", next: false, previous: true } TAIL
0 commit comments