Skip to content

Commit ba110e2

Browse files
committed
fix(linkedlist): refactored methods and improve book images and
explanations
1 parent fcf3db6 commit ba110e2

11 files changed

+191
-200
lines changed

Diff for: book/content/part02/linked-list.asc

+75-93
Large diffs are not rendered by default.

Diff for: book/images/dll-add-first.png

54.7 KB
Loading

Diff for: book/images/dll-add-last.png

57.2 KB
Loading

Diff for: book/images/dll-insert-middle.png

47.5 KB
Loading

Diff for: book/images/dll-remove-first.png

15.9 KB
Loading

Diff for: book/images/dll-remove-last.png

15.6 KB
Loading

Diff for: book/images/dll-remove-middle.png

14.3 KB
Loading

Diff for: book/images/dll.png

9.99 KB
Loading

Diff for: src/data-structures/linked-lists/linked-list.js

+108-99
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ class LinkedList {
2727

2828
newNode.next = this.first;
2929

30-
if (this.first) {
31-
this.first.previous = newNode;
32-
} else {
30+
if (this.first) { // check if first node exists (list not empty)
31+
this.first.previous = newNode; // <1>
32+
} else { // if list is empty, first & last will point to newNode.
3333
this.last = newNode;
3434
}
3535

@@ -52,11 +52,11 @@ class LinkedList {
5252
addLast(value) {
5353
const newNode = new Node(value);
5454

55-
if (this.first) {
55+
if (this.first) { // check if first node exists (list not empty)
5656
newNode.previous = this.last;
5757
this.last.next = newNode;
5858
this.last = newNode;
59-
} else {
59+
} else { // if list is empty, first & last will point to newNode.
6060
this.first = newNode;
6161
this.last = newNode;
6262
}
@@ -71,20 +71,18 @@ class LinkedList {
7171
/**
7272
* Insert new element at the given position (index)
7373
*
74+
* Runtime: O(n)
75+
*
7476
* @param {any} value new node's value
7577
* @param {Number} position position to insert element
76-
* @returns {Node} new node or 'undefined' if the index is out of bound.
78+
* @returns {Node|undefined} new node or 'undefined' if the index is out of bound.
7779
*/
78-
add(value, position = 0) {
79-
if (position === 0) { // <1>
80-
return this.addFirst(value);
81-
}
80+
addAt(value, position = 0) {
81+
if (position === 0) return this.addFirst(value); // <1>
82+
if (position === this.size) return this.addLast(value); // <2>
8283

83-
if (position === this.size) { // <2>
84-
return this.addLast(value);
85-
}
8684
// Adding element in the middle
87-
const current = this.get(position);
85+
const current = this.findBy({ index: position }).node;
8886
if (!current) return undefined; // out of bound index
8987

9088
const newNode = new Node(value); // <3>
@@ -99,6 +97,7 @@ class LinkedList {
9997

10098
// tag::searchByValue[]
10199
/**
100+
* @deprecated use findBy
102101
* Search by value. It finds first occurrence of
103102
* the position of element matching the value.
104103
* Similar to Array.indexOf.
@@ -112,17 +111,13 @@ class LinkedList {
112111
* @returns {number} return index or undefined
113112
*/
114113
getIndexByValue(value) {
115-
return this.find((current, position) => {
116-
if (current.value === value) {
117-
return position;
118-
}
119-
return undefined;
120-
});
114+
return this.findBy({ value }).index;
121115
}
122116
// end::searchByValue[]
123117

124118
// tag::searchByIndex[]
125119
/**
120+
* @deprecated use findBy directly
126121
* Search by index
127122
* Runtime: O(n)
128123
* @example: assuming a linked list with: a -> b -> c
@@ -133,134 +128,100 @@ class LinkedList {
133128
* this list or undefined if was not found.
134129
*/
135130
get(index = 0) {
136-
return this.find((current, position) => {
137-
if (position === index) {
138-
return current;
139-
}
140-
return undefined;
141-
});
131+
return this.findBy({ index }).node;
142132
}
143133
// end::searchByIndex[]
144134

145135
// tag::find[]
146136
/**
147-
* Iterate through the list until callback returns a truthy value
148-
* @example see #get and #getIndexByValue
149-
* @param {Function} callback evaluates current node and index.
150-
* If any value other than undefined it's returned it will stop the search.
151-
* @returns {any} callbacks's return value or undefined
137+
* Find by index or by value, whichever happens first.
138+
* Runtime: O(n)
139+
* @example
140+
* this.findBy({ index: 10 }).node; // node at index 10.
141+
* this.findBy({ value: 10 }).node; // node with value 10.
142+
* this.findBy({ value: 10 }).index; // node's index with value 10.
143+
*
144+
* @param {Object} params - The search params
145+
* @param {number} params.index - The index/position to search for.
146+
* @param {any} params.value - The value to search for.
147+
* @returns {{node: any, index: number}}
152148
*/
153-
find(callback) {
149+
findBy({ value, index = Infinity } = {}) {
154150
for (let current = this.first, position = 0; // <1>
155-
current; // <2>
151+
current && position <= index; // <2>
156152
position += 1, current = current.next) { // <3>
157-
const result = callback(current, position); // <4>
158-
159-
if (result !== undefined) {
160-
return result; // <5>
153+
if (position === index || value === current.value) { // <4>
154+
return { node: current, index: position }; // <5>
161155
}
162156
}
163-
return undefined; // not found
157+
return {}; // not found
164158
}
165159
// end::find[]
166160

167161

168162
// tag::removeFirst[]
169163
/**
170164
* Removes element from the start of the list (head/root).
171-
* Similar to Array.shift
165+
* Similar to Array.shift().
172166
* Runtime: O(1)
173167
* @returns {any} the first element's value which was removed.
174168
*/
175169
removeFirst() {
170+
if (!this.first) return null; // Check if list is already empty.
176171
const head = this.first;
177172

178-
if (head) {
179-
this.first = head.next;
180-
if (this.first) {
181-
this.first.previous = null;
182-
} else {
183-
this.last = null;
184-
}
185-
this.size -= 1;
173+
this.first = head.next; // move first pointer to the next element.
174+
if (this.first) {
175+
this.first.previous = null;
176+
} else { // if list has size zero, then we need to null out last.
177+
this.last = null;
186178
}
187-
return head && head.value;
179+
this.size -= 1;
180+
return head.value;
188181
}
189182
// end::removeFirst[]
190183

191184
// tag::removeLast[]
192185
/**
193-
* Removes element to the end of the list. Similar to Array.pop
194-
* Using the `last.previous` we can reduce the runtime from O(n) to O(1)
186+
* Removes element to the end of the list.
187+
* Similar to Array.pop().
195188
* Runtime: O(1)
196-
* @returns {value} the last element's value which was removed
189+
* @returns {any} the last element's value which was removed
197190
*/
198191
removeLast() {
192+
if (!this.last) return null; // Check if list is already empty.
199193
const tail = this.last;
200194

201-
if (tail) {
202-
this.last = tail.previous;
203-
if (this.last) {
204-
this.last.next = null;
205-
} else {
206-
this.first = null;
207-
}
208-
this.size -= 1;
195+
this.last = tail.previous;
196+
if (this.last) {
197+
this.last.next = null;
198+
} else { // if list has size zero, then we need to null out first.
199+
this.first = null;
209200
}
210-
return tail && tail.value;
201+
this.size -= 1;
202+
return tail.value;
211203
}
212204
// end::removeLast[]
213205

214206
// tag::removeByPosition[]
215207
/**
216-
* Removes the element at the specified position in this list.
208+
* Removes the element at the given position (index) in this list.
217209
* Runtime: O(n)
218210
* @param {any} position
219211
* @returns {any} the element's value at the specified position that was removed.
220212
*/
221213
removeByPosition(position = 0) {
222-
const current = this.get(position);
223-
224-
if (position === 0) {
225-
this.removeFirst();
226-
} else if (position === this.size - 1) {
227-
this.removeLast();
228-
} else if (current) {
229-
current.previous.next = current.next;
230-
current.next.previous = current.previous;
231-
this.size -= 1;
232-
}
233-
214+
if (position === 0) return this.removeFirst();
215+
if (position === this.size - 1) return this.removeLast();
216+
const current = this.findBy({ index: position }).node;
217+
if (!current) return null;
218+
current.previous.next = current.next;
219+
current.next.previous = current.previous;
220+
this.size -= 1;
234221
return current && current.value;
235222
}
236223
// end::removeByPosition[]
237224

238-
/**
239-
* Removes the first occurrence of the specified elementt
240-
* from this list, if it is present.
241-
* Runtime: O(n)
242-
* @param {any} callbackOrIndex callback or position index to remove
243-
*/
244-
remove(callbackOrIndex) {
245-
if (typeof callbackOrIndex !== 'function') {
246-
return this.removeByPosition(parseInt(callbackOrIndex, 10) || 0);
247-
}
248-
249-
// find desired position to remove using #find
250-
const position = this.find((node, index) => {
251-
if (callbackOrIndex(node, index)) {
252-
return index;
253-
}
254-
return undefined;
255-
});
256-
257-
if (position !== undefined) { // zero-based position.
258-
return this.removeByPosition(position);
259-
}
260-
261-
return false;
262-
}
263-
264225
/**
265226
* Remove element by Node
266227
* O(1)
@@ -303,6 +264,54 @@ class LinkedList {
303264
get length() {
304265
return this.size;
305266
}
267+
268+
/**
269+
* @deprecated use findBy
270+
* Iterate through the list until callback returns a truthy value
271+
* @example see #get and #getIndexByValue
272+
* @param {Function} callback evaluates current node and index.
273+
* If any value other than undefined it's returned it will stop the search.
274+
* @returns {any} callbacks's return value or undefined
275+
*/
276+
find(callback) {
277+
for (let current = this.first, position = 0; // <1>
278+
current; // <2>
279+
position += 1, current = current.next) { // <3>
280+
const result = callback(current, position); // <4>
281+
282+
if (result !== undefined) {
283+
return result; // <5>
284+
}
285+
}
286+
return undefined; // not found
287+
}
288+
289+
/**
290+
* @deprecated use removeByNode or removeByPosition
291+
* Removes the first occurrence of the specified elementt
292+
* from this list, if it is present.
293+
* Runtime: O(n)
294+
* @param {any} callbackOrIndex callback or position index to remove
295+
*/
296+
remove(callbackOrIndex) {
297+
if (typeof callbackOrIndex !== 'function') {
298+
return this.removeByPosition(parseInt(callbackOrIndex, 10) || 0);
299+
}
300+
301+
// find desired position to remove using #find
302+
const position = this.find((node, index) => {
303+
if (callbackOrIndex(node, index)) {
304+
return index;
305+
}
306+
return undefined;
307+
});
308+
309+
if (position !== undefined) { // zero-based position.
310+
return this.removeByPosition(position);
311+
}
312+
313+
return false;
314+
}
306315
}
307316

308317
// Aliases

Diff for: src/data-structures/linked-lists/linked-list.spec.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,16 @@ describe('LinkedList Test', () => {
155155
});
156156

157157
it('should return undefined if not found', () => {
158-
expect(linkedList.remove(2)).toBe(undefined);
159-
expect(linkedList.remove(-2)).toBe(undefined);
158+
expect(linkedList.remove(2)).toBe(null);
159+
expect(linkedList.remove(-2)).toBe(null);
160160
});
161161

162162
it('should update size, last and first', () => {
163163
expect(linkedList.remove(0)).toBe(0);
164164
expect(linkedList.size).toBe(1);
165165
expect(linkedList.remove(0)).toBe('found');
166166
expect(linkedList.size).toBe(0);
167-
expect(linkedList.remove(0)).toBe(undefined);
167+
expect(linkedList.remove(0)).toBe(null);
168168
expect(linkedList.size).toBe(0);
169169
expect(linkedList.first).toBe(null);
170170
expect(linkedList.last).toBe(null);
@@ -173,15 +173,15 @@ describe('LinkedList Test', () => {
173173

174174
describe('#addAt', () => {
175175
it('should insert at the beginning', () => {
176-
const newNode = linkedList.add('first', 0);
176+
const newNode = linkedList.addAt('first', 0);
177177
expect(newNode.value).toBe('first');
178178
expect(newNode.next.value).toBe(0);
179179
expect(linkedList.size).toBe(3);
180180
expect(linkedList.first).toBe(newNode);
181181
});
182182

183183
it('should insert at the middle', () => {
184-
const newNode = linkedList.add('middle', 1);
184+
const newNode = linkedList.addAt('middle', 1);
185185
expect(newNode.value).toBe('middle');
186186
// checking the 4 surrounding links were updated
187187
expect(newNode.next.value).toBe('found');
@@ -194,7 +194,7 @@ describe('LinkedList Test', () => {
194194
});
195195

196196
it('should insert at the end', () => {
197-
const newNode = linkedList.add('end', 2);
197+
const newNode = linkedList.addAt('end', 2);
198198
expect(newNode.value).toBe('end');
199199
expect(newNode.next).toBe(null);
200200
expect(newNode.previous.value).toBe('found');
@@ -203,7 +203,7 @@ describe('LinkedList Test', () => {
203203
});
204204

205205
it('should not insert out of bound', () => {
206-
const newNode = linkedList.add('out-of-bound', 3);
206+
const newNode = linkedList.addAt('out-of-bound', 3);
207207
expect(newNode).toBe(undefined);
208208
expect(linkedList.last.value).toBe('found');
209209
expect(linkedList.size).toBe(2);

Diff for: src/data-structures/linked-lists/node.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Node with reference to next and previous element
44
*/
55
class Node {
6-
constructor(value) {
6+
constructor(value = null) {
77
this.value = value;
88
this.next = null;
99
this.previous = null; // for doubly linked list

0 commit comments

Comments
 (0)