Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e15fbdb

Browse files
Implemented Doubly Linked List (#5 IP)
1 parent c6c04e9 commit e15fbdb

File tree

4 files changed

+420
-2
lines changed

4 files changed

+420
-2
lines changed
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /