Implementing lazy evaluation for `reduceRight()` function in JavaScript - javascript

A problem in the codewars.com:
https://www.codewars.com/kata/foldr/train/javascript
Define a foldr function for array which implement the same function like built-in function reduceRight(). The problem is very abstract because I knew few about functional programming.
I find the most important is how to implement lazy-evaluation in the JavaScript. But I don't know how to deal with it.
For example, we have two functions, indexOf and logging, indexOf(x) is a function which will be called as arguments in the foldr method, logging is a wrapper function which tell us how many times indexOf(x) could be called.
const indexOf = y => function (cur, acc) {
if (cur === y) {
return 0
} else {
return acc + 1 || -1
}
};
const logging = fn => function logging(...a) {
i++;
return fn(...a);
};
If we implement it without laziness and use recursion:
Object.defineProperty(Array.prototype, "foldr", {
value: function foldr(fn, z) {
return function _foldr(a) {
if (a.length === 0) {
return z
}
return fn(a[0], _foldr(a.slice(1)))
}(this);
}
});
let i = 0
let x = [1, 2, 3].foldr(logging(indexOf(1)), -1)
console.log(`x: ${x}`) // x: 0
console.log(`i: ${i}`) // i: 3
The variable i shows that the function has been called 3 times, the whole array has been iterated. However, if we observe the indexOf funciton we'll find that we don't need to iterate the whole array if we use lazy evaluation.
In the first level of recursion
indexOf(1)(a[0], _foldr(a.slice(1))) equals indexOf(1)(1, _foldr([2,3])), because cur === y, it should return 0 immediately and doesn't need to evaluate the second argument _foldr([2,3]). So in the test case of the codewars.com, the i should be 1.
How could I deal with it?

You are close ...:
fn(a[0], () => _foldr(a.slice(1)))
If you pass a function instead of the accumulator value, the function can decide to evaluate the accumulator or not:
const indexOf = y => function (cur, acc) {
if (cur === y) {
return 0; // acc() was not called, ends here
} else {
return acc() + 1 || -1; // acc() gets called, traversal goes on
}
};

I find the solution. Use Object.prototype.valueOf()
The valueOf() method returns the primitive value of the specified object.
Because we'd like to call the function when we do some algorithms such as return acc + 1 || -1.
We could use:
Object.defineProperty(Array.prototype, 'foldr', {
value(fn, z) {
let _foldr = (a) => {
if (!a.length) return z;
let r = fn(a[0], { valueOf: () => _foldr(a.slice(1)) });
return (r.valueOf) ? r.valueOf() : r;
};
return _foldr(this);
},
});

Related

Apply callback to 2 arbitrary length arrays using functional programming in JavaScript

Given that these function declarations exist within scope:
function compose() {
var funcs = arguments;
return function() {
var args, i;
args = arguments;
for (i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(null, args)];
}
return args[0];
};
}
function id(x) {
return x;
}
function partial(targetfn) {
var arity = targetfn.length;
return function fn() {
if (arguments.length < arity) {
return Function.prototype.bind.apply(fn, [null].concat(
Array.prototype.slice.call(arguments)
));
} else {
return targetfn.apply(null, arguments);
}
}
}
Having this callback function as an example:
function cb(x, y) {
console.log(x);
console.log(y);
}
Is it possible using FP techniques (such as argument binding, composition and partial application) to apply the callback function to 2 arrays of arbitrary length? E.g.: ["foo", "bar", "baz", "quux"] and [12, 42]? The expected output in this case would be:
foo
12
foo
42
bar
12
bar
42
baz
12
...and so on
Since Array.prototype.forEach and Array.prototype.map pass 3 arguments to the callback function, it's at least possible to iterate over a single array using function composition as follows:
["foo", "bar", "baz", "quux"].forEach(compose(cb, id))
It's also possible to map an array into partially applied functions like so:
[12, 42].map(compose(partial(cb), id))
I've been looking into using Function.prototype.apply, Function.prototype.bind and Function.prototype.call, combining them with Array.prototype.forEach and Array.prototype.map, so far without success. I'm trying hard not to introduce more function declarations / expressions.
Update
Thanks to Mulan, the problem can be solved by splitting it into the part that computes the cartesian product of lists and applying the resulting list to a given function.
function product() {
var args = [].slice.call(arguments);
return args.reduce(function(prev, cur) {
var ret = [];
prev.forEach(function(x) {
cur.forEach(function(y) {
ret.push(x.concat(y));
});
});
return ret;
}, args.shift().map(function(val) {
return [val];
}));
}
product(
["foo", "bar", "baz", "quux"],
[12, 42]
).forEach(Function.prototype.apply.bind(cb, null));
I think you might be making more work for yourself by forcing compose and partial where they're not particularly useful. It seems like you're looking to compute a product of input arrays. The result is an array of products, each of which contains a unique grouping of elements taken from the input arrays -
function product(a, ...more) {
if (more.length == 0)
return a.map(v => [v])
else
return a.flatMap(v => product(...more).map(p => [v, ...p]))
}
function cb(x,y) {
console.log(x, y)
}
product(["foo", "bar", "baz", "quux"], [12, 42])
.forEach(p => cb(...p))
.as-console-wrapper { min-height: 100%; top: 0; }
If you really want to use partial, you can accept a callback cb as the first argument, and then all arrays to multiply thereafter. Each step in product applies one argument to cb. The result is an array of products where each product is an array of partially applied functions, which can be executed by calling cb() -
function partial(f, ...x) {
return (...y) => f(...x, ...y)
}
function product(cb, a, ...more) {
if (more.length == 0)
return a.map(v => partial(cb, v))
else
return a.flatMap(v => product(partial(cb, v), ...more))
}
const cb = partial(console.log, "i would like")
const res = product(cb,
["🍔", "🌮", "🥪"],
["🍟", "🥨"],
["🥤", "☕️"]
)
res.forEach(cb => cb())
.as-console-wrapper { min-height: 100%; top: 0; }

