Skip to content

Commit fad1ec9

Browse files
authored
initial implementation (#1)
1 parent d75a562 commit fad1ec9

File tree

1 file changed

+327
-0
lines changed

1 file changed

+327
-0
lines changed

lib/main.js

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
/**
2+
* @param {object} value
3+
*
4+
* @return {boolean}
5+
*/
6+
const isIterator = function(value) {
7+
return (Symbol.iterator in value);
8+
};
9+
10+
/**
11+
* @callback MapCallback
12+
*
13+
* @param {object} item
14+
* @param {number} index
15+
*
16+
* @return {object}
17+
*/
18+
19+
/**
20+
* @callback ForEachCallback
21+
*
22+
* @param {object} item
23+
* @param {number} index
24+
*
25+
* @return {undefined}
26+
*/
27+
28+
/**
29+
* @callback FilterCallback
30+
*
31+
* @param {object} item
32+
* @param {number} index
33+
*
34+
* @return {boolean}
35+
*/
36+
37+
/**
38+
* @callback ReduceCallback
39+
*
40+
* @param {object} initial
41+
* @param {object} item
42+
* @param {number} index
43+
*
44+
* @return {object}
45+
*/
46+
47+
/**
48+
* Wrapper Object for iterable objects that provides well known array methods
49+
* for any kind of iterable value. This object itself implements the iterator
50+
* protocol and is therefore more or less invisible.
51+
*
52+
* @example
53+
* for (let a of x) {}
54+
* // behaves equal to:
55+
* for (let a of Iterator.new(x)) {}
56+
*/
57+
export const Iterator = {
58+
ref: null,
59+
60+
get [Symbol.iterator]() {
61+
return this.ref[Symbol.iterator].bind(this.ref);
62+
},
63+
64+
/**
65+
* Maps all values of the iterable via the lambda function
66+
* and returns a new iterator.
67+
*
68+
* @param {MapCallback} lambda
69+
*
70+
* @return {Iterator}
71+
*/
72+
map(lambda) {
73+
return Iterator.new(this.rawMap(lambda));
74+
},
75+
76+
/**
77+
* @param {MapCallback} lambda
78+
*/
79+
*rawMap(lambda) {
80+
let index = 0;
81+
82+
for (const item of this.ref) {
83+
yield lambda(item, index);
84+
85+
index += 1;
86+
}
87+
},
88+
89+
/**
90+
* Applies the lambda function to each item of the interable.
91+
*
92+
* @param {ForEachCallback} lambda
93+
*/
94+
forEach(lambda) {
95+
let index = 0;
96+
97+
for (const item of this.ref) {
98+
lambda(item, index);
99+
100+
index += 1;
101+
}
102+
},
103+
104+
/**
105+
* Filters the values of the iterable by applying the lambda function to
106+
* each of them.
107+
*
108+
* @param {FilterCallback} lambda
109+
*
110+
* @return {Iterator}
111+
*/
112+
filter(lambda) {
113+
return Iterator.new(this.rawFilter(lambda));
114+
},
115+
116+
/**
117+
* @param {FilterCallback} lambda
118+
*/
119+
*rawFilter(lambda) {
120+
let index = 0;
121+
122+
for (const item of this.ref) {
123+
const keep = lambda(item, index);
124+
125+
index += 1;
126+
127+
if (!keep) {
128+
continue;
129+
}
130+
131+
yield item;
132+
}
133+
},
134+
135+
/**
136+
* Reduces the iterable into a single value by applying the lambda function
137+
* to each item.
138+
*
139+
* @param {ReduceCallback} lambda
140+
* @param {object} initial
141+
*
142+
* @return {object}
143+
*/
144+
reduce(lambda, initial) {
145+
let index = 0;
146+
147+
for (const item of this.ref) {
148+
initial = lambda(initial, item, index);
149+
150+
index += 1;
151+
}
152+
153+
return initial;
154+
},
155+
156+
/**
157+
* Locates a value inside the iterable by applying the lambda function
158+
* to each element until it returns true.
159+
*
160+
* @param {FilterCallback} lambda
161+
*
162+
* @return {object}
163+
*/
164+
find(lambda) {
165+
let index = -1;
166+
167+
for (const item of this.ref) {
168+
index += 1;
169+
170+
if (!lambda(item, index)) {
171+
continue;
172+
}
173+
174+
return item;
175+
}
176+
},
177+
178+
/**
179+
* Behaves like .find() but returns an index instead of a value.
180+
*
181+
* @param {FilterCallback} lambda
182+
*
183+
* @return {number}
184+
*/
185+
findIndex(lambda) {
186+
let index = -1;
187+
188+
for (const item of this.ref) {
189+
index += 1;
190+
191+
if (!lambda(item, index)) {
192+
continue;
193+
}
194+
195+
return index;
196+
}
197+
},
198+
199+
/**
200+
* Flattens a nested interable structure for n depth.
201+
*
202+
* @param {number} [depth=1]
203+
*
204+
* @return {Iterator}
205+
*/
206+
flat(depth = 1) {
207+
return Iterator.new(this.rawFlat(null, depth));
208+
},
209+
210+
/**
211+
* Applies both .map() and .flat()
212+
*
213+
* @param {MapCallback} lambda
214+
* @param {number} [depth=1]
215+
*
216+
* @return {Iterator}
217+
*/
218+
flatMap(lambda, depth = 1) {
219+
return this.map(lambda).flat(depth);
220+
},
221+
222+
/**
223+
* @param {number} [depth=1]
224+
*/
225+
*rawFlat(depth = 1) {
226+
for (const item of this.ref) {
227+
228+
if (depth === 0) {
229+
yield item;
230+
231+
continue;
232+
}
233+
234+
if (!isIterator(item)) {
235+
yield item;
236+
237+
continue;
238+
}
239+
240+
const innerIter = Iterator.new(item);
241+
242+
for (const inner of innerIter.rawFlat(null, depth - 1)) {
243+
yield inner;
244+
}
245+
}
246+
},
247+
248+
/**
249+
* Applies the lambda function to each item until it returns true.
250+
* If the iterable is completely consumed {false} is returned.
251+
*
252+
* @param {FilterCallback} lambda
253+
*
254+
* @return {boolean}
255+
*/
256+
some(lambda) {
257+
return !!this.find(lambda);
258+
},
259+
260+
/**
261+
* Removes duplicate entries from the iterable
262+
*
263+
* @param {Function} [identify=item=>item]
264+
* @param {Function} [merge=existing=>existing]
265+
*
266+
* @return {Iterator}
267+
*/
268+
dedupe(identify = item => item, merge = existing => existing) {
269+
const map = this.reduce((map, next) => {
270+
const id = identify(next);
271+
const existing = map.get(id);
272+
273+
if (existing ?? true) {
274+
map.set(id, next);
275+
276+
return map;
277+
}
278+
279+
map.set(id, merge(existing, next) ?? existing);
280+
281+
return map;
282+
}, new Map());
283+
284+
return Iterator.new(map.values());
285+
},
286+
287+
/**
288+
* @param {{ '[Symbol.iterator]': Function }} value
289+
* @return {Iterator}
290+
*/
291+
new(value) {
292+
if (!isIterator(value)) {
293+
throw new TypeError('value is does not implement iterator!');
294+
}
295+
296+
return { ref: value, __proto__: this };
297+
},
298+
299+
/**
300+
* converts iterator into an array.
301+
*
302+
* @return {Array}
303+
*/
304+
intoArray() {
305+
return Array.from(this.ref);
306+
},
307+
308+
/**
309+
* converts iterator into a set.
310+
*
311+
* @return {Set}
312+
*/
313+
intoSet() {
314+
return new Set(this.ref);
315+
},
316+
317+
/**
318+
* converts iterator into a map.
319+
*
320+
* @return {Map}
321+
*/
322+
intoMap() {
323+
return new Map(this.ref);
324+
}
325+
};
326+
327+
export default Iterator;

0 commit comments

Comments
 (0)