I've been trying to calculate median but still I've got some mathematical issues I guess as I couldn't get the correct median value and couldn't figure out why. Here's the code;
class StatsCollector {
constructor() {
this.inputNumber = 0;
this.average = 0;
this.timeout = 19000;
this.frequencies = new Map();
for (let i of Array(this.timeout).keys()) {
this.frequencies.set(i, 0);
}
}
pushValue(responseTimeMs) {
let req = responseTimeMs;
if (req > this.timeout) {
req = this.timeout;
}
this.average = (this.average * this.inputNumber + req) / (this.inputNumber + 1);
console.log(responseTimeMs / 1000)
let groupIndex = Math.floor(responseTimeMs / 1000);
this.frequencies.set(groupIndex, this.frequencies.get(groupIndex) + 1);
this.inputNumber += 1;
}
getMedian() {
let medianElement = 0;
if (this.inputNumber <= 0) {
return 0;
}
if (this.inputNumber == 1) {
return this.average
}
if (this.inputNumber == 2) {
return this.average
}
if (this.inputNumber > 2) {
medianElement = this.inputNumber / 2;
}
let minCumulativeFreq = 0;
let maxCumulativeFreq = 0;
let cumulativeFreq = 0;
let freqGroup = 0;
for (let i of Array(20).keys()) {
if (medianElement <= cumulativeFreq + this.frequencies.get(i)) {
minCumulativeFreq = cumulativeFreq;
maxCumulativeFreq = cumulativeFreq + this.frequencies.get(i);
freqGroup = i;
break;
}
cumulativeFreq += this.frequencies.get(i);
}
return (((medianElement - minCumulativeFreq) / (maxCumulativeFreq - minCumulativeFreq)) + (freqGroup)) * 1000;
}
getAverage() {
return this.average;
}
}
Here's the snapshot of the results when I enter the values of
342,654,987,1093,2234,6243,7087,20123
The correct result should be;
Median: 1663.5
Change your median method to this:
function median(values){
if(values.length ===0) throw new Error("No inputs");
values.sort(function(a,b){
return a-b;
});
var half = Math.floor(values.length / 2);
if (values.length % 2)
return values[half];
return (values[half - 1] + values[half]) / 2.0;
}
fiddle
Here's another solution:
function median(numbers) {
const sorted = Array.from(numbers).sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
console.log(median([4, 5, 7, 1, 33]));
The solutions above - sort then find middle - are fine, but slow on large data sets. Sorting the data first has a complexity of n x log(n).
There is a faster median algorithm, which consists in segregating the array in two according to a pivot, then looking for the median in the larger set. Here is some javascript code, but here is a more detailed explanation
// Trying some array
alert(quickselect_median([7,3,5])); // 2300,5,4,0,123,2,76,768,28]));
function quickselect_median(arr) {
const L = arr.length, halfL = L/2;
if (L % 2 == 1)
return quickselect(arr, halfL);
else
return 0.5 * (quickselect(arr, halfL - 1) + quickselect(arr, halfL));
}
function quickselect(arr, k) {
// Select the kth element in arr
// arr: List of numerics
// k: Index
// return: The kth element (in numerical order) of arr
if (arr.length == 1)
return arr[0];
else {
const pivot = arr[0];
const lows = arr.filter((e)=>(e<pivot));
const highs = arr.filter((e)=>(e>pivot));
const pivots = arr.filter((e)=>(e==pivot));
if (k < lows.length) // the pivot is too high
return quickselect(lows, k);
else if (k < lows.length + pivots.length)// We got lucky and guessed the median
return pivot;
else // the pivot is too low
return quickselect(highs, k - lows.length - pivots.length);
}
}
Astute readers will notice a few things:
I simply transliterated Russel Cohen's Python solution into JS,
so all kudos to him.
There are several small optimisations worth
doing, but there's parallelisation worth doing, and the code as is
is easier to change in either a quicker single-threaded, or quicker
multi-threaded, version.
This is the average linear time
algorithm, there is more efficient a deterministic linear time version, see Russel's
post for details, including performance data.
ADDITION 19 Sept. 2019:
One comment asks whether this is worth doing in javascript. I ran the code in JSPerf and it gives interesting results.
if the array has an odd number of elements (one figure to find), sorting is 20% slower that this "fast median" proposition.
if there is an even number of elements, the "fast" algorithm is 40% slower, because it filters through the data twice, to find elements number k and k+1 to average. It is possible to write a version of fast median that doesn't do this.
The test used rather small arrays (29 elements in the jsperf test). The effect appears to be more pronounced as arrays get larger. A more general point to make is: it shows these kinds of optimisations are worth doing in Javascript. An awful lot of computation is done in JS, including with large amounts of data (think of dashboards, spreadsheets, data visualisations), and in systems with limited resources (think of mobile and embedded computing).
var arr = {
max: function(array) {
return Math.max.apply(null, array);
},
min: function(array) {
return Math.min.apply(null, array);
},
range: function(array) {
return arr.max(array) - arr.min(array);
},
midrange: function(array) {
return arr.range(array) / 2;
},
sum: function(array) {
var num = 0;
for (var i = 0, l = array.length; i < l; i++) num += array[i];
return num;
},
mean: function(array) {
return arr.sum(array) / array.length;
},
median: function(array) {
array.sort(function(a, b) {
return a - b;
});
var mid = array.length / 2;
return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2;
},
modes: function(array) {
if (!array.length) return [];
var modeMap = {},
maxCount = 1,
modes = [array[0]];
array.forEach(function(val) {
if (!modeMap[val]) modeMap[val] = 1;
else modeMap[val]++;
if (modeMap[val] > maxCount) {
modes = [val];
maxCount = modeMap[val];
}
else if (modeMap[val] === maxCount) {
modes.push(val);
maxCount = modeMap[val];
}
});
return modes;
},
variance: function(array) {
var mean = arr.mean(array);
return arr.mean(array.map(function(num) {
return Math.pow(num - mean, 2);
}));
},
standardDeviation: function(array) {
return Math.sqrt(arr.variance(array));
},
meanAbsoluteDeviation: function(array) {
var mean = arr.mean(array);
return arr.mean(array.map(function(num) {
return Math.abs(num - mean);
}));
},
zScores: function(array) {
var mean = arr.mean(array);
var standardDeviation = arr.standardDeviation(array);
return array.map(function(num) {
return (num - mean) / standardDeviation;
});
}
};
2022 TypeScript Approach
const median = (arr: number[]): number | undefined => {
if (!arr.length) return undefined;
const s = [...arr].sort((a, b) => a - b);
const mid = Math.floor(s.length / 2);
return s.length % 2 === 0 ? ((s[mid - 1] + s[mid]) / 2) : s[mid];
};
Notes:
The type in the function signature (number[]) ensures only an array of numbers can be passed to the function. It could possibly be empty though.
if (!arr.length) return undefined; checks for the possible empty array, which would not have a median.
[...arr] creates a copy of the passed-in array to ensure we don't overwrite the original.
.sort((a, b) => a - b) sorts the array of numbers in ascending order.
Math.floor(s.length / 2) finds the index of the middle element if the array has odd length, or the element just to the right of the middle if the array has even length.
s.length % 2 === 0 determines whether the array has an even length.
(s[mid - 1] + s[mid]) / 2 averages the two middle items of the array if the array's length is even.
s[mid] is the middle item of an odd-length array.
TypeScript Answer 2020:
// Calculate Median
const calculateMedian = (array: Array<number>) => {
// Check If Data Exists
if (array.length >= 1) {
// Sort Array
array = array.sort((a: number, b: number) => {
return a - b;
});
// Array Length: Even
if (array.length % 2 === 0) {
// Average Of Two Middle Numbers
return (array[(array.length / 2) - 1] + array[array.length / 2]) / 2;
}
// Array Length: Odd
else {
// Middle Number
return array[(array.length - 1) / 2];
}
}
else {
// Error
console.error('Error: Empty Array (calculateMedian)');
}
};
const median = (arr) => {
return arr.slice().sort((a, b) => a - b)[Math.floor(arr.length / 2)];
};
Short and sweet.
Array.prototype.median = function () {
return this.slice().sort((a, b) => a - b)[Math.floor(this.length / 2)];
};
Usage
[4, 5, 7, 1, 33].median()
Works with strings as well
["a","a","b","b","c","d","e"].median()
For better performance in terms of time complexity, use MaxHeap - MinHeap to find the median of stream of array.
Simpler & more efficient
const median = dataSet => {
if (dataSet.length === 1) return dataSet[0]
const sorted = ([ ...dataSet ]).sort()
const ceil = Math.ceil(sorted.length / 2)
const floor = Math.floor(sorted.length / 2)
if (ceil === floor) return sorted[floor]
return ((sorted[ceil] + sorted[floor]) / 2)
}
Simple solution:
function calcMedian(array) {
const {
length
} = array;
if (length < 1)
return 0;
//sort array asc
array.sort((a, b) => a - b);
if (length % 2) {
//length of array is odd
return array[(length + 1) / 2 - 1];
} else {
//length of array is even
return 0.5 * [(array[length / 2 - 1] + array[length / 2])];
}
}
console.log(2, calcMedian([1, 2, 2, 5, 6]));
console.log(3.5, calcMedian([1, 2, 2, 5, 6, 7]));
console.log(9, calcMedian([13, 9, 8, 15, 7]));
console.log(3.5, calcMedian([1, 4, 6, 3]));
console.log(5, calcMedian([5, 1, 11, 2, 8]));
Simpler, more efficient, and easy to read
cloned the data to avoid alterations to the original data.
sort the list of values.
get the middle point.
get the median from the list.
return the median.
function getMedian(data) {
const values = [...data];
const v = values.sort( (a, b) => a - b);
const mid = Math.floor( v.length / 2);
const median = (v.length % 2 !== 0) ? v[mid] : (v[mid - 1] + v[mid]) / 2;
return median;
}
const medianArr = (x) => {
let sortedx = x.sort((a,b)=> a-b);
let halfIndex = Math.floor(sortedx.length/2);
return (sortedx.length%2) ? (sortedx[Math.floor(sortedx.length/2)]) : ((sortedx[halfIndex-1]+sortedx[halfIndex])/2)
}
console.log(medianArr([1,2,3,4,5]));
console.log(medianArr([1,2,3,4,5,6]));
function Median(arr){
let len = arr.length;
arr = arr.sort();
let result = 0;
let mid = Math.floor(len/2);
if(len % 2 !== 0){
result += arr[mid];
}
if(len % 2 === 0){
result += (arr[mid] + arr[mid+1])/2
}
return result;
}
console.log(`The median is ${Median([0,1,2,3,4,5,6])}`)
function median(arr) {
let n = arr.length;
let med = Math.floor(n/2);
if(n % 2 != 0){
return arr[med];
} else{
return (arr[med -1] + arr[med])/ 2.0
}
}
console.log(median[1,2,3,4,5,6]);
The arr.sort() method sorts the elements of an array in place and returns the array. By default, it sorts the elements alphabetically, so if the array contains numbers, they will not be sorted in numerical order.
On the other hand, the arr.sort((a, b) => a - b) method uses a callback function to specify how the array should be sorted. The callback function compares the two elements a and b and returns a negative number if a should be sorted before b, a positive number if b should be sorted before a, and zero if the elements are equal. In this case, the callback function subtracts b from a, which results in a sorting order that is numerical in ascending order.
So, if you want to sort an array of numbers in ascending order, you should use arr.sort((a, b) => a - b), whereas if you want to sort an array of strings alphabetically, you can use arr.sort():
function median(numbers) {
const sorted = Array.from(numbers).sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
function findMedian(arr) {
arr.sort((a, b) => a - b)
let i = Math.floor(arr.length / 2)
return arr[i]
}
let result = findMedian([0, 1, 2, 4, 6, 5, 3])
console.log(result)
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