How to use currying function to work if one of the argument contains multiple value?

I have a higher order function , though the first case multiply(4,5) works as expected but is it possible to pass multiple argument like multiply(2)(4, 5). In this case the answer is 8, but can a curried function be created in such a way so that it gives 40 as result
function multiply(s) {
return function(b) {
return s * b;
}
}
console.log(multiply(4)(5))
console.log(multiply(2)(4, 5))
You could use rest parameters ... and collect all arguments on both functions and return the reduced result.
function multiply(...a) {
return function(...b) {
return [...a, ...b].reduce((a, b) => a * b);
};
}
console.log(multiply(4)(5)); // 20
console.log(multiply(2)(4, 5)); // 40
console.log(multiply(3, 2)(4, 5)); // 120
function multiply(s) {
return function(b) {
for(key in arguments)s*=arguments[key];
return s;
}
}
console.log(multiply(4)(5))
console.log(multiply(2)(4, 5))
I think its best to use arguments property in your case.
You could do something like this using arguments:
function multiply(s) {
return function () {
return Array.from(arguments).reduce(function(accumulator, currentValue) {
return accumulator * currentValue;
}, s);
}
}
You could do it this way if you have one or more b arguments :
function multiply(s) {
// Will contain the multiplication of all b arguments
var result = 1;
// ...b will return an the array arguments passed
return function(...b) {
// Loop through each of this array and update result
for (var i = 0; i < b.length; i++) {
result *= b[i];
}
// return the final result
return s * result;
}
}
console.log(multiply(4)(5))
console.log(multiply(2)(4, 5))

Getting range numbers using recursion in JavaScript

