I have found this interesting code example which flattens an array using the recursion and reduce function instead of flat. I get what it does except the following:
acc = acc.concat(flatWithRec(item)); why accumulator is being reassign? how is it possible in reduce function?
and why use concat here?
return acc; why acc is being returned? is it a new Flat Array each time function is called?
is there a way to, still, use recursion and make it easier for the reader to understand?
Please clarify
function flatWithRec(arr) {
const flatArray = arr.reduce((acc, item) => {
if (Array.isArray(item)) {
acc = acc.concat(flatWithRec(item));
} else {
acc.push(item);
}
return acc;
}, []);
return flatArray;
}
console.log(flatWithRec([1, [2, 3, [4],
[5, 6, [7]]
]]))
// output: [1, 2, 3, 4, 5, 6, 7])
The accumulator is an array. You reassign it to give it the new array containing the items of the one you have at the beginning of the loop and the items of the array items to add. As said in the comments, acc.concat returns a new array containing the items of the arrays passed in parameter. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
You need to return the accumulator at the end of each loop for the new value to be taken in account at the next loop.
Javascript recursive array flattening
acc = acc.concat(flatWithRec(item)); why accumulator is being reassign? how is it possible in reduce function? and why use concat here?
The accumulator (acc) is a function argument, and can be re-assigned, although it's not a good practice to do so. Concat combines two items (array or otherwise), and returns a new array, so you need to assign it to the acc as the result of the current loop.
return acc; why acc is being returned? is it a new Flat Array each time function is called?
The accumulator holds the current state of the reduced items, in your case the current flat array after each loop. You need to return it, so the next loop can continue to accumulate.
is there a way to, still, use recursion and make it easier for the reader to understand?
My take on flatWithRec - always concat, but if it's an array call flatWithRec on it before concatenating:
function flatWithRec(arr) {
return arr.reduce((acc, item) =>
acc.concat(
Array.isArray(item)
? flatWithRec(item)
: item
), []);
}
const result = flatWithRec([1, [2, 3, [4], [5, 6, [7]]]])
console.log(result) // output: [1, 2, 3, 4, 5, 6, 7])
So, the callback for the reduce method runs for every item in the array and whatever is returned from iteration x is passed as the first argument to iteration x+1. So, it is essential to make sure that during every iteration the correct state is returned.
acc = acc.concat(flatWithRec(item)) Why accumulator is being reassigned? How is it possible in reduce function? And why use concat here?
So, we are assigning acc the return value of concat because concat does not change the original array, it returns a fresh array. Accumulator is just like any other parameter so you can reassign. Not at all necessary to use concat (see my solution at the end using push and spread).
return acc; Why acc is being returned? Is it a new flat array each time the function is called?
As, already mentioned you need to return the correct state from the callback. And yes, during each iteration a new array is being created (which is not that performant, see my solution at the end)
Is there a way to still use recursion and make it easier for the reader to understand?
Easier for reader I can't tell, but here's my solution using => functions and spread.
const flatWithRec = (arr) =>
arr.reduce((acc, item) => (
Array.isArray(item) ? acc.push(...flatWithRec(item)) : acc.push(item), acc
), []);
console.log(flatWithRec([1, [2, 3, [4], [5, 6, [7]]]]));
Yes, there is a way to use recursion and make it easier to understand:
function f(A, i=0){
return i == A.length ? [] : (Array.isArray(A[i]) ? f(A[i]) : [A[i]]).concat(f(A, i+1));
}
var A = [1, [2, 3, [4], [5, 6, [7]]]];
console.log(JSON.stringify(A));
console.log(JSON.stringify(f(A)));
Related
const array = [7, 2, 4, 1, 10, 6, 5, 11]
const max = array.reduce((acc, val) => {
console.log(val, acc)
return val > acc ? val : acc
}, 0)
console.log(max)
I was looking at this code of reduce array method, one thing I couldn't understand at all is, How the reducer function is going to the next iteration? There is no condition that forces the reducer function to go to the next element in the array. In the first iteration, the val is 7, the first element of the array, and acc is 0, the reducer function returns 7 as per the condition written.
My question is how the number 7 as being the new accumulator is going to be called on the reducer function. I thought the normal procedure is you have to meet some kind of condition to iterate over again and again. Is there something written in the reduce method itself? Can you explain me please?
Note that array.reduce:
reduce
calls the callback, as a function, once for each element after the
first element present in the array, in ascending order.
You could understand the reduce as a array.map but the goal of it is to change the array to a singe output.
It will loop over the whole array same with the forEach/map/...
Check below example, even though you don't do anything, like return or anything else to array.reduce, it will still work and iterate the array
You could check here for more
But of course if you don't use return for array.reduce, there will be no benefit for you to use array.reduce
const array = [7, 2, 4, 1, 10, 6, 5, 11]
const max = array.reduce((acc, val) => {
console.log(val)
}, 0)
console.log(max)
As per the docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
The reduce() method executes a user-supplied “reducer” callback function on each element of the array,
The reduce method's implementation resembles something like this:
Array.prototype.reduce = function(callback, initialValue) {
var acc = initialValue;
for(var i = 0; i < this.length; i++) {
acc = callback(acc, this[i], i);
}
return acc;
}
The reduce methods iteration condition is the array length.
The same goes for map.
In the following example, we have access to the array as numbers and arr. It seems more in line with functional programming to use the internal variable arr but what is an explicit reason why we should use it instead of the exterior variable, since, numbers and arr are both pointers to the same array value anyway.
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, m, index, arr) => {
console.log(`acc=${acc}, m=${m}, index=${index}, arr=${arr}`);
console.log(`acc=${acc}, m=${m}, index=${index}, numbers=${numbers}`);
return acc += m;
}, 100);
console.log(sum);
Because not every array will be stored in a variable. You can chain calls to map() & other, or after a call to a function that returns an array, in those cases you can access the array by variable name.
functionThatReturnsAnArray(...).map((acc, m, index, arr) => {
// We can only access the array because
//it was passed as an argument to the anonymous function
})
The MDN defines reduce() as below:
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
Let's not take anything for granted and just analyse the syntax of below statements:
const array1 = [1, 2, 3, 4];
const reducer = (y, x) => y + x;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
reducer is a function that takes two parameters and return their sum
reducer is executed on each element of the array, but 'each element of the array' is only ONE parameter, why can i assume that the SUM is the other parameter and cached somewhere waiting for the next add operation? and why can i assume reduce is returning the sum at the end?
Could someone answer these questions? From someone coming from other language background e.g. C which also has function concept. I am often confused by Javascript's syntax.
and that's how i get more confused when i see:
const pipeline = [
array => { array.pop(); return array; },
array => array.reverse()
];
pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);
because again, according to MDN, The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
only this time,
reducer: (xs, f) => f(xs)
accumulator: [1, 2, 3]
array1: pipeline
then how do we explain its behaviour similar to our first example in English?
reducer is executed on each element of the array, but 'each element of the array' is only ONE parameter, why can i assume that the SUM is the other parameter and cached somewhere waiting for the next add operation?
The callback provided is called multiple times, once for each item in the array (or, for length - 1 times, in case no initial value is provided). You could easily implement this yourself:
const array1 = [1, 2, 3, 4];
const reducer = (y, x) => y + x;
Array.prototype.myReduce = function(callback, initialValue) {
let accum = initialValue === undefined
? this.shift()
: initialValue;
for (let i = 0; i < this.length; i++) {
accum = callback(accum, this[i], i, this);
}
return accum;
}
// 1 + 2 + 3 + 4
console.log(array1.myReduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.myReduce(reducer, 5));
Just because you pass one function to .reduce (or to any other function) doesn't put a limit on how many times that function can be called.
and why can i assume reduce is returning the sum at the end?
That's just how the method is defined - like with the implementation above, the accumulator (or accum) gets reassigned for every iteration, and passed to the next invocation of the callback.
The browser's native implementation of the method isn't actually written in Javascript like above, but its functionality is the same (for the most part).
The pipeline works the same way. For every element of the array provided, the accumulator is reassigned, and the next element is called with the new accumulator. Here, there's an array of functions which are being called, and each function's return value is being used as the next accumulator, and the value returned by the last function call is what the whole .reduce call resolves to.
const pipeline = [
array => { array.pop(); return array; },
array => array.reverse()
];
pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);
Item 1: initial value (accumulator) is [1, 2, 3]. Plugging into array => { array.pop(); return array; } and you .pop() its last value (the 3, resulting in [1, 2], then you return the array.
Item 2: Accumulator (return value of last iteration) is [1, 2]. Plug it into array => array.reverse(), and you get the same array, reversed: [2, 1].
There are no more items in the array, so this [2, 1] is the value that the whole reduce call evaluates to.
I'm reading a book called Eloquent JavaScript. There's an exercise in it that requires one to flatten a heterogeneous array & after trying so long and failing to get the answer, I looked up the solution online & couldn't understand the code. I'm hoping someone will be kind enough to explain, especially for argument "flat" and how it's supposed to work. The code is below
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));
The reduce function defined in the book is:
function reduce(array, combine, start) {
var current = start;
for (var i = 0; i < array.length; i++)
current = combine(current, array[i]);
return current;
}
and as a method of an array,
arr.reduce(combine, start);
Let's look at each part of the reduce method. The book describes it as "folding up the array, one element at a time." The first argument for reduce is the "combiner function", that accepts two arguments, the "current" value and the "next" item in the array.
Now, the initial "current" value is given as the second argument of the reduce function, and in the solution of flattening arrays, it is the empty array, []. Note that in the beginning, the "next" item in the array is the 0th item.
Quoting the book to observe: "If your array contains at least one element, you are allowed to leave off the start argument."
It may also be confusing that in the flattening solution, current is placed as the second argument to reduce, whereas in the reduce definition above, current is used to assign the cumulative, folded value. In the flattening solution, current refers to the "next" arrays item (the individual array of integers)
Now, at each step of the reduction, the "current" value plus the next array item is fed to the (anonymous) combiner, and the return value becomes the updated "current" value. That is, we consumed an element of the array and continue with the next item.
flat is merely the name given to the accumulated result. Because we wish to return a flat array, it is an appropriate name. Because an array has the concat function, the first step of the reduce function is, (pretending that I can assign the internal variables)
flat = []; // (assignment by being the second argument to reduce)
Now, walk through the reduction as iterating over arrays, by going through the steps shown above in reduce's definition
for (var i = 0; i < arrays.length; i++)
flat = combine(flat, arrays[i]);
Calling combine gives [].concat([1, 2, 3]) // => [1, 2, 3]
Then,
flat = [1, 2, 3].concat([4, 5]) // => [1, 2, 3, 4, 5]
and again for the next iteration of the reduction. The final return value of the reduce function is then the final value of flat.
This would be the solution I came with with ES6 format:
const reduced = arrays.reduce((result,array) => result.concat(array),[]);
console.log(reduced);
I have implemented this solution and this seems to work for nested arrays as well.
function flattenArray(arr){
for(var i=0;i<arr.length;i++){
if(arr[i] instanceof Array){
Array.prototype.splice.apply(arr,[i,1].concat(arr[i]))
}
}
return arr;
}
There is an easy way to do these exercises. those functions are already built inside the javascript so you can use them easily.
But the whole joy of this exercise is to create those functions:
Create reduce function. Reduce function should add all array elements. you can use a higher-order function or just a normal one. here is an example for higher-order:
function reduce(array, calculate){
let sumOfElements = 0;
for(let element of array){
sumOfElements = calculate(sumOfElements, element)
}
return sumOfElements
}
Next step is to create a concat function. since we need to return those reduced arrays in new array we will just return them. (Warning: you must use rest parameter)
function concat(...arr){
return arr
}
And for last. you will just display it (You can use any example)
console.log(concat(reduce([1, 2, 3, 4], (a, b) => a + b), reduce([5, 6], (a, b) => a + b)))
The reduce method acts as a for loop iterating over each element in an array. The solution takes each array element and concatenates it to the next one. That should flatten the array.
var arr =[[1,2],[3,4],[5,6]]
function flatten(arr){
const flat= arr.reduce((accumulator,currentValue)=>{
return accumulator.concat(currentValue)
})
return flat
}
console.log(flatten(arr))
//Output 1,2,3,4,5,6
Other than stating "transform is a more powerful alternative to reduce", I can find no documentation of what the differences are. What are the differences between transform and reduce in lodash (Other than it being 25% slower)?
I like to dive into the source code before I pull in utilities. For lo-dash this can be difficult as there is a ton of abstracted internal functionality in all the utilities.
transform source
reduce source
So the obvious differences are:
If you dont specify the accumulator (commonly referred to as memo
if you're used to underscore), _.transform will guess if you want
an array or object while reduce will make the accumulator the initial item of the collection.
Example, array or object map via transform:
_.transform([1, 2, 3], function(memo, val, idx) {
memo[idx] = val + 5;
});
// => [6, 7, 8]
Versus reduce (note, you have to know the input type!)
_.reduce([1, 2, 3], function(memo, val, idx) {
memo[idx] = val + 5;
return memo;
}, []);
So while with reduce the below will compute the sum of an array, this wouldn't be possible with transform as a will be an array.
var sum = _.reduce([1, 2, 3, 4], function(a, b) {
return a + b;
});
Another big difference is you don't have to return the accumulator with transform and the accumulator can't change value (ie it will always be the same array). I'd say this is the biggest advantage of the function as I've often forgotten to return the accumulator using reduce.
For example if you wanted to convert an array to dictionary of values with reduce you would write code like the following:
_.reduce([1, 2, 3, 4, 5], function(memo, idx) {
memo[idx] = true;
return memo;
}, {});
Whereas with transform we can write this very nicely without needing to return the accumulator like in reduce
_.transform([1, 2, 3, 4, 5], function(memo, idx) {
memo[idx] = true;
}, {});
Another distinction is we can exit the transform iteration by returning false.
Overall, I'd say reduce is a more flexible method, as you have more control over the accumulator, but transform can be used to write equivalent code for some cases in a simpler style.
transform works on objects, reduce does not not