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 ba110e2

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

File tree

11 files changed

+191
-200
lines changed

11 files changed

+191
-200
lines changed

‎book/content/part02/linked-list.asc

Lines changed: 75 additions & 93 deletions
Large diffs are not rendered by default.

‎book/images/dll-add-first.png

54.7 KB
Loading[フレーム]

‎book/images/dll-add-last.png

57.2 KB
Loading[フレーム]

‎book/images/dll-insert-middle.png

47.5 KB
Loading[フレーム]

‎book/images/dll-remove-first.png

15.9 KB
Loading[フレーム]

‎book/images/dll-remove-last.png

15.6 KB
Loading[フレーム]

‎book/images/dll-remove-middle.png

14.3 KB
Loading[フレーム]

‎book/images/dll.png

9.99 KB
Loading[フレーム]

‎src/data-structures/linked-lists/linked-list.js

Lines changed: 108 additions & 99 deletions
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

‎src/data-structures/linked-lists/linked-list.spec.js

Lines changed: 7 additions & 7 deletions
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);

0 commit comments

Comments
(0)

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