I am trying to get the range of numbers using recursion. Can someone explain to me why it isn't working?
function range(x,y){
var results = [];
if(x === y){
return results;
}
return results.push(range(x + 1,y));
}
range(1,5);
The beauty of recursion is that you don't need local variables (var results). You just pass state as arguments to each recursive iteration:
const concat = (xs, y) => xs.concat(y);
const range = (x, y) => {
const rec = (x, y, acc) => x < y ? rec(x + 1, y, concat(acc, x)) : acc;
return rec(x, y, []);
}
ES5 version in case you aren't familiar with the arrow syntax:
function concat(xs, y) {
return xs.concat(y);
}
function range(x, y) {
function rec(x, y, acc) {
return x < y ? rec(x + 1, y, concat(acc, x)) : acc;
}
return rec(x, y, []);
}
That isn't the most elegant solution though!
With recursion we can simply build up the stack with each recursive call. Each stack frame contains a computed partial result. Then we just need to unwind the stack and attach each partial result to an array:
const range = (x, y) => x < y ? [x].concat(range(x + 1, y)) : [];
Or more functional:
const concat = (xs, y) => xs.concat(y);
const range = (x, y) => x < y ? concat([x], range(x + 1, y)) : [];
Note that concat([x], range(x + 1, y)) is the recursive case and [] the base case.
Try this:
function rangeOfNumbers(startNum, endNum) {
if (startNum - endNum === 0) {
return [startNum];
} else {
const numbers = rangeOfNumbers(startNum + 1, endNum);
numbers.unshift(startNum);
return numbers;
}
};
Solution:
Solved this recursion problem, which is taking 2 numbers as input and returning back the array which contains range of the numbers inclusive of the startNumber and EndNumber
Assumption-> end_num is always greater than start_num
function rangeOfNumbers(start_num, end_num) {
if(start_num!==end_num){
let numbers = rangeOfNumbers(start_num+1,end_num);
numbers.unshift(start_num);
return numbers;
}
else
return [start_num];
};
Results will be always empty since you actually don't put anything in it.
What would work is this
function range(x,y){
var results = [];
if(x === y){
return results;
}
results.push(x);
return results.concat(range(x + 1,y));
}
range(1,5);
Let's firstly try to answer your "why" question before we give a solution because none of these answers explain your "why" question.
When you return results.push(<any argument>) the return value is the length of the array after the push. On the first call in your example, x does not equal y, so we are returning the call to push. You can think of it like this:
return array.push(<anything>) is going to be the same as:
array.push(<anything>)
return array.length
Therefore, you will always return the number 1 from this because the length of the array when you push the function call to it is 1. The content of that array will be another array that's nested all the way to the n levels deep where n is the range, but it's length will still be one and you will never see the content of this array unless you did it this way:
results.push(rangeOfNumbers(x+1, y))
return results;
In your example rangeOfNumbers(1, 5), if you logged that return value it would look like this:
[ [ [ [ [ ] ] ] ] ]
I solved it this way, but I like the functional solution that was posted by another user more:
function rangeOfNumbers(s, e) {
return s == e ? [s] : [s, ...rangeOfNumbers(s+1, e)];
}
function rangeOfNumbers(startNum, endNum) {
if (startNum>endNum){
return [];
}
else{
const range = rangeOfNumbers(startNum+1, endNum);
range.unshift(startNum);
return range;
}
};
//or more simple
function rangeOfNumbers(startNum, endNum) {
return endNum>=startNum?rangeOfNumbers(startNum,endNum-1).concat(endNum):[];
};
function rangeOfNumbers(firstNum, lastNum) {
if (firstNum - lastNum === 0) {
return [lastNum];
} else {
let rangeArray = rangeOfNumbers(firstNum, lastNum - 1);
rangeArray.push(lastNum);
return rangeArray;
}
}
console.log(rangeOfNumbers(1, 5))
Lots of clever solutions posted, but I think this is a use case for the plain old 'for loop'. It's easier to see what's happening, and it will be easier for new devs on your team. My example is inclusive (it will include the min value and the max value), and it has an optional step parameter which will default to 1 if not passed in.
function range(min, max, step = 1) {
let arr = []
for (let i = min; i <= max; i = i + step ) {
arr.push(i)
}
return arr
}

Currying a variadic function in JavaScript [duplicate]

