I'm trying to transform my array so that its easily used while rendering views
I have this sample code for now
let arr = [
{
date: d.setDate(d.getDate() - 1),
name: "john",
country: "AU",
text: "Hey"
},
{
date: d.setDate(d.getDate() - 1),
name: "jake",
country: "US",
text: "Hey"
},
{
date:d.setDate(d.getDate() - 3),
name: "jeff",
country: "US",
text: "Hey"
},
{
date:d.setDate(d.getDate() - 5),
name: "jared",
country: "US",
text: "Hey"
},
{
date:d.setDate(d.getDate() - 2),
name: "jane",
country: "UK",
text: "Hey"
}
]
let hours = _.groupBy(arr, (result) => moment(result['date']).startOf('hour').format("LT"));
let day = _.groupBy(arr, (result) => moment(result['date']).startOf('day').format("MMM Do YY"));
console.log(day)
What i want is to group the data by hours which is grouped by days from a single "date" string (i can do that seperately via _.groupby function but i want it to output a combined array.
The desired output should be something like this :
{
'Apr 15th 19': [
{'4 pm':[
{
date: 1555318593445,
name: 'ahmed',
country: 'AU',
text: 'Hey'
}
]
]
}....
Use _.flow() to create a function that groups by the day, and then maps each days values, and groups them by the hour:
const { flow, groupBy, mapValues } = _
const fn = flow(
arr => groupBy(arr, v => moment(v.date).startOf('day').format("MMM Do YY")),
groups => mapValues(groups, g => _.groupBy(g, v =>
moment(v.date).startOf('hour').format("LT")
))
)
const arr = [{"date":1555318593445,"name":"john","country":"AU","text":"Hey"},{"date":155531859300,"name":"jake","country":"US","text":"Hey"},{"date":1555316593445,"name":"jeff","country":"US","text":"Hey"},{"date":1555316593345,"name":"jared","country":"US","text":"Hey"},{"date":155531659400,"name":"jane","country":"UK","text":"Hey"}]
const result = fn(arr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
And the terser lodash/fp version:
const { flow, groupBy, mapValues } = _
const fn = flow(
groupBy(v => moment(v.date).startOf('day').format("MMM Do YY")),
mapValues(_.groupBy(v => moment(v.date).startOf('hour').format("LT")))
)
const arr = [{"date":1555318593445,"name":"john","country":"AU","text":"Hey"},{"date":155531859300,"name":"jake","country":"US","text":"Hey"},{"date":1555316593445,"name":"jeff","country":"US","text":"Hey"},{"date":1555316593345,"name":"jared","country":"US","text":"Hey"},{"date":155531659400,"name":"jane","country":"UK","text":"Hey"}]
const result = fn(arr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
Comments Q&A:
How is flow different from chain?
Flow generates a function that runs the code in a sequence and passes the results of one function call to the next one. The lodash chain runs the code, but tries to make lazy evaluation, and combines several calls to a single one, which means that a chain of .map.filter will only iterate the array once.
However, the laziness comes with a price, you need to import the entire lodash module for it to work. While using flow, you can import just the functions that you need.
why mapvalues was the second step?
The result of the 1st group is an object { date: {}, date: {} ), and you need to group each the object values by itself. To do so you need to map the values of each date, and group them by the hour.
Related
i have the following array instance
this.array=[{id:"121",score:"5",createdOn:"2022-05-17T19:52:23.6846702+00:00"}
{id:"121",score:"8",createdOn:"2022-05-19T13:00:00.6846702+00:00"}
{id:"122",score:"7",createdOn:"2022-04-11T08:00:00.6846702+00:00"}
{id:"121",score:"1",createdOn:"2022-03-12T12:00:00.6846702+00:00"}
]
how do i some the values with a matching id and month.
for example my ouput should be
newArray=[{id:"121",score:13,month:"May"}
{id:"122",score:7,month:"April"}
{id:"121",score:1,month:"March"}
]
i tried something like this
this.array.forEach(item => {
var obj = {}
if (obj.hasOwnProperty(item.id)) {
obj["id"] = obj[item.id] + parseFloat(item.score);
}
else {
obj["id"] = parseFloat(item.score);
}
this.newArray.push(obj);
});
But it didnt work and i dont know how to check the month
You can use a fairly standard 'group by' using a compound key of id_month.
The example uses reduce() to iterate the array, toLocaleDateString() to retrieve the month name from the ISO date string and a template literal to create the compound key.
The score should be converted to a number before adding to avoid accidental concatenation, here using the unary plus (+) operator.
Finally we take just the Object.values of the grouped object as the result.
const array = [{ id: "121", score: "5", createdOn: "2022-05-17T19:52:23.6846702+00:00" }, { id: "121", score: "8", createdOn: "2022-05-19T13:00:00.6846702+00:00" }, { id: "122", score: "7", createdOn: "2022-04-11T08:00:00.6846702+00:00" }, { id: "121", score: "1", createdOn: "2022-03-12T12:00:00.6846702+00:00" },];
const result = Object.values(
array.reduce((a, { id, createdOn, score, ...rest }) => {
const month = new Date(createdOn).toLocaleDateString('en', { month: 'long' });
a[`${id}_${month}`] ??= { id, month, score: 0, ...rest };
a[`${id}_${month}`].score += +score;
return a;
}, {})
)
console.log(result)
The exact same logic can be used in a standard for...of loop instead of within a reduce() call if you prefer.
const array = [{ id: "121", score: "5", createdOn: "2022-05-17T19:52:23.6846702+00:00" }, { id: "121", score: "8", createdOn: "2022-05-19T13:00:00.6846702+00:00" }, { id: "122", score: "7", createdOn: "2022-04-11T08:00:00.6846702+00:00" }, { id: "121", score: "1", createdOn: "2022-03-12T12:00:00.6846702+00:00" },];
const grouped = {}
for (const { id, createdOn, score, ...rest } of array) {
const month = new Date(createdOn).toLocaleDateString('en', { month: 'long' });
grouped[`${id}_${month}`] ??= { id, month, score: 0, ...rest }
grouped[`${id}_${month}`].score += +score;
}
const result = Object.values(grouped);
console.log(result)
Use a temporary object to hold the updated information.
Because you need to separate out the objects that have the same ids but that were created on different months you can use a key id-month on the temporary object to identify them.
Loop over the array of objects getting the month name, and create the key. (I've used Intl.DateTimeFormat here because you can pass in a language string to get a different result from the function - try 'es-ES' for example.)
If the property with that key doesn't exist on the temporary object add a default object to the temporary object with a default object value, and then increase its score (making sure you coerce it to a number first).
Finally get the Object.values which will return an array of all those values in the temporary object.
const arr=[{id:"121",score:"5",createdOn:"2022-05-17T19:52:23.6846702+00:00"},{id:"121",score:"8",createdOn:"2022-05-19T13:00:00.6846702+00:00"},{id:"122",score:"7",createdOn:"2022-04-11T08:00:00.6846702+00:00"},{id:"121",score:"1",createdOn:"2022-03-12T12:00:00.6846702+00:00"}];
const temp = {};
const language = 'en-GB';
function getMonth(createdOn, language) {
const date = new Date(createdOn);
return new Intl.DateTimeFormat(language, { month: 'long' }).format(date);
}
for (const obj of arr) {
const { id, score, createdOn } = obj;
const month = getMonth(createdOn, language);
const key = `${id}-${month}`;
temp[key] ??= { id, score: 0, month };
temp[key].score += Number(score);
}
console.log(Object.values(temp));
Additional documentation
Destructuring assignment
for/of
I am using Ramda library in my project.
is it possible to transform following JSON array
from
[
{
"id": 1,
"name": "test",
},
{
"id": 2,
"name": "test2"
}
];
To
[
{
"id": 1,
"id": 2,
},
{
"name": "test",
"name": "test2"
}
];
pleas help
As OriDrori pointed out, your requested output is not valid JS. I'm going to make a slightly different guess, though, as to a useful variant of it that is valid, namely that we want an output like this:
{
id: [1, 2],
name: ['test1', 'test2']
}
Here's one simple way to achieve that in vanilla JS:
const extract = (data) => {
const keys = [... new Set (data .flatMap (Object .keys))]
return Object .fromEntries (
keys.map (k => [k, data .map (o => o [k])])
)
}
const data = [{id: 1, name: "test"}, {id: 2, name: "test2"}]
console .log (
extract (data)
)
We can definitely clean that up with Ramda functions. Another version might look like this:
const extract = (data) => fromPairs (
map (k => [k, map (o => o [k], data)]) (uniq (chain (keys) (data)))
)
const data = [{id: 1, name: "test"}, {id: 2, name: "test2"}]
console .log (extract (data))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script>
<script> const {fromPairs, map, uniq, chain, keys} = R </script>
While we could go completely point-free, I find this version much less readable:
const extract = compose (
fromPairs,
lift (map) (
unary (compose (ap (pair), flip (pluck))),
compose (uniq, chain (keys))
)
)
const data = [
{id: 1, name: "test"},
{id: 2, name: "test2"}
]
console .log (extract (data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script>
<script>
const {compose, fromPairs, lift, map, unary, ap, pair, flip, pluck, uniq, chain, keys} = R
</script>
Objects can't have multiple properties with the same key, so
{ "id": 1, "id": 2 } and { "name": "test", "name": "test2" } are invalid. I assume that you need an array of ids and an array of names:
[[1, 2, 3], ['test', 'test2', 'test3']]
If all objects are have the same order of keys - ie no { id: 1, name: 'test'} and { name: 'test2', id: 1 }, and you need all the values in an object, you can map the objects to their values, and then transpose:
const { pipe, map, values, transpose } = R;
const fn = pipe(
map(values),
transpose,
);
const arr = [{"id":1,"name":"test"},{"id":2,"name":"test2"},{"id":3,"name":"test3"}];
const result = fn(arr);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
If some objects have a different keys insertion order, you want to change the order of the resulting arrays, or if you need some of the keys, you can get the values with R.props, and then transpose:
const { pipe, map, props, transpose } = R;
const fn = pipe(
map(props(['name', 'id'])), // example - name would be the 1st sub-array
transpose,
);
const arr = [{"id":1,"name":"test"},{"id":2,"name":"test2"},{"id":3,"name":"test3"}];
const result = fn(arr);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
If you want the structure suggested by Scott Sauyet's:
{
id: [1, 2],
name: ['test1', 'test2']
}
I would map and flatten the objects to an array of pairs with R.chain and R.toPairs, group them by the 1st item in each pair (the original key), and then map each groups item to the last item in each pair (the original value).
const { pipe, chain, toPairs, groupBy, head, map, last } = R
const fn = pipe(
chain(toPairs),
groupBy(head),
map(map(last)), // map(pipe(map(last), uniq)) if you want only unique items
)
const arr = [{"id":1,"name":"test"},{"id":2,"name":"test2"},{"id":3,"name":"test3"}];
console.log(fn(arr))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script>
I've been playing around with Lodash and not getting close to a solution that doesn't involve a lot of extra looping and overhead.
data: [
{
name: "FirstResult", values: [
{
value: { NameCount: 1, OtherCount: 1 },
date: 2019-05-15T07:00:00+0000
},
{
value: { NameCount: 1 },
date: 2019-05-16T07:00:00+0000
}
]
},
{
name: "SecondResult",
values: [
{
value: { NameCount: 1 },
date: 2019-05-15T07:00:00+0000
},
{
value: { BuyCount: 2, SellCount: 1 },
date: 2019-05-16T07:00:00+0000
}
]
}
]
I'd like to flatten this and have it combined and aggregated by using the date as the key returning some configuration like:
[
{ date: 2019-05-15T07:00:00+0000, values: { NameCount: 2, OtherCount: 1 } },
{ date: 2019-05-16T07:00:00+0000, values: { NameCount: 1, BuyCount: 2, SellCount: 1 } }
]
Or even just a flat object array is fine like:
[
{ date: 2019-05-15T07:00:00+0000, NameCount: 2, OtherCount: 1 },
{ date: 2019-05-16T07:00:00+0000, NameCount: 1, BuyCount: 2, SellCount: 1 }
]
Does anyone have any ideas on how to do this with either a Lodash or Vanilla solution?
You can use a lodash's chain to flatten, group by the date, and then map and merge each group to a single object:
const fn = data => _(data)
.flatMap('values') // flatten to array of objects
.groupBy(o => o.date.toISOString()) // group by the iso representation
.map(group => { // map the groups by merging, and converting to require format
const { date, value } = _.mergeWith({}, ...group, (objValue, srcValue) =>
_.isNumber(objValue) ? objValue + srcValue : undefined // combine numeric values
)
return {
date,
...value,
}
})
.value()
const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Or you can use _.flow() to generate the function (I'm using lodash/fp here):
const { flow, flatMap, groupBy, map, mergeAllWith, cond, isNumber, add } = _
const fn = flow(
flatMap('values'), // flatten to array of objects
groupBy(o => o.date.toISOString()), // group by the iso representation
map(mergeAllWith(cond([[isNumber, add]]))), // combine numeric values
map(({ date, value }) => ({ date, ...value })) // format the objects
)
const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]
const result = fn(data)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
Here is pure ES6 solution based on Array.reduce and Array.forEach for the object keys:
const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]
let result = data.reduce((r, { values }) => {
values.forEach(({ value, date }) => {
let keys = Object.keys(value), d = date.toISOString()
r[d] = r[d] || Object.assign({}, ...keys.map(x => ({ date: d, [x]: 0 })))
keys.forEach(k => r[d][k] = (r[d][k] || 0) + value[k])
})
return r
}, {})
console.log(Object.values(result))
The main idea is to get the keys of the value object white iterating and compose an object with them while grouping by the date. Then last forEach just sums each result object value.
I have a large javascript array of some people Bought a car in different years.
the simplified array is like this:
const owners = [{
name: "john",
hasCar: true,
yearBought: 2002
}, {
name: "john",
hasCar: true,
yearBought: 2005
}, {
name: "mary",
hasCar: true,
yearBought: 2015
}, {
name: "john",
hasCar: true,
yearBought: 2018
}]
if a person has more than one car (like John in this example), there is different objects for him with different years he has bought the car. I want to merge objects belongs to each individual person and the final result should be like this:
const owners = [{
name: "john",
hasCar: true,
yearBought: [2002, 2005, 2018]
}, {
name: "mary",
hasCar: true,
yearBought: 2018
}]
You could use reduce the array and group them based on name first. The accumulator is an object with each unique name as key. If the name already exists, use concat to push them into the array. Else, create a new key in the accumulator and set it to the current object. Then, use Object.values() to get the values of the array as an array
const owners = [{name:"john",hasCar:!0,yearBought:2002},{name:"john",hasCar:!0,yearBought:2005},{name:"mary",hasCar:!0,yearBought:2015},{name:"john",hasCar:!0,yearBought:2018}];
const merged = owners.reduce((r, o) => {
if(r[o.name])
r[o.name].yearBought = [].concat(r[o.name].yearBought, o.yearBought)
else
r[o.name] = { ...o };
return r;
},{})
console.log(Object.values(merged))
Just use reduce:
const owners = [{name:"john",hasCar:true,yearBought:2002},{name:"john",hasCar:true,yearBought:2005},{name:"mary",hasCar:true,yearBought:2015},{name:"john",hasCar:true,yearBought:2018}];
const newOwners = Object.values(owners.reduce((acc, curr) => {
acc[curr.name] = acc[curr.name] ? { ...acc[curr.name], yearBought: [].concat(acc[curr.name].yearBought, curr.yearBought) } : curr;
return acc;
}, {}));
console.log(newOwners);
I hope this help using PURE lodash functions.. it looks clean and readable.
var array = [{
name: "john",
hasCar: true,
yearBought: 2002
}, {
name: "john",
hasCar: true,
yearBought: 2005
}, {
name: "mary",
hasCar: true,
yearBought: 2015
}, {
name: "john",
hasCar: true,
yearBought: 2018
}]
function mergeNames(arr) {
return Object.values(_.chain(arr).groupBy('name').mapValues((g) => (_.merge(...g, {
yearBought: _.map(g, 'yearBought')
}))).value());
}
console.log(mergeNames(array));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
Thanks :)
You can group the items with a unique key (name in your case), then map the groups, and merge the items in each group:
const { flow, partialRight: pr, groupBy, map, mergeWith, concat, isUndefined } = _
const mergeDuplicates = (isCollected, key) => flow(
pr(groupBy, key), // group by the unique key
pr(map, group => mergeWith({}, ...group,
(o, s, k) => isCollected(k) && !isUndefined(o) ? concat(o, s) : s
)) // merge each group to a new object
)
const owners = [{name:"john",hasCar:true,yearBought:2002},{name:"john",hasCar:true,yearBought:2005},{name:"mary",hasCar:!0,yearBought:2015},{name:"john",hasCar:true,yearBought:2018}]
const isCollected = key => key === 'yearBought'
const result = mergeDuplicates(isCollected, 'name')(owners)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
And the lodash/fp version:
const { flow, groupBy, map, mergeAllWith, cond, nthArg, concat } = _
const mergeDuplicates = (isCollected, key) => flow(
groupBy(key),
map(mergeAllWith(cond([[
flow(nthArg(2), isCollected),
concat,
nthArg(1)
]])))
)
const owners = [{name:"john",hasCar:!0,yearBought:2002},{name:"john",hasCar:!0,yearBought:2005},{name:"mary",hasCar:!0,yearBought:2015},{name:"john",hasCar:!0,yearBought:2018}]
const isCollected = key => key === 'yearBought'
const result = mergeDuplicates(isCollected, 'name')(owners)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
I have an array containing stories, each story also has a "day", I'd like to be able to merge stories on the same "day" into a new array.
stories = [
{
id: 1,
day: '18-02-2017',
user: 1,
story_data: //JSON containing a single story + other data
},
{
id: 2,
day: '18-02-2017',
user: 1,
story_data: //JSON containing a single story + other data
},
{
id: 3,
day: '17-02-2017',
user: 1,
story_data: //JSON containing a single story + other data
}
]
Here is what I'd like my output array to look like:
feed = [
{
day: '18-02-2017',
stories: [
//multiple JSON story items
]
},
{
day: '17-02-2017',
stories: [
//multiple JSON story items
]
}
]
I'm using the Async library in NodeJS for the necessary FOR loops since I also need to work with the data asynchronously before it's added to this final array - I understand what needs to happen to make the new array it's just been going completely over my head how to put it into code.
Try running this code:
var jsonStories = {};
stories.forEach(function(story) {
if (!jsonStories[story['day']]) {
jsonStories[story['day']] = {
'day': story['day'],
'stories': []
};
}
jsonStories[story['day']]['stories'].push(story);
});
var storiesArr = [];
Object.keys(jsonStories).forEach(function(key) {
storiesArr.push(jsonStories[key]);
});
You will get an ordered array.
You can also get it as JSON array if you remove the four last rows.
function groupByDay(arr) {
var hash = arr.reduce(function(h, s) { // h is the hash object, s is the current story
h[s.day] = h[s.day] || {'day': s.day, 'stories': []}; // if the hash doesn't have an entry for this story's day, then add it
h[s.day].stories.push(s); // add this story to the stories array of the object that acummulates the result for this day
return h;
}, {});
return Object.keys(hash).map(function(key) { // unwrap the objects from the hash object and return them as an array (the result)
return hash[key];
});
}
Here is the MDN docs for Array.prototype.reduce, Array.prototype.map and Object.keys.
You could do it in a single loop with a closure over a hash table.
var stories = [{ id: 1, day: '18-02-2017', user: 1, story_data: '01' }, { id: 2, day: '18-02-2017', user: 1, story_data: '02' }, { id: 3, day: '17-02-2017', user: 1, story_data: '03' }],
result = stories.reduce(function (hash) {
return function (r, a) {
if (!hash[a.day]) {
hash[a.day] = { day: a.day, stories: [] };
r.push(hash[a.day]);
}
hash[a.day].stories.push(a);
return r;
};
}(Object.create(null)), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
let day2story = {};
stories.forEach((story) => {
let curr = day2story[story.day] || [];
curr.push(story.story_data);
day2story[story.day] = curr;
});
let feed = [];
Object.keys(day2story).forEach((day) => feed.push({day: day, stories: day2story[day]}));
console.log(JSON.stringify(feed))
You could use this ES6 code:
const result = Array.from(
stories.reduce(
(acc, {day, story_data}) => acc.set(day, (acc.get(day) || []).concat(story_data)),
new Map
),
([day, story_data]) => ({day, story_data})
);
const stories = [
{
id: 1,
day: '18-02-2017',
user: 1,
story_data: "story1"
},
{
id: 2,
day: '18-02-2017',
user: 1,
story_data: "story2"
},
{
id: 3,
day: '17-02-2017',
user: 1,
story_data: "story3"
}
];
const result = Array.from(
stories.reduce(
(acc, {day, story_data}) => acc.set(day, (acc.get(day) || []).concat(story_data)),
new Map
),
([day, story_data]) => ({day, story_data})
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Explanation
The reduce method creates a Map, keyed by the dates, and with the corresponding story arrays as values. So if the key does not yet exist in the map, an empty array is taken (|| []), otherwise its current value, and then the new story is added to it.
The map returned by reduce would in itself be a nice structure to work with, but as you asked for an array of objects, Array.from is applied to that map. This results in an array of pairs (sub-arrays with key / value entries), but Array.from accepts a callback function with which this pair can be transformed into the object with 2 properties.
This solution uses arrow functions, destructuring and Map which are all ES6 features.