Reputation: 8338
I'm trying to take an array and create a nested object from it, where each item in the array, is a property of the previous item.
I think reduce
is the way to do this, but I find reduce
hard to grasp, and everything I try I get stuck knowing how to push into the next level.
JS: Reduce array to nested objects is a similar question, but I still can't work it having tried many variations of that.
const myArray = ['one', 'two', 'three'];
// Intended Output (note, the staticCount is always 1)
{
one: {
staticCount: 1,
two: {
staticCount: 1,
three: {
staticCount: 1
}
}
}
}
Upvotes: 1
Views: 1324
Reputation: 2766
Some jobs call for Array.prototype.reduceRight
:
const myArray = ['one', 'two', 'three']
const nestNode = (acc, key) => {
acc.staticCount = 1
return { [key]: acc }
}
console.log(myArray.reduceRight(nestNode, {}))
Let's take a look at reduceRight
(and, by extension, reduce
):
I moved the iterator function definition out of the call to reduceRight
to make the example easier to talk about (see nestNode
).
reduce
and reduceRight
are similar:
Each takes two arguments, an iterator function and an initial value for that function's accumulator. The second argument is optional, but I will ignore that here.
Each iterates over all of the items in the array on which they're called, calling the iterator function for each item in the array with four arguments, the accumulator, the current item in the array, the current iteration count and the whole array on which you called reduce
. The last two arguments are not relevant here (and I rarely use them).
The first time the iterator function is called, it will be passed the second argument you provided to reduce
or reduceRight
(the initial accumulator value). Afterwards, it will be passed whatever was returned by the iterator function in the previous step.
Because I think reduce
(and by extension reduceRight
) are powerful abstractions that are worth understanding, I'll step through the first two steps in the code example:
On the first step in the iteration, our iterator function is called like this: nestNode(acc = {}, key = 'three')
. Inside nestNode
, we add a staticCount
property to acc
and set it to 1
, giving us acc = { staticCount: 1 }
. Then we create and return a new object with a property named 'three'
that has a value equal to acc
. The value returned by nestNode
in the first step is { three: { staticCount: 1 } }
, and nestNode
will be called with this value in the second step.
On the second step in the iteration, our iterator function is called like this: nestNode(acc = { three: { staticCount: 1 } }, key = 'two')
. Again, we add a staticCount
property to acc
and set it to 1
, giving us acc = { three: { staticCount: 1 }, staticCount: 1 }
. We then create and return a new object with a property named 'two'
that has a value equal to acc
. The value we return is { two: { three: { staticCount: 1 }, staticCount: 1 } }
. Again, this value will be used in the next step.
I'll skip the last step, since I hope that taking a look at the first two steps, in detail, is enough to clear things up a little. If you have other questions or still find something unclear or confusing, please let me know.
reduce
(and reduceRight
) are powerful, flexible tools that are worth learning and becoming comfortable with.
As a coda, I'll leave you with the return value of the iterator function after each step:
{ three: { staticCount: 1 } }
{ two: { three: { staticCount: 1 } }, staticCount: 1 }
{ one: { two: { three: { staticCount: 1 } }, staticCount: 1 }, staticCount: 1 }
Upvotes: 5
Reputation: 3604
reduce
, like map
, will loop over each item in an array and return a result. The key difference is that map
will return an array of equal size to the original with any modifications you made. reduce
takes what’s called an accumulator
and returns that as the final result.
reduce()
takes two parameters:
function()
accumulator
The function()
you provide is given three values:
accumulator
The most important thing to understand about the accumulator
is that it will become the value of whatever your function()
returns, and your function ALWAYS has to return something, otherwise accumulator
will be undefined on the next loop.
Following the solution to your problem is a basic example using reduce
.
const myArray = ['one','two','three'];
const result = {};
myArray.reduce((accumulator, num) => {
accumulator[num] = { staticCount: 1}
return accumulator[num];
}, result);
console.log(result);
The reduce
solution provided here can perform 7.6 million operations per second, vs. reduceRight
at 2.2 million operations per second.
https://jsperf.com/reduceright-vs-reduce/
var numbers = [1, 2, 3, 4, 5];
// Reduce will assign sum whatever
// the value of result is on the last loop
var sum = numbers.reduce((result, number) => {
return result + number;
}, 0); // start result at 0
console.log(sum);
var numbers = [1, 2, 3, 4, 5];
// Here we're using the iterator, and
// assinging "too much" to sum if there
// are more than 4 numbers.
var sum = numbers.reduce((result, number, i) => {
if (i >= 4) return "too much";
return result + number;
}, 0);
console.log(sum);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
Upvotes: 2
Reputation: 18921
This can also be achieved with a recursive function:
const createObj = (keys) => keys.length > 0 && ({
[keys[0]]: {
staticCount: 1,
...createObj(keys.slice(1))
}
});
console.log(createObj(['one', 'two', 'three']));
Upvotes: 1
Reputation: 4215
With thanks to Tex for making me replace reverse().reduce()
with reduceRight
:
['one', 'two', 'three'].reduceRight((a, c) => ({[c]: { staticCount: 1, ...a }}), {});
Upvotes: 2
Reputation: 12806
The trick would be not to start from an empty object, but from some declared variable. Then just pass the newly child down as the aggregate for the next recursion.
The reference gets updated, and you can then print the root again.
const myArray = ['one', 'two', 'three'];
const root = {};
myArray.reduce( (agg, c) => {
agg[c] = { staticCount: 1 };
return agg[c];
}, root );
console.log( root );
Upvotes: 1
Reputation: 1436
Just use reduce. It works because objects are passed by reference.
const myArray = ['one', 'two', 'three'];
const newObject = {};
myArray.reduce((acummulator, element) => {
acummulator[element] = {
staticCount: 1
};
return acummulator[element];
}, newObject);
// Intended Output (note, the staticCount is always 1)
console.log(newObject);
Read about reduce here.
Upvotes: 1