I need a js sum function to work like this:
sum(1)(2) = 3
sum(1)(2)(3) = 6
sum(1)(2)(3)(4) = 10
etc.
I heard it can't be done. But heard that if adding + in front of sum can be done.
Like +sum(1)(2)(3)(4). Any ideas of how to do this?
Not sure if I understood what you want, but
function sum(n) {
var v = function(x) {
return sum(n + x);
};
v.valueOf = v.toString = function() {
return n;
};
return v;
}
console.log(+sum(1)(2)(3)(4));
JsFiddle
This is an example of using empty brackets in the last call as a close key (from my last interview):
sum(1)(4)(66)(35)(0)()
function sum(firstNumber) {
let accumulator = firstNumber;
return function adder(nextNumber) {
if (nextNumber === undefined) {
return accumulator;
}
accumulator += nextNumber;
return adder;
}
}
console.log(sum(1)(4)(66)(35)(0)());
I'm posting this revision as its own post since I apparently don't have enough reputation yet to just leave it as a comment. This is a revision of #Rafael 's excellent solution.
function sum (n) {
var v = x => sum (n + x);
v.valueOf = () => n;
return v;
}
console.log( +sum(1)(2)(3)(4) ); //10
I didn't see a reason to keep the v.toString bit, as it didn't seem necessary. If I erred in doing so, please let me know in the comments why v.toString is required (it passed my tests fine without it). Converted the rest of the anonymous functions to arrow functions for ease of reading.
New ES6 way and is concise.
You have to pass empty () at the end when you want to terminate the call and get the final value.
const sum= x => y => (y !== undefined) ? sum(x + y) : x;
call it like this -
sum(10)(30)(45)();
Here is a solution that uses ES6 and toString, similar to #Vemba
function add(a) {
let curry = (b) => {
a += b
return curry
}
curry.toString = () => a
return curry
}
console.log(add(1))
console.log(add(1)(2))
console.log(add(1)(2)(3))
console.log(add(1)(2)(3)(4))
Another slightly shorter approach:
const sum = a => b => b? sum(a + b) : a;
console.log(
sum(1)(2)(),
sum(3)(4)(5)()
);
Here's a solution with a generic variadic curry function in ES6 Javascript, with the caveat that a final () is needed to invoke the arguments:
const curry = (f) =>
(...args) => args.length? curry(f.bind(0, ...args)): f();
const sum = (...values) => values.reduce((total, current) => total + current, 0)
curry(sum)(2)(2)(1)() == 5 // true
Here's another one that doesn't need (), using valueOf as in #rafael's answer. I feel like using valueOf in this way (or perhaps at all) is very confusing to people reading your code, but each to their own.
The toString in that answer is unnecessary. Internally, when javascript performs a type coersion it always calls valueOf() before calling toString().
// invokes a function if it is used as a value
const autoInvoke = (f) => Object.assign(f, { valueOf: f } );
const curry = autoInvoke((f) =>
(...args) => args.length? autoInvoke(curry(f.bind(0, ...args))): f());
const sum = (...values) => values.reduce((total, current) => total + current, 0)
curry(sum)(2)(2)(1) + 0 == 5 // true
Try this
function sum (...args) {
return Object.assign(
sum.bind(null, ...args),
{ valueOf: () => args.reduce((a, c) => a + c, 0) }
)
}
console.log(+sum(1)(2)(3,2,1)(16))
Here you can see a medium post about carried functions with unlimited arguments
https://medium.com/#seenarowhani95/infinite-currying-in-javascript-38400827e581
Try this, this is more flexible to handle any type of input. You can pass any number of params and any number of paranthesis.
function add(...args) {
function b(...arg) {
if (arg.length > 0) {
return add(...[...arg, ...args]);
}
return [...args, ...arg].reduce((prev,next)=>prev + next);
}
b.toString = function() {
return [...args].reduce((prev,next)=>prev + next);
}
return b;
}
// Examples
console.log(add(1)(2)(3, 3)());
console.log(+add(1)(2)(3)); // 6
console.log(+add(1)(2, 3)(4)(5, 6, 7)); // 28
console.log(+add(2, 3, 4, 5)(1)()); // 15
Here's a more generic solution that would work for non-unary params as well:
const sum = function (...args) {
let total = args.reduce((acc, arg) => acc+arg, 0)
function add (...args2) {
if (args2.length) {
total = args2.reduce((acc, arg) => acc+arg, total)
return add
}
return total
}
return add
}
document.write( sum(1)(2)() , '<br/>') // with unary params
document.write( sum(1,2)() , '<br/>') // with binary params
document.write( sum(1)(2)(3)() , '<br/>') // with unary params
document.write( sum(1)(2,3)() , '<br/>') // with binary params
document.write( sum(1)(2)(3)(4)() , '<br/>') // with unary params
document.write( sum(1)(2,3,4)() , '<br/>') // with ternary params
ES6 way to solve the infinite currying. Here the function sum will return the sum of all the numbers passed in the params:
const sum = a => b => b ? sum(a + b) : a
sum(1)(2)(3)(4)(5)() // 15
function add(a) {
let curry = (b) => {
a += b
return curry;
}
curry[Symbol.toPrimitive] = (hint) => {
return a;
}
return curry
}
console.log(+add(1)(2)(3)(4)(5)); // 15
console.log(+add(6)(6)(6)); // 18
console.log(+add(7)(0)); // 7
console.log(+add(0)); // 0
Here is another functional way using an iterative process
const sum = (num, acc = 0) => {
if !(typeof num === 'number') return acc;
return x => sum(x, acc + num)
}
sum(1)(2)(3)()
and one-line
const sum = (num, acc = 0) => !(typeof num === 'number') ? acc : x => sum(x, acc + num)
sum(1)(2)(3)()
You can make use of the below function
function add(num){
add.sum || (add.sum = 0) // make sure add.sum exists if not assign it to 0
add.sum += num; // increment it
return add.toString = add.valueOf = function(){
var rtn = add.sum; // we save the value
return add.sum = 0, rtn // return it before we reset add.sum to 0
}, add; // return the function
}
Since functions are objects, we can add properties to it, which we are resetting when it's been accessed.
we can also use this easy way.
function sum(a) {
return function(b){
if(b) return sum(a+b);
return a;
}
}
console.log(sum(1)(2)(3)(4)(5)());
To make sum(1) callable as sum(1)(2), it must return a function.
The function can be either called or converted to a number with valueOf.
function sum(a) {
var sum = a;
function f(b) {
sum += b;
return f;
}
f.toString = function() { return sum }
return f
}
function sum(a){
let res = 0;
function getarrSum(arr){
return arr.reduce( (e, sum=0) => { sum += e ; return sum ;} )
}
function calculateSumPerArgument(arguments){
let res = 0;
if(arguments.length >0){
for ( let i = 0 ; i < arguments.length ; i++){
if(Array.isArray(arguments[i])){
res += getarrSum( arguments[i]);
}
else{
res += arguments[i];
}
}
}
return res;
}
res += calculateSumPerArgument(arguments);
return function f(b){
if(b == undefined){
return res;
}
else{
res += calculateSumPerArgument(arguments);
return f;
}
}
}
let add = (a) => {
let sum = a;
funct = function(b) {
sum += b;
return funct;
};
Object.defineProperty(funct, 'valueOf', {
value: function() {
return sum;
}
});
return funct;
};
console.log(+add(1)(2)(3))
After looking over some of the other solutions on here, I would like to provide my two solutions to this problem.
Currying two items using ES6:
const sum = x => y => (y !== undefined ) ? +x + +y : +x
sum(2)(2) // 4
Here we are specifying two parameters, if the second one doesnt exist we just return the first parameter.
For three or more items, it gets a bit trickier; here is my solution. For any additional parameters you can add them in as a third
const sum = x => (y=0) => (...z) => +x + +y + +z.reduce((prev,curr)=>prev+curr,0)
sum(2)()()//2
sum(2)(2)()//4
sum(2)(2)(2)//6
sum(2)(2)(2,2)//8
I hope this helped someone

