Skip to content

Commit 75969d5

Browse files
authored
feat: take, drop, takeWhile, dropWhile, and more (#14)
* feat: `average` * test: NaN checks for reducers * feat: `drop` * feat: `dropUntil` * feat: `takeWhile` * feat: `dropWhile`
1 parent 89e61d1 commit 75969d5

File tree

5 files changed

+245
-0
lines changed

5 files changed

+245
-0
lines changed

fp.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@ export const includes = curryIterFunction(reducers.includes);
1818
export const find = curryIterFunction(reducers.find);
1919
export const findIndex = curryIterFunction(reducers.findIndex);
2020
export const sum = reducers.sum;
21+
export const average = reducers.average;
2122
export const product = reducers.product;
2223
export const norm = reducers.norm;
2324

2425
// Transformers
2526
export const map = curryIterFunction(transformers.map);
2627
export const flatMap = curryIterFunction(transformers.flatMap);
2728
export const take = curryIterFunction(transformers.take);
29+
export const takeWhile = curryIterFunction(transformers.takeWhile);
30+
export const drop = curryIterFunction(transformers.drop);
31+
export const dropUntil = curryIterFunction(transformers.dropUntil);
32+
export const dropWhile = curryIterFunction(transformers.dropWhile);
2833
export const until = curryIterFunction(transformers.until);
2934
export const filter = curryIterFunction(transformers.filter);
3035
export const indexedPairs = transformers.indexedPairs;

lib/reducers.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,30 @@ export function sum(it: Iterable<number>): number {
301301
return reduce(it, (acc, n) => acc + n, 0, (acc) => isNaN(acc));
302302
}
303303

304+
/**
305+
* Finds the average of all items in `it`.
306+
*
307+
* :warning: When ran on an endless iterable, this never returns.
308+
* @param it - The iterable to calculate the average of.
309+
* @returns The average of all items in `it`.
310+
* @example
311+
* ```ts
312+
* import * as iter from "https://deno.land/x/iter";
313+
*
314+
* const myRange = iter.create.range(1, 100);
315+
* console.log(iter.average(myRange)); // -> 50.5
316+
*/
317+
export function average(it: Iterable<number>): number {
318+
let count = 0;
319+
let accumulator = 0;
320+
for (const item of it) {
321+
accumulator += item;
322+
if (isNaN(accumulator)) break;
323+
count++;
324+
}
325+
return accumulator / count;
326+
}
327+
304328
/**
305329
* Finds the product of all items in `it`.
306330
*

lib/reducers_test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,27 @@ Deno.test("includes", () => {
108108

109109
Deno.test("sum", () => {
110110
assertEquals(reducers.sum([0, 3, 2, 4, -2, 5, 1, -3]), 10);
111+
112+
const withNaN = concat(range(10), [NaN], increments());
113+
assertEquals(reducers.sum(withNaN), NaN);
114+
});
115+
116+
Deno.test("avergage", () => {
117+
assertEquals(reducers.average([0, 3, 2, 4, -2, 5, 1, -3]), 1.25);
118+
assertEquals(reducers.average(range(1, 100)), 50.5);
119+
120+
const withNaN = concat(range(10), [NaN], increments());
121+
assertEquals(reducers.average(withNaN), NaN);
111122
});
112123

113124
Deno.test("product", () => {
114125
assertEquals(reducers.product(range(1, 9)), 362880);
115126

116127
const determinableEndless = concat(range(100), [0], increments());
117128
assertEquals(reducers.product(determinableEndless), 0);
129+
130+
const withNaN = concat(range(1, 9), [NaN], increments());
131+
assertEquals(reducers.product(withNaN), NaN);
118132
});
119133

120134
Deno.test("norm", () => {

lib/transformers.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,39 @@ export function take<T>(it: Iterable<T>, n: number): IterableCircular<T> {
155155
};
156156
}
157157

158+
/**
159+
* Returns a new iterable containing the items of `it` except the first `n` items.
160+
* @param it - The iterable being taken from.
161+
* @param n - The number of items to drop.
162+
* @typeParam T - The type of items in both `it` and the returned iterable.
163+
* @returns A new iterable of `it` which skips the first `n` items.
164+
* @example
165+
* ```ts
166+
* import * as iter from "https://deno.land/x/iter/mod.ts";
167+
*
168+
* const numbers = iter.create.range(1, 10);
169+
* const from7 = iter.drop(numbers, 6);
170+
*
171+
* for (const num of from7) {
172+
* console.log(num);
173+
* }
174+
*
175+
* // -> 7
176+
* // -> 8
177+
* // -> 9
178+
* // -> 10
179+
* ```
180+
*/
181+
export function drop<T>(it: Iterable<T>, n: number): IterableCircular<T> {
182+
return {
183+
*[Symbol.iterator]() {
184+
const iterator = it[Symbol.iterator]();
185+
for (let i = 0; i < n; i++) iterator.next();
186+
yield* { [Symbol.iterator]: () => iterator };
187+
},
188+
};
189+
}
190+
158191
/**
159192
* Returns a new iterable which yields until `f` returns true.
160193
*
@@ -208,6 +241,126 @@ export function until<T>(
208241
};
209242
}
210243

244+
/**
245+
* Returns a new iterable which skips items from `it` until `f` returns true.
246+
* true.
247+
* @param it - The iterable being skipped.
248+
* @param {IterablePredicateCallback} f - A function that accepts up to three
249+
* arguments. The `dropUntil` function calls `f` one time for each item
250+
* in the iterable until `f` returns true.
251+
* @param includeFirst - Whether the item for which `f` returns true should be
252+
* included.
253+
* @typeParam T - The type of items in both `it` and the returned iterable.
254+
* @returns A new iterable of `it` which begins at the first element where `f` returns true
255+
* @example
256+
* ```ts
257+
* import * as iter from "https://deno.land/x/iter/mod.ts";
258+
*
259+
* const numbers = iter.create.range(1, 10);
260+
* const dropped = iter.dropUntil(numbers, (n) => n >= 5);
261+
*
262+
* for (const num of dropped) {
263+
* console.log(num);
264+
* }
265+
*
266+
* // -> 5
267+
* // -> 6
268+
* // -> 7
269+
* // -> 8
270+
* // -> 9
271+
* // -> 10
272+
* ```
273+
*/
274+
export function dropUntil<T>(
275+
it: Iterable<T>,
276+
f: IterablePredicateCallback<T>,
277+
includeFirst = true,
278+
): IterableCircular<T> {
279+
return {
280+
*[Symbol.iterator]() {
281+
let index = 0;
282+
let dropping = true;
283+
for (const item of it) {
284+
if (dropping) {
285+
dropping = !f(item, index++, it);
286+
if (dropping || !includeFirst) continue;
287+
}
288+
yield item;
289+
}
290+
},
291+
};
292+
}
293+
294+
/**
295+
* Returns a new iterable which yields while `f` returns true.
296+
*
297+
* @param it - The iterable being cut.
298+
* @param {IterablePredicateCallback} f - A function that accepts up to three
299+
* arguments. The `takeWhile` function calls `f` one time for each item in the iterable.
300+
* included.
301+
* @typeParam T - The type of items in both `it` and the returned iterable.
302+
* @returns A new iterables of `it` which terminates
303+
* @example
304+
* ```ts
305+
* import * as iter from "https://deno.land/x/iter/mod.ts";
306+
*
307+
* const naturals = iter.create.increments(1);
308+
* const numbers = iter.takeWhile(naturals, (n) => n <= 5);
309+
*
310+
* for (const num of numbers) {
311+
* console.log(num);
312+
* }
313+
*
314+
* // -> 1
315+
* // -> 2
316+
* // -> 3
317+
* // -> 4
318+
* // -> 5
319+
* ```
320+
*/
321+
export function takeWhile<T>(
322+
it: Iterable<T>,
323+
f: IterablePredicateCallback<T>,
324+
): IterableCircular<T> {
325+
return until(it, (...args) => !f(...args), false);
326+
}
327+
328+
/**
329+
* Returns a new iterable which skips items from `it` while `f` returns true.
330+
* true.
331+
* @param it - The iterable being skipped.
332+
* @param {IterablePredicateCallback} f - A function that accepts up to three
333+
* arguments. The `dropWhile` function calls `f` one time for each item
334+
* in the iterable.
335+
*
336+
* @typeParam T - The type of items in both `it` and the returned iterable.
337+
* @returns A new iterable of `it` which begins at the first element where `f` returns false
338+
* @example
339+
* ```ts
340+
* import * as iter from "https://deno.land/x/iter/mod.ts";
341+
*
342+
* const numbers = iter.create.range(1, 10);
343+
* const dropped = iter.dropWhile(numbers, (n) => n < 5);
344+
*
345+
* for (const num of dropped) {
346+
* console.log(num);
347+
* }
348+
*
349+
* // -> 5
350+
* // -> 6
351+
* // -> 7
352+
* // -> 8
353+
* // -> 9
354+
* // -> 10
355+
* ```
356+
*/
357+
export function dropWhile<T>(
358+
it: Iterable<T>,
359+
f: IterablePredicateCallback<T>,
360+
): IterableCircular<T> {
361+
return dropUntil(it, (...args) => !f(...args));
362+
}
363+
211364
/**
212365
* Returns the items of an iterable that meet the condition specified in a
213366
* callback function.

lib/transformers_test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ Deno.test("take", () => {
1414
assertEquals(testArray.slice(0, 5), [...transformers.take(testIter, 5)]);
1515
});
1616

17+
Deno.test("drop", () => {
18+
const testArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
19+
const testIter = testArray[Symbol.iterator]();
20+
21+
assertEquals(testArray.slice(5), [...transformers.drop(testIter, 5)]);
22+
23+
const numbers = create.range(1, 10);
24+
25+
assertEquals([...numbers].slice(6), [...transformers.drop(numbers, 6)]);
26+
});
27+
1728
Deno.test("map", () => {
1829
const id: (x: number) => number = (x) => x;
1930

@@ -83,6 +94,44 @@ Deno.test("until", () => {
8394
assertEquals(cutNumbersExclusive[cutNumbersExclusive.length - 1], 4);
8495
});
8596

97+
Deno.test("dropUntil", () => {
98+
const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
99+
100+
const cutNumbers = [...transformers.dropUntil(numbers, (n) => n === 5)];
101+
const cutNumbersExclusive = [
102+
...transformers.dropUntil(numbers, (n) => n === 5, false),
103+
];
104+
105+
assertEquals(cutNumbers, [5, 6, 7, 8, 9]);
106+
assertEquals(cutNumbersExclusive, [6, 7, 8, 9]);
107+
108+
{
109+
const numbers = create.range(1, 10);
110+
const dropped = transformers.dropUntil(numbers, (n) => n >= 5);
111+
const droppedExclusive = transformers.dropUntil(
112+
numbers,
113+
(n) => n >= 5,
114+
false,
115+
);
116+
assertEquals([...dropped], [5, 6, 7, 8, 9, 10]);
117+
assertEquals([...droppedExclusive], [6, 7, 8, 9, 10]);
118+
}
119+
});
120+
121+
Deno.test("takeWhile", () => {
122+
const numbers = create.range(1, 10);
123+
124+
const taken = transformers.takeWhile(numbers, (n) => n <= 5);
125+
assertEquals([...taken], [1, 2, 3, 4, 5]);
126+
});
127+
128+
Deno.test("dropWhile", () => {
129+
const numbers = create.range(1, 10);
130+
131+
const dropped = transformers.dropWhile(numbers, (n) => n <= 5);
132+
assertEquals([...dropped], [6, 7, 8, 9, 10]);
133+
});
134+
86135
Deno.test("indexedPairs", () => {
87136
const numbers = [9, 8, 7, 6, 5, 4, 3, 2, 1];
88137
[...transformers.indexedPairs(numbers)].forEach(([i, v]) => {

0 commit comments

Comments
 (0)