@@ -16,13 +16,6 @@ class LinkedList {
1616 }
1717 // end::constructor[]
1818
19- /**
20- * Alias for size
21- */
22- get length() {
23- return this.size;
24- }
25-
2619 // tag::addFirst[]
2720 /**
2821 * Adds element to the begining of the list. Similar to Array.unshift
@@ -34,9 +27,9 @@ class LinkedList {
3427
3528 newNode.next = this.first;
3629
37- if (this.first) {
38- this.first.previous = newNode;
39- } 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.
4033 this.last = newNode;
4134 }
4235
@@ -59,11 +52,11 @@ class LinkedList {
5952 addLast(value) {
6053 const newNode = new Node(value);
6154
62- if (this.first) {
55+ if (this.first) { // check if first node exists (list not empty)
6356 newNode.previous = this.last;
6457 this.last.next = newNode;
6558 this.last = newNode;
66- } else {
59+ } else { // if list is empty, first & last will point to newNode.
6760 this.first = newNode;
6861 this.last = newNode;
6962 }
@@ -78,195 +71,157 @@ class LinkedList {
7871 /**
7972 * Insert new element at the given position (index)
8073 *
74+ * Runtime: O(n)
75+ *
8176 * @param {any} value new node's value
8277 * @param {Number} position position to insert element
83- * @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.
8479 */
85- add(value, position = 0) {
86- if (position === 0) { // <1>
87- return this.addFirst(value);
88- }
80+ addAt(value, position = 0) {
81+ if (position === 0) return this.addFirst(value); // <1>
82+ if (position === this.size) return this.addLast(value); // <2>
8983
90- if (position === this.size) { // <2>
91- return this.addLast(value);
92- }
9384 // Adding element in the middle
94- const current = this.get(position);
95- if (current) {
96- const newNode = new Node(value); // <3>
97- newNode.previous = current.previous; // <4>
98- newNode.next = current; // <5>
99-
100- current.previous.next = newNode; // <6>
101- current.previous = newNode; // <7>
102- this.size += 1;
103- return newNode;
104- }
105-
106- return undefined; // out of bound index
85+ const current = this.findBy({ index: position }).node;
86+ if (!current) return undefined; // out of bound index
87+
88+ const newNode = new Node(value); // <3>
89+ newNode.previous = current.previous; // <4>
90+ newNode.next = current; // <5>
91+ current.previous.next = newNode; // <6>
92+ current.previous = newNode; // <7>
93+ this.size += 1;
94+ return newNode;
10795 }
10896 // end::addMiddle[]
10997
11098 // tag::searchByValue[]
11199 /**
100+ * @deprecated use findBy
112101 * Search by value. It finds first occurrence of
113- * the element matching the value.
102+ * the position of element matching the value.
103+ * Similar to Array.indexOf.
104+ *
114105 * Runtime: O(n)
106+ *
115107 * @example: assuming a linked list with: a -> b -> c
116- * linkedList.indexOf ('b') // ↪️ 1
117- * linkedList.indexOf ('z') // ↪️ undefined
108+ * linkedList.getIndexByValue ('b') // ↪️ 1
109+ * linkedList.getIndexByValue ('z') // ↪️ undefined
118110 * @param {any} value
119111 * @returns {number} return index or undefined
120112 */
121- indexOf(value) {
122- return this.find((current, position) => {
123- if (current.value === value) {
124- return position;
125- }
126- return undefined;
127- });
113+ getIndexByValue(value) {
114+ return this.findBy({ value }).index;
128115 }
129116 // end::searchByValue[]
130117
131118 // tag::searchByIndex[]
132119 /**
120+ * @deprecated use findBy directly
133121 * Search by index
134122 * Runtime: O(n)
135123 * @example: assuming a linked list with: a -> b -> c
136124 * linkedList.get(1) // ↪️ 'b'
137125 * linkedList.get(40) // ↪️ undefined
138126 * @param {Number} index position of the element
139- * @returns {Node} element at the specified position in this list.
127+ * @returns {Node|undefined} element at the specified position in
128+ * this list or undefined if was not found.
140129 */
141130 get(index = 0) {
142- return this.find((current, position) => {
143- if (position === index) {
144- return current;
145- }
146- return undefined;
147- });
131+ return this.findBy({ index }).node;
148132 }
149133 // end::searchByIndex[]
150134
151135 // tag::find[]
152136 /**
153- * Iterate through the list until callback returns a truthy value
154- * @example see #get and #indexOf
155- * @param {Function} callback evaluates current node and index.
156- * If any value other than undefined it's returned it will stop the search.
157- * @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}}
158148 */
159- find(callback ) {
149+ findBy({ value, index = Infinity } = {} ) {
160150 for (let current = this.first, position = 0; // <1>
161- current; // <2>
151+ current && position <= index ; // <2>
162152 position += 1, current = current.next) { // <3>
163- const result = callback(current, position); // <4>
164-
165- if (result !== undefined) {
166- return result; // <5>
153+ if (position === index || value === current.value) { // <4>
154+ return { node: current, index: position }; // <5>
167155 }
168156 }
169- return undefined ; // not found
157+ return {} ; // not found
170158 }
171159 // end::find[]
172160
173161
174162 // tag::removeFirst[]
175163 /**
176164 * Removes element from the start of the list (head/root).
177- * Similar to Array.shift
165+ * Similar to Array.shift().
178166 * Runtime: O(1)
179167 * @returns {any} the first element's value which was removed.
180168 */
181169 removeFirst() {
170+ if (!this.first) return null; // Check if list is already empty.
182171 const head = this.first;
183172
184- if (head) {
185- this.first = head.next;
186- if (this.first) {
187- this.first.previous = null;
188- } else {
189- this.last = null;
190- }
191- 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;
192178 }
193- return head && head.value;
179+ this.size -= 1;
180+ return head.value;
194181 }
195182 // end::removeFirst[]
196183
197184 // tag::removeLast[]
198185 /**
199- * Removes element to the end of the list. Similar to Array.pop
200- * 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().
201188 * Runtime: O(1)
202- * @returns {value } the last element's value which was removed
189+ * @returns {any } the last element's value which was removed
203190 */
204191 removeLast() {
192+ if (!this.last) return null; // Check if list is already empty.
205193 const tail = this.last;
206194
207- if (tail) {
208- this.last = tail.previous;
209- if (this.last) {
210- this.last.next = null;
211- } else {
212- this.first = null;
213- }
214- 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;
215200 }
216- return tail && tail.value;
201+ this.size -= 1;
202+ return tail.value;
217203 }
218204 // end::removeLast[]
219205
220206 // tag::removeByPosition[]
221207 /**
222- * Removes the element at the specified position in this list.
208+ * Removes the element at the given position (index) in this list.
223209 * Runtime: O(n)
224210 * @param {any} position
225211 * @returns {any} the element's value at the specified position that was removed.
226212 */
227213 removeByPosition(position = 0) {
228- const current = this.get(position);
229-
230- if (position === 0) {
231- this.removeFirst();
232- } else if (position === this.size - 1) {
233- this.removeLast();
234- } else if (current) {
235- current.previous.next = current.next;
236- current.next.previous = current.previous;
237- this.size -= 1;
238- }
239-
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;
240221 return current && current.value;
241222 }
242223 // end::removeByPosition[]
243224
244- /**
245- * Removes the first occurrence of the specified elementt
246- * from this list, if it is present.
247- * Runtime: O(n)
248- * @param {any} callbackOrIndex callback or position index to remove
249- */
250- remove(callbackOrIndex) {
251- if (typeof callbackOrIndex !== 'function') {
252- return this.removeByPosition(parseInt(callbackOrIndex, 10) || 0);
253- }
254-
255- // find desired position to remove using #find
256- const position = this.find((node, index) => {
257- if (callbackOrIndex(node, index)) {
258- return index;
259- }
260- return undefined;
261- });
262-
263- if (position !== undefined) { // zero-based position.
264- return this.removeByPosition(position);
265- }
266-
267- return false;
268- }
269-
270225 /**
271226 * Remove element by Node
272227 * O(1)
@@ -302,6 +257,61 @@ class LinkedList {
302257 const parts = [...this]; // see [Symbol.iterator]()
303258 return parts.map((n) => util.inspect(n.node.value)).join(' -> ');
304259 }
260+
261+ /**
262+ * Alias for size
263+ */
264+ get length() {
265+ return this.size;
266+ }
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+ }
305315}
306316
307317// Aliases
0 commit comments