How to early break reduce() method?

How can I break the iteration of reduce() method?
for:
for (var i = Things.length - 1; i >= 0; i--) {
if(Things[i] <= 0){
break;
}
};
reduce()
Things.reduce(function(memo, current){
if(current <= 0){
//break ???
//return; <-- this will return undefined to memo, which is not what I want
}
}, 0)
You CAN break on any iteration of a .reduce() invocation by mutating the 4th argument of the reduce function: "array". No need for a custom reduce function. See Docs for full list of .reduce() parameters.
Array.prototype.reduce((acc, curr, i, array))
The 4th argument is the array being iterated over.
const array = ['apple', '-pen', '-pineapple', '-pen'];
const x = array
.reduce((acc, curr, i, arr) => {
if(i === 2) arr.splice(1); // eject early
return acc += curr;
}, '');
console.log('x: ', x); // x: apple-pen-pineapple
WHY?:
The one and only reason I can think of to use this instead of the many other solutions presented is if you want to maintain a functional programming methodology to your algorithm, and you want the most declarative approach possible to accomplish that. If your entire goal is to literally REDUCE an array to an alternate non-falsey primitive (string, number, boolean, Symbol) then I would argue this IS in fact, the best approach.
WHY NOT?
There's a whole list of arguments to make for NOT mutating function parameters as it's a bad practice.
UPDATE
Some of the commentators make a good point that the original array is being mutated in order to break early inside the .reduce() logic.
Therefore, I've modified the answer slightly by adding a .slice(0) before calling a follow-on .reduce() step, yielding a copy of the original array.
NOTE: Similar ops that accomplish the same task are slice() (less explicit), and spread operator [...array] (slightly less performant). Bear in mind, all of these add an additional constant factor of linear time to the overall runtime ... + O(n).
The copy, serves to preserve the original array from the eventual mutation that causes ejection from iteration.
const array = ['apple', '-pen', '-pineapple', '-pen'];
const x = array
.slice(0) // create copy of "array" for iterating
.reduce((acc, curr, i, arr) => {
if (i === 2) arr.splice(1); // eject early by mutating iterated copy
return (acc += curr);
}, '');
console.log("x: ", x, "\noriginal Arr: ", array);
// x: apple-pen-pineapple
// original Arr: ['apple', '-pen', '-pineapple', '-pen']
Don't use reduce. Just iterate on the array with normal iterators (for, etc) and break out when your condition is met.
You can use functions like some and every as long as you don't care about the return value. every breaks when the callback returns false, some when it returns true:
things.every(function(v, i, o) {
// do stuff
if (timeToBreak) {
return false;
} else {
return true;
}
}, thisArg);
Edit
A couple of comments that "this doesn't do what reduce does", which is true, but it can. Here's an example of using every in a similar manner to reduce that returns as soon as the break condition is reached.
// Soruce data
let data = [0,1,2,3,4,5,6,7,8];
// Multiple values up to 5 by 6,
// create a new array and stop processing once
// 5 is reached
let result = [];
data.every(a => a < 5? result.push(a*6) : false);
console.log(result);
This works because the return value from push is the length of the result array after the new element has been pushed, which will always be 1 or greater (hence true), otherwise it returns false and the loop stops.
There is no way, of course, to get the built-in version of reduce to exit prematurely.
But you can write your own version of reduce which uses a special token to identify when the loop should be broken.
var EXIT_REDUCE = {};
function reduce(a, f, result) {
for (let i = 0; i < a.length; i++) {
let val = f(result, a[i], i, a);
if (val === EXIT_REDUCE) break;
result = val;
}
return result;
}
Use it like this, to sum an array but exit when you hit 99:
reduce([1, 2, 99, 3], (a, b) => b === 99 ? EXIT_REDUCE : a + b, 0);
> 3
Array.every can provide a very natural mechanism for breaking out of high order iteration.
const product = function(array) {
let accumulator = 1;
array.every( factor => {
accumulator *= factor;
return !!factor;
});
return accumulator;
}
console.log(product([2,2,2,0,2,2]));
// 0
You can break every code - and thus every build in iterator - by throwing an exception:
function breakReduceException(value) {
this.value = value
}
try {
Things.reduce(function(memo, current) {
...
if (current <= 0) throw new breakReduceException(memo)
...
}, 0)
} catch (e) {
if (e instanceof breakReduceException) var memo = e.value
else throw e
}
You can use try...catch to exit the loop.
try {
Things.reduce(function(memo, current){
if(current <= 0){
throw 'exit loop'
//break ???
//return; <-- this will return undefined to memo, which is not what I want
}
}, 0)
} catch {
// handle logic
}
As the promises have resolve and reject callback arguments, I created the reduce workaround function with the break callback argument. It takes all the same arguments as native reduce method, except the first one is an array to work on (avoid monkey patching). The third [2] initialValue argument is optional. See the snippet below for the function reducer.
var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];
var result = reducer(list,(total,current,index,arr,stop)=>{
if(current === " ") stop(); //when called, the loop breaks
return total + current;
},'hello ');
console.log(result); //hello world
function reducer(arr, callback, initial) {
var hasInitial = arguments.length >= 3;
var total = hasInitial ? initial : arr[0];
var breakNow = false;
for (var i = hasInitial ? 0 : 1; i < arr.length; i++) {
var currentValue = arr[i];
var currentIndex = i;
var newTotal = callback(total, currentValue, currentIndex, arr, () => breakNow = true);
if (breakNow) break;
total = newTotal;
}
return total;
}
And here is the reducer as an Array method modified script:
Array.prototype.reducer = function(callback,initial){
var hasInitial = arguments.length >= 2;
var total = hasInitial ? initial : this[0];
var breakNow = false;
for (var i = hasInitial ? 0 : 1; i < this.length; i++) {
var currentValue = this[i];
var currentIndex = i;
var newTotal = callback(total, currentValue, currentIndex, this, () => breakNow = true);
if (breakNow) break;
total = newTotal;
}
return total;
};
var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];
var result = list.reducer((total,current,index,arr,stop)=>{
if(current === " ") stop(); //when called, the loop breaks
return total + current;
},'hello ');
console.log(result);
Reduce functional version with break can be implemented as 'transform', ex. in underscore.
I tried to implement it with a config flag to stop it so that the implementation reduce doesn't have to change the data structure that you are currently using.
const transform = (arr, reduce, init, config = {}) => {
const result = arr.reduce((acc, item, i, arr) => {
if (acc.found) return acc
acc.value = reduce(config, acc.value, item, i, arr)
if (config.stop) {
acc.found = true
}
return acc
}, { value: init, found: false })
return result.value
}
module.exports = transform
Usage1, simple one
const a = [0, 1, 1, 3, 1]
console.log(transform(a, (config, acc, v) => {
if (v === 3) { config.stop = true }
if (v === 1) return ++acc
return acc
}, 0))
Usage2, use config as internal variable
const pixes = Array(size).fill(0)
const pixProcessed = pixes.map((_, pixId) => {
return transform(pics, (config, _, pic) => {
if (pic[pixId] !== '2') config.stop = true
return pic[pixId]
}, '0')
})
Usage3, capture config as external variable
const thrusts2 = permute([9, 8, 7, 6, 5]).map(signals => {
const datas = new Array(5).fill(_data())
const ps = new Array(5).fill(0)
let thrust = 0, config
do {
config = {}
thrust = transform(signals, (_config, acc, signal, i) => {
const res = intcode(
datas[i], signal,
{ once: true, i: ps[i], prev: acc }
)
if (res) {
[ps[i], acc] = res
} else {
_config.stop = true
}
return acc
}, thrust, config)
} while (!config.stop)
return thrust
}, 0)
You cannot break from inside of a reduce method. Depending on what you are trying to accomplish you could alter the final result (which is one reason you may want to do this)
const result = [1, 1, 1].reduce((a, b) => a + b, 0); // returns 3
console.log(result);
const result = [1, 1, 1].reduce((a, b, c, d) => {
if (c === 1 && b < 3) {
return a + b + 1;
}
return a + b;
}, 0); // now returns 4
console.log(result);
Keep in mind: you cannot reassign the array parameter directly
const result = [1, 1, 1].reduce( (a, b, c, d) => {
if (c === 0) {
d = [1, 1, 2];
}
return a + b;
}, 0); // still returns 3
console.log(result);
However (as pointed out below), you CAN affect the outcome by changing the array's contents:
const result = [1, 1, 1].reduce( (a, b, c, d) => {
if (c === 0) {
d[2] = 100;
}
return a + b;
}, 0); // now returns 102
console.log(result);
Providing you do not need to return an array, perhaps you could use some()?
Use some instead which auto-breaks when you want. Send it a this accumulator. Your test and accumulate function cannot be an arrow function as their this is set when the arrow function is created.
const array = ['a', 'b', 'c', 'd', 'e'];
var accum = {accum: ''};
function testerAndAccumulator(curr, i, arr){
this.tot += arr[i];
return curr==='c';
};
accum.tot = "";
array.some(testerAndAccumulator, accum);
var result = accum.tot;
In my opinion this is the better solution to the accepted answer provided you do not need to return an array (eg in a chain of array operators), as you do not alter the original array and you do not need to make a copy of it which could be bad for large arrays.
So, to terminate even earlier the idiom to use would be arr.splice(0).
Which prompts the question, why can't one just use arr = [] in this case?
I tried it and the reduce ignored the assignment, continuing on unchanged.
The reduce idiom appears to respond to forms such as splice but not forms such as the assignment operator??? - completely unintuitive - and has to be rote-learnt as precepts within the functional programming credo ...
const array = ['9', '91', '95', '96', '99'];
const x = array
.reduce((acc, curr, i, arr) => {
if(i === 2) arr.splice(1); // eject early
return acc += curr;
}, '');
console.log('x: ', x); // x: 99195
The problem is, that inside of the accumulator it is not possible to just stop the whole process. So by design something in the outer scope must be manipulated, which always leads to a necessary mutation.
As many others already mentioned throw with try...catch is not really an approach which can be called "solution". It is more a hack with many unwanted side effects.
The only way to do this WITHOUT ANY MUTATIONS is by using a second compare function, which decides whether to continue or stop. To still avoid a for-loop, it has to be solved with a recursion.
The code:
function reduceCompare(arr, cb, cmp, init) {
return (function _(acc, i) {
return i < arr.length && cmp(acc, arr[i], i, arr) === true ? _(cb(acc, arr[i], i, arr), i + 1) : acc;
})(typeof init !== 'undefined' ? init : arr[0], 0);
}
This can be used like:
var arr = ['a', 'b', 'c', 'd'];
function join(acc, curr) {
return acc + curr;
}
console.log(
reduceCompare(
arr,
join,
function(acc) { return acc.length < 1; },
''
)
); // logs 'a'
console.log(
reduceCompare(
arr,
join,
function(acc, curr) { return curr !== 'c'; },
''
)
); // logs 'ab'
console.log(
reduceCompare(
arr,
join,
function(acc, curr, i) { return i < 3; },
''
)
); // logs 'abc'
I made an npm library out of this, also containing a TypeScript and ES6 version. Feel free to use it:
https://www.npmjs.com/package/array-reduce-compare
or on GitHub:
https://github.com/StefanJelner/array-reduce-compare
You could to write your own reduce method. Invoking it like this, so it follows same logic and you control your own escape / break solution. It retains functional style and allows breaking.
const reduce = (arr, fn, accum) => {
const len = arr.length;
let result = null;
for(let i = 0; i < len; i=i+1) {
result = fn(accum, arr[i], i)
if (accum.break === true) {
break;
}
}
return result
}
const arr = ['a', 'b', 'c', 'shouldnotgethere']
const myResult = reduce(arr, (accum, cur, ind) => {
accum.result = accum.result + cur;
if(ind === 2) {
accum.break = true
}
return accum
}, {result:'', break: false}).result
console.log({myResult})
Or create your own reduce recursion method:
const rcReduce = (arr, accum = '', ind = 0) => {
const cur = arr.shift();
accum += cur;
const isBreak = ind > 1
return arr.length && !isBreak ? rcReduce(arr, accum, ind + 1) : accum
}
const myResult = rcReduce(['a', 'b', 'c', 'shouldngethere'])
console.log({myResult})
Another simple implementation that I came with solving the same issue:
function reduce(array, reducer, first) {
let result = first || array.shift()
while (array.length > 0) {
result = reducer(result, array.shift())
if (result && result.reduced) {
return result.reduced
}
}
return result
}
If you want to chain promises sequentially with reduce using the pattern below:
return [1,2,3,4].reduce(function(promise,n,i,arr){
return promise.then(function(){
// this code is executed when the reduce loop is terminated,
// so truncating arr here or in the call below does not works
return somethingReturningAPromise(n);
});
}, Promise.resolve());
But need to break according to something happening inside or outside a promise
things become a little bit more complicated because the reduce loop is terminated before the first promise is executed, making truncating the array in the promise callbacks useless, I ended up with this implementation:
function reduce(array, promise, fn, i) {
i=i||0;
return promise
.then(function(){
return fn(promise,array[i]);
})
.then(function(result){
if (!promise.break && ++i<array.length) {
return reduce(array,promise,fn,i);
} else {
return result;
}
})
}
Then you can do something like this:
var promise=Promise.resolve();
reduce([1,2,3,4],promise,function(promise,val){
return iter(promise, val);
}).catch(console.error);
function iter(promise, val) {
return new Promise(function(resolve, reject){
setTimeout(function(){
if (promise.break) return reject('break');
console.log(val);
if (val==3) {promise.break=true;}
resolve(val);
}, 4000-1000*val);
});
}
I solved it like follows, for example in the some method where short circuiting can save a lot:
const someShort = (list, fn) => {
let t;
try {
return list.reduce((acc, el) => {
t = fn(el);
console.log('found ?', el, t)
if (t) {
throw ''
}
return t
}, false)
} catch (e) {
return t
}
}
const someEven = someShort([1, 2, 3, 1, 5], el => el % 2 === 0)
console.log(someEven)
UPDATE
Away more generic answer could be something like the following
const escReduce = (arr, fn, init, exitFn) => {
try {
return arr.reduce((...args) => {
if (exitFn && exitFn(...args)) {
throw args[0]
}
return fn(...args)
}, init)
} catch(e){ return e }
}
escReduce(
Array.from({length: 100}, (_, i) => i+1),
(acc, e, i) => acc * e,
1,
acc => acc > 1E9
); // 6227020800
give we pass an optional exitFn which decides to break or not

Categories