Reputation: 2252
Is there a way to create a map (mapping string to array) from an array in the example below using map function? Not sure how to append an object to a key that already exists instead of overwriting what's inside already
WANT
{"Germany": [{score: 1}], "Austria": [{score: 1}], "Switzerland": [{score: 2},{score: 3}]}
GETTING
{"Germany": [{score: 1}], "Austria": [{score: 1}], "Switzerland": [{score: 3}]}
let data = [
{country: 'Germany', score: 1},
{country: 'Austria', score: 1},
{country: 'Switzerland', score: 2},
{country: 'Switzerland', score: 3}
];
let dictionary = Object.assign({}, ...data.map((x) => ({[x.country]: [{score: x.score}]})));
Upvotes: 0
Views: 689
Reputation: 12089
I like the brevity and elegance of the accepted answer. However, the brevity, and new technology, obfuscates the process. After researching and inquiring, here's what I understand is happening:
result = data.reduce(
reduce
loops through the subject array and executes a function on each element, passing the result of the last iteration to the current iteration, or the initial value on first iteration.
The first parameter of the reduce
function is a user-defined function. The function can be defined elsewhere and named here, or an anonymous function can be defined in place. Arrow function syntax is used in this case to express an anonymous function.
First, instantiate the parameter variables to capture input and be used in the function body. Here, two parameter variables are instantiated:
(
a, // accumulator, returned value from the previous iteration
{country, score} // value of currently iterated element of subject array, destructured
) => ({
The first parameter is the accumulator object, represented as the variable a
. Then, in this case, the second is an object, {country, score}
, and it simultaneously provides the current element of the reduce
-iterated array, and is destructured into two variables, country
, and score
, corresponding to object keys.
Notes:
The function body entirely consists of an object: => ({...})
. All the operations of this function are embedded in the object expression.
If the arrow function consists of an expression that results in a single value, in this case an object, there is no need to express return
, such as => { return {...} }
.
However, since the expression is an object, parenthesis are wrapped around the object, => ({...})
, in order to implicitly return the object, and prevent the curly braces from being interpreted as function body delimiters.
There are two implementations of the spread
syntax within this object expression, both are used, more or less, to concatenate two objects or arrays into a single object or array, respectively.
Continuing with the function body, which is, as mentioned, simply an object——first, "spread" the accumulated object into a list of its key/value pairs, for re-incorporation into the accumulated object:
) => ({
...a,
Spread, here, effectively concatenates the components of the accumulating object with a newly derived object component resulting in a single, flat, object.
Next, derive a new key/value pair from the current element and append it to the accumulated object:
[country]: // destructure, or interpolate, the variable, as key
// array that accumulates the values of the above defined key
[
...( // "spread" and assign the existing value of key (an array containing one or more objects)
a[country] || [] // get current value of key, if defined, but return an undefined value if not
),
{score} // destructure variable and append object to array that is the value of the above key
]
}),
The new object key is dynamic. In other words the name of the key is not known until runtime. The value of the variable country
is used to name the key, but since bare-words, i.e., unquoted strings, are allowed when setting a key, using country:
as the key would not set the key to the value of the variable. In order to coerce the variable into it's value it is wrapped in a literal array: [country]:
. What this returns is an array with one element which is the value of the variable. And only strings and symbols are permissible as object keys so the Array.prototype.toString
method is automatically called to convert the array to a string.
The value part of the key/value pair is an array. It collects a list of objects. Spread is also used here to concatenate, and flatten, the previously collected score
objects and the score
of the current iteration, corresponding to the key. The OR operator, ||
is used to first determine the existence of an existing key in the accumulator object, and if it exists flatten and incorporate the existing scores. But if the key does not exist alternatively provide an empty list, effectively starting a new list.
}),
...and implicitly return
the accumulation object for the next iteration, or assignment to result
upon completion.
The reduce
first parameter, a function, is now fully included, so on to the reduce
function second parameter, which is used for the first iteration:
{} // an empty object
);
Upvotes: 0
Reputation: 8670
You could use the reduce
function, which takes an array and return a value based on some operation done on every element of the array.
In your case, this operation could be to add the score to an already existing key, in another object.
The reduce
function take a callback with two parameter, a collector and a current item.
let data = [
{country: 'Germany', score: 1},
{country: 'Austria', score: 1},
{country: 'Switzerland', score: 2},
{country: 'Switzerland', score: 3}
];
// since the reduce function returns a new object, we don't need to deep copy it.
let dictionary = data.reduce((collector, x) => {
// we first have to check if the key exists in the collector object.
// to do so, we use the `in` operator, and negate it's output.
if(!(x.country in collector)) {
collector[x.country] = [];
}
// we, then, can push the current element's score into the appropriate array
collector[x.country].push({score: x.score});
// and we need to return the updated value. In our case, it's the collector.
return collector;
}, {} /** the second parameter of the function is the initial value of the collector */);
console.log(dictionary)
Upvotes: 1
Reputation: 13255
You can use Array.prototype.reduce() to achive it.
Try like below:
let data = [
{ country: "Germany", score: 1 },
{ country: "Austria", score: 1 },
{ country: "Switzerland", score: 2 },
{ country: "Switzerland", score: 3 },
];
const output = data.reduce((prev, { country, score }) => {
if (prev[country]) {
prev[country].push({ score });
} else {
prev[country] = [{ score }];
}
return prev;
}, {});
console.log(output);
Upvotes: 1
Reputation: 97322
Fairly straightforward to do with reduce()
, some destructuring and spread syntax:
const data = [
{country: 'Germany', score: 1},
{country: 'Austria', score: 1},
{country: 'Switzerland', score: 2},
{country: 'Switzerland', score: 3}
];
const result = data.reduce((a, {country, score}) => ({
...a,
[country]: [...a[country] || [], {score}]
}), {});
console.log(result);
Upvotes: 2