-
-
Save robmathers/1830ce09695f759bf2c4df15c29dd22d to your computer and use it in GitHub Desktop.
var groupBy = function(data, key) { // `data` is an array of objects, `key` is the key (or property accessor) to group by | |
// reduce runs this anonymous function on each element of `data` (the `item` parameter, | |
// returning the `storage` parameter at the end | |
return data.reduce(function(storage, item) { | |
// get the first instance of the key by which we're grouping | |
var group = item[key]; | |
// set `storage` for this instance of group to the outer scope (if not empty) or initialize it | |
storage[group] = storage[group] || []; | |
// add this item to its group within `storage` | |
storage[group].push(item); | |
// return the updated storage to the reduce function, which will then loop through the next | |
return storage; | |
}, {}); // {} is the initial value of the storage | |
}; |
It would be great to have a TypeScript version of this.
It would be great to have a TypeScript version of this.
@DarkLite1 This is how I've adapted it for TS use:
const groupBy = <T>(arr: T[], keys: (keyof T)[]): { [key: string]: T[] } => {
return arr.reduce((storage, item) => {
const objKey = keys.map(key => `${ item[key] }`).join(':');
if (storage[objKey]) {
storage[objKey].push(item);
} else {
storage[objKey] = [item];
}
return storage;
}, {} as { [key: string]: T[] });
}
Thank you for getting back to me. On StackOvelfow we posted the same question and got this answer:
type ObjectKey = string | number | symbol
export const groupBy = <
K extends ObjectKey,
TItem extends Record<K, ObjectKey>
>(
items: TItem[],
key: K
): Record<ObjectKey, TItem[]> =>
items.reduce(
(result, item) => ({
...result,
[item[key]]: [...(result[item[key]] || []), item],
}),
{} as Record<ObjectKey, TItem[]>
)
I'm still a noob, so I don't know which one is better but it works fine. The only thing to keep in mind is that the value for ObjectKey
cannot be empty. It would be great if it was empty that it would use a default value to group by.
The answer you got on SO is more complete, mine is a more naive approach that assumes objects in the array have no nested objects internally and that keys are always strings, though the SO answer does restrict you to a single key (you can pretty easily change that to make the second argument into the function an array of keys or just do a spread of string values for all other function arguments). If you need this for a more simple use case you could use my approach which doesn’t require you to specify key typing
thanks,really helpfull
This was very helpful. Here's my version, which supports multiple keys in case the property you want to group by is deeper in the data structure:
// Takes an array of objects and the property by which they should be grouped.
// Produces an object of arrays keyed by the specified property values.
//
// Provide multiple keys if your data is nested: groupBy(dogs, 'values', 'emoji')
//
// Ex: [{id: 1, group: 'A'}, {id: 2, group: 'B'}, {id: 3, group: 'A'}], 'group'
// =>
// {A: [{id: 1, group: 'A'}, {id: 3, group: 'A'}], B: [{id: 2, group: 'B'}]}
export const groupBy = (data, ...keys) =>
{
// Ex: {values: {color: 'red'}}, ['values', 'color'] => 'red'
const getGroupFromItem = (item, keys) =>
{
return (keys.length > 1)
? getGroupFromItem(item[keys[0]], keys.slice(1))
: item[keys[0]]
}
return data.reduce((results, item) =>
{
// Get the first instance of the key by which we're grouping
var group = getGroupFromItem(item, keys);
// Ensure that there's an array to hold our results for this group
results[group] = results[group] || [];
// Add this item to the appropriate group within results
results[group].push(item);
// Return the updated results object to be passed into next reduce call
return results;
},
// Initial value of the results object
{}
);
};
thank you all for these great solutions. Really helped me 😄
Thanks!
A simple version with a supplier for the groups
const groupBy = (arr, keysSupplier) => {
return arr.reduce((obj, elt) => {
let key = keysSupplier(elt)
if (obj[key] === undefined)
obj[key] = []
obj[key].push(elt)
return obj
}, {})
}
// Example
// Grouping array by typeof
groupBy(array, elt => typeof elt)
i looking for these outputs
[
{
custom: 'data1 custom b',
group_by: 'school x',
"children": {
// data
}
},
{
custom: 'data2 custom a',
group_by: 'school a',
"children": {
// data
}
},
]
but the unexpected is
'school a': {
// data
},
'school x': {
// data
}
i have tried with
storage['data'] = storage['data'] || [];
storage[group] = storage[group] || [];
// add this item to its group within `storage`
storage[group].push(item);
// storage['data'].push({
// label: group,
// mode: 'span',
// html: false,
// children: storage[group]
// });
but its looping the result.
i have solved by add new function look like these
exports.groupByVue = async function(data) {
let result = [];
for (const key in data) {
if (Object.hasOwnProperty.call(data, key)) {
const element = data[key];
result.push({
label: key,
children: element,
})
}
}
return result;
}
but i want the code is inside the group by as default .
I'm using a version of this that allows supplying any list of keys (granted that they're all strings). You can rewrite the function supplied to
keys.map
to handle non-string values fairly easily: