3

I am trying to nest a recursive function in an Array.reduce call.

But the following does not work for the third instance only

const i1 = {'local': [['a','b']]};
const i2 = {'local': [['c','d']], 'recursive': []};
const i3 = {'local': [['c','d']], 'recursive': [{'local': [['e','f']]}]};
function reduce(current, result = []) {
 if(current.hasOwnProperty('local'))
 result.push(...current.local);
 if(current.hasOwnProperty('recursive'))
 result = current.recursive.reduce(reduce, result);
 //'result =' should be optional, but yields a wrong answer anyway
 return result; 
}
console.log(reduce(i1));
console.log(reduce(i2));
console.log(reduce(i3));

So instead of calling reduce I tried the following loop

for(var i = 0; i < current.recursive.length; ++i)
 result = reduce(current.recursive[i], result);
 //'result = ' is optional for it is passed by reference

and it works. Being new to JavaScript, I am certain of missing a key feature here, so could you explain ?

Output for the third instance should be

[ [ 'c', 'd' ], [ 'e', 'f' ] ]

but is

{ local: [ [ 'e', 'f' ] ] }

or

[ [ 'c', 'd' ] ]

when result = is removed.

asked Aug 15, 2019 at 17:21
4
  • 3
    why do you use Array#reduce? why do you need a recursion? the data base is not the same and the signature of Array#reduce needs the second parameter for the element. Commented Aug 15, 2019 at 17:24
  • 2
    You've made your code extra confusing by combining manual recursion with Array#reduce, mixing assignments and functional style, and naming your own function reduce. But I think the main issue is that your order of arguments is wrong. It should be function reduce(result, current). Commented Aug 15, 2019 at 17:28
  • The callback to array reduce takes the accumulator first and the element second. Commented Aug 15, 2019 at 17:32
  • Oh yeah, I should have checked the doc again. Thanks! Commented Aug 15, 2019 at 17:34

3 Answers 3

2

The problem is the order of the parameters, the first argument to the reduce callback function is the accumulator not the current value. Try this:

const i1 = {'local': [['a','b']]};
const i2 = {'local': [['c','d']], 'recursive': []};
const i3 = {'local': [['c','d']], 'recursive': [{'local': [['e','f']]}]};
function reduce(result, current) {
 if(current.hasOwnProperty('local'))
 result.push(...current.local);
 if(current.hasOwnProperty('recursive'))
 result = current.recursive.reduce(reduce, result);
 //'result =' should be optional, but yields a wrong answer anyway
 return result; 
}
console.log(reduce([], i1));
console.log(reduce([], i2));
console.log(reduce([], i3));

answered Aug 15, 2019 at 17:31
Sign up to request clarification or add additional context in comments.

2 Comments

You could also make the first argument optional, allowing you to simply call reduce(i1). Add if (current === undefined) { current = result; result = []; } to the start of the reduce function.
@3limin4t0r I'm sure the OP will change this to fit his needs, I just wanted show what the problem is.
1

I think what you want to implement is flatten - It's no coincidence that it's a simple wrapper for Array.prototype.flatMap -

const i1 =
 { local: [[1,2]] }
const i2 = 
 { local: [[3,4]], recursive: [] }
const i3 =
 { local: [[3,4]], recursive: [{ local: [[5,6]] }] }
const i4 =
 { local: [[1,2]],
 recursive: [
 { local: [[3,4]] },
 { local: [[5,6]] , recursive: [{ local: [[7,8]] }] }] }
const flatten = ({ local = [], recursive = [] }) =>
 [ ...local, ...recursive.flatMap(flatten) ]
console.log(flatten(i1))
// [ [ 1, 2 ] ]
console.log(flatten(i2))
// [ [ 3, 4 ] ]
console.log(flatten(i3))
// [ [ 3, 4 ], [ 5, 6 ] ]
console.log(flatten(i4))
// [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ] ]

The spread arguments can be traded for Array.prototype.concat, if that is preferred -

const flatten = ({ local = [], recursive = [] }) =>
 (削除) [ ...local, ...recursive.flatMap(flatten) ] (削除ここまで)
 local.concat(recursive.flatMap(flatten))

Array.prototype.flatMap is a special kind of Array.prototype.reduce -

const flatten = ({ local = [], recursive = [] }) =>
 local.concat(
 recursive.reduce((r, x) => r.concat(flatten(x)), [])
 )

And since Array.prototype.concat is a pure operation, we can simplify it a bit more -

const flatten = ({ local = [], recursive = [] }) =>
 recursive.reduce((r, x) => r.concat(flatten(x)), local)

And finally we see it again using reduce and array spread arguments -

const flatten = ({ local = [], recursive = [] }) =>
 recursive.reduce((r, x) => [...r, ...flatten(x)], local)

Each of these flatten produce the exact same outputs and the input arrays are not mutated in the process. Hopefully this gives you a little insight on how the helpful Array.prototype.flatMap works.

answered Aug 15, 2019 at 20:27

Comments

0

This only covers the cases when local and recursive only includes one element, like in your example.

const i1 = { local: [["a", "b"]] };
const i2 = { local: [["c", "d"]], recursive: [] };
const i3 = { local: [["c", "d"]], recursive: [{ local: [["e", "f"]] }] };
const i4 = {
 local: [["c", "d"]],
 recursive: [{ local: [["e", "f"]], recursive: [{ local: [["g", "h"]] }] }]
};
const solution = ({ local: [firstItem], recursive }) =>
 [firstItem].concat(
 recursive && recursive.length ? solution(recursive[0]) : []
 );
console.log(solution(i1));
console.log(solution(i2));
console.log(solution(i3));
console.log(solution(i4));

answered Aug 15, 2019 at 17:45

1 Comment

local and recursive are arrays so it makes sense to me to append all children, not just the first, firstItem and recursive[0]

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.