summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2025-07-11 18:37:47 -0300
committerEuAndreh <eu@euandre.org>2025-07-11 18:37:47 -0300
commiteec483093df7075cc94f686b1440f0dcfd76f466 (patch)
treed3d26325bc1aca97758253203b743dd271e38227
parentsrc/sjs.mjs: Add isNumeric() (diff)
downloadsjs-eec483093df7075cc94f686b1440f0dcfd76f466.tar.gz
sjs-eec483093df7075cc94f686b1440f0dcfd76f466.tar.xz
src/sjs.mjs: Add reductions()
-rw-r--r--src/sjs.mjs29
-rw-r--r--tests/sjs.mjs70
2 files changed, 96 insertions, 3 deletions
diff --git a/src/sjs.mjs b/src/sjs.mjs
index 2c9d551..e9da7fc 100644
--- a/src/sjs.mjs
+++ b/src/sjs.mjs
@@ -294,9 +294,8 @@ export const reduced = x => new Reduced(x);
const reduceRec = (coll, fn, acc, index) =>
isReduced(acc) ? acc.value :
- index === coll.length
- ? acc
- : reduceRec(
+ index === coll.length ? acc :
+ reduceRec(
coll,
fn,
fn(acc, coll[index], index, coll),
@@ -308,6 +307,30 @@ export const reduce = (coll, fn, init) =>
? reduceRec(coll.slice(1), fn, coll[0], 0)
: reduceRec(coll, fn, init, 0);
+const reductionsRec = (coll, fn, acc, index, steps) => {
+ if (isReduced(acc)) {
+ return steps.concat(acc.value);
+ }
+
+ if (index === coll.length) {
+ return steps;
+ }
+
+ const newAcc = fn(acc, coll[index], index, coll);
+ return reductionsRec(
+ coll,
+ fn,
+ newAcc,
+ index + 1,
+ steps.concat(newAcc),
+ );
+};
+
+export const reductions = (coll, fn, init) =>
+ init === undefined
+ ? reductionsRec(coll.slice(1), fn, coll[0], 0, [coll[0]])
+ : reductionsRec(coll, fn, init, 0, []);
+
export const mapValues = (obj, fn) =>
Object.keys(obj).reduce(
(o, k) => ({
diff --git a/tests/sjs.mjs b/tests/sjs.mjs
index c8f52dd..9376429 100644
--- a/tests/sjs.mjs
+++ b/tests/sjs.mjs
@@ -32,6 +32,8 @@ import {
reduced,
reduceRec,
reduce,
+ reductionsRec,
+ reductions,
mapValues,
repeat,
runTests,
@@ -958,6 +960,72 @@ const test_reduce = t => {
});
};
+const test_reductionsRec = t => {
+ t.start("reductionsRec()");
+
+ t.testing("return the value if isReduced()", () => {
+ t.assertEq(
+ reductionsRec(null, null, reduced("abc"), null, []),
+ ["abc"],
+ );
+ });
+
+ t.testing("the bare value if at the end of the collection", () => {
+ t.assertEq(reductionsRec([], null, null, 0, "ret"), "ret");
+ t.assertEq(reductionsRec([1, 2], null, null, 2, "ret"), "ret");
+ });
+
+ t.testing("recurse while applying function", () => {
+ const expected = [{
+ acc: -5,
+ el: 4,
+ index: 3,
+ coll: [1, 2, 3, 4, 5],
+ ret: -1,
+ }, {
+ acc: -1,
+ el: 5,
+ index: 4,
+ coll: [1, 2, 3, 4, 5],
+ ret: 4,
+ }];
+ const calls = [];
+ const fn = (acc, el, index, coll) => {
+ const ret = acc + el;
+ calls.push({
+ acc,
+ el,
+ index,
+ coll,
+ ret,
+ });
+ return ret;
+ };
+ const coll = [1, 2, 3, 4, 5];
+ t.assertEq(reductionsRec(coll, fn, -5, 3, []), [-1, 4]);
+ t.assertEq(calls, expected);
+ });
+};
+
+const test_reductions = t => {
+ t.start("reductions()");
+
+ const add = (a, b) => a + b;
+
+
+ t.testing("with and without initial values", () => {
+ t.assertEq(reductions([1, 1, 1, 1], add), [1, 2, 3, 4]);
+ t.assertEq(reductions([1, 1, 1], add, 0), [1, 2, 3]);
+ t.assertEq(reductions([1, 1, 1], add, 1), [2, 3, 4]);
+ t.assertEq(reductions([1, 2, 3], add), [1, 3, 6]);
+ });
+
+ t.testing("the last values corresponds to reduce()", () => {
+ const arr = [1, 2, 3];
+ t.assertEq(last(reductions(arr, add)), reduce(arr, add));
+ });
+};
+
const test_mapValues = t => {
t.start("mapValues()");
@@ -1021,6 +1089,8 @@ runTests([
test_reduced,
test_reduceRec,
test_reduce,
+ test_reductionsRec,
+ test_reductions,
test_mapValues,
test_repeat,
]);