Skip to content

Commit 9ff9603

Browse files
committed
feat: Provide chunkify functions
1 parent 533cff4 commit 9ff9603

File tree

2 files changed

+112
-6
lines changed

2 files changed

+112
-6
lines changed

src/sequence.js

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ const into = curry('into', (seq, t) => Into.invoke(t, seq));
915915
* ```
916916
* # Remove odd numbers from a set
917917
* const st = new Set([1,1,2,2,3,4,5]);
918-
* into(filter(st, n => n % 2 == 0), Set) # Set(2,4)
918+
* into(filter(st, n => n % 2 === 0), Set) # Set(2,4)
919919
*
920920
* # Remove a key/value pair from an object
921921
* const obj = {foo: 42, bar: 5};
@@ -1053,7 +1053,7 @@ const map = curry('map', function* map(seq, fn) {
10531053
* Remove values from the sequence based on the given condition.
10541054
*
10551055
* ```
1056-
* filter(range(0,10), x => x%2 == 0) // [2,4,6,8]
1056+
* filter(range(0,10), x => x%2 === 0) // [2,4,6,8]
10571057
* ```
10581058
*
10591059
* @function
@@ -1448,7 +1448,7 @@ const zipLongest2 = curry('zipLongest2', (a, b, fallback) => zipLongest([a, b],
14481448
* Will throw IteratorEnded if the sequence is shorter than
14491449
* the given window.
14501450
*
1451-
* Returns an empty sequence if `no == 0`.
1451+
* Returns an empty sequence if `no === 0`.
14521452
*
14531453
* @function
14541454
* @param {Sequence} seq A sequence of sequences
@@ -1483,7 +1483,7 @@ const slidingWindow = curry('slidingWindow', (seq, no) => {
14831483
* Like slidingWindow, but returns an empty sequence if the given
14841484
* sequence is too short.
14851485
*
1486-
* Returns an empty sequence if `no == 0`.
1486+
* Returns an empty sequence if `no === 0`.
14871487
*
14881488
* @function
14891489
* @param {Sequence} seq A sequence of sequences
@@ -1529,6 +1529,8 @@ const trySlidingWindow = curry('trySlidingWindow', function* trySlidingWindow(se
15291529
* Try sliding window would yield an empty array in each of the examples
15301530
* above.
15311531
*
1532+
* Returns an empty sequence if `no === 0`.
1533+
*
15321534
* @function
15331535
* @param {Sequence} seq
15341536
* @param {Number} no Number of elements to look ahead to.
@@ -1540,6 +1542,91 @@ const lookahead = curry('lookahead', (seq, no, filler) => {
15401542
return trySlidingWindow(filled, no + 1);
15411543
});
15421544

1545+
/**
1546+
* Split the given input sequence into chunks of a specific length.
1547+
* The last chunk may be shorter than the given chunk size if the input
1548+
* sequence is not long enough.
1549+
*
1550+
* ```
1551+
* const { list, chunkifyShort } = require('ferrum');
1552+
* list(chunkifyShort([1,2,3,4,5], 2)); // => [[1,2], [3,4], [5]]
1553+
* ```
1554+
*
1555+
* Returns an empty sequence if `no === 0`.
1556+
*
1557+
* @function
1558+
* @param {Sequence} seq A sequence of sequences
1559+
* @param {Number} len The length of the chunk
1560+
* @returns {Iterator} Sequence of lists
1561+
*/
1562+
const chunkifyShort = curry('chunkifyShort', (seq, len) => {
1563+
if (len === 0) {
1564+
return iter([]);
1565+
}
1566+
1567+
const it = iter(seq);
1568+
return pipe(
1569+
repeatFn(() => takeShort(it, len)),
1570+
takeWhile(chunk => !empty(chunk)),
1571+
);
1572+
});
1573+
1574+
/**
1575+
* Split the given input sequence into chunks of a specific length.
1576+
* If the length of the sequence is not divisible by the chunk length
1577+
* IteratorEnded will be thrown.
1578+
*
1579+
* ```
1580+
* const { list, chunkify } = require('ferrum');
1581+
* list(chunkify([1,2,3,4], 2)); // => [[1,2], [3,4]]
1582+
* ```
1583+
*
1584+
* Returns an empty sequence if `no === 0`.
1585+
*
1586+
* @function
1587+
* @param {Sequence} seq A sequence of sequences
1588+
* @param {Number} len The length of the chunk
1589+
* @throws {IteratorEnded}
1590+
* @returns {Iterator} Sequence of lists
1591+
*/
1592+
const chunkify = curry('chunkify', (seq, len) => pipe(
1593+
chunkifyShort(seq, len),
1594+
map((chunk) => {
1595+
if (size(chunk) === len) {
1596+
return chunk;
1597+
} else {
1598+
throw new IteratorEnded('chunkify() needs sequences of the correct length!');
1599+
}
1600+
}),
1601+
));
1602+
1603+
/**
1604+
* Split the given input sequence into chunks of a specific length.
1605+
* If the input sequence is not long enough, the last chunk will be filled
1606+
* with the given fallback value.
1607+
*
1608+
* ```
1609+
* const { list, chunkifyWithFallback } = require('ferrum');
1610+
* list(chunkifyWithFallback([1,2,3,4,5], 2), 99); // => [[1,2], [3,4], [5, 99]]
1611+
* ```
1612+
*
1613+
* @function
1614+
* @param {Sequence} seq A sequence of sequences
1615+
* @param {Number} len The length of the chunk
1616+
* @param {Any} fallback The value to use if the input sequence is too short.
1617+
* @returns {Iterator} Sequence of lists
1618+
*/
1619+
const chunkifyWithFallback = curry('chunkifyWithFallback', (seq, len, fallback) => pipe(
1620+
chunkifyShort(seq, len),
1621+
map((chunk) => {
1622+
if (size(chunk) === len) {
1623+
return chunk;
1624+
} else {
1625+
return take(concat(chunk, repeat(fallback)), len);
1626+
}
1627+
}),
1628+
));
1629+
15431630
/**
15441631
* Calculate the cartesian product of the given sequences.
15451632
*
@@ -1728,6 +1815,9 @@ module.exports = {
17281815
slidingWindow,
17291816
trySlidingWindow,
17301817
lookahead,
1818+
chunkifyShort,
1819+
chunkify,
1820+
chunkifyWithFallback,
17311821
cartesian,
17321822
cartesian2,
17331823
mod,

test/sequence.test.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515

1616
const assert = require('assert');
1717
const {
18-
and, plus, or, mul, not,
18+
and, plus, or, mul, not, curry,
1919
size, TraitNotImplemented, _typedArrays,
2020
iter, range, range0, repeat, repeatFn, extend, extend1, flattenTree,
2121
IteratorEnded, next, tryNext, nth, first, second, last, tryNth, tryFirst,
2222
trySecond, tryLast, seqEq, each, find, tryFind, contains, count, list,
2323
uniq, join, dict, obj, into, foldl, foldr, any, all, sum, product, map,
2424
filter, reject, reverse, enumerate, trySkip, skip, skipWhile, tryTake,
25-
takeShort, takeWithFallback,
25+
takeShort, takeWithFallback, chunkifyShort, chunkify, chunkifyWithFallback,
2626
take, takeWhile, takeUntilVal, takeDef, flat, concat, prepend, append,
2727
mapSort, zipLeast, zip, zipLongest, zipLeast2, zip2, zipLongest2,
2828
slidingWindow, trySlidingWindow, lookahead, mod, union, union2,
@@ -426,6 +426,22 @@ it('lookahead', () => {
426426
ck([42, 23], 0, null, [[42], [23]]);
427427
});
428428

429+
it('chunkify/chunkifyShort/chunkifyWithFallback', () => {
430+
const withFallback = curry('withFallback', (seq, len) => chunkifyWithFallback(len, null)(seq));
431+
each([chunkify, withFallback, chunkifyShort], (fn) => {
432+
ckEqSeq(fn(2)([1, 2, 3, 4]), [[1, 2], [3, 4]]);
433+
ckEqSeq(fn(2)([]), []);
434+
ckEqSeq(fn(1)([1, 2, 3, 4]), [[1], [2], [3], [4]]);
435+
ckEqSeq(fn(1)([]), []);
436+
ckEqSeq(fn(0)([1, 2, 3, 4]), []);
437+
ckEqSeq(fn(0)([]), []);
438+
});
439+
440+
ckEqSeq(chunkifyShort([1, 2, 3, 4], 3), [[1, 2, 3], [4]]);
441+
ckEqSeq(chunkifyWithFallback([1, 2, 3, 4], 3, null), [[1, 2, 3], [4, null, null]]);
442+
ckThrows(IteratorEnded, () => list(chunkify([1, 2, 3, 4], 3)));
443+
});
444+
429445
it('cartesian', () => {
430446
ckEqSeq(cartesian([]), []);
431447
ckEqSeq(cartesian([[]]), []);

0 commit comments

Comments
 (0)