Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A more readable and annotated version of the Javascript groupBy from Ceasar Bautista (https://stackoverflow.com/a/34890276/1376063)
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
};
@robmathers

This comment has been minimized.

Copy link
Owner Author

@robmathers robmathers commented Oct 25, 2018

To make this more readable, I renamed the parameters, and split up one line into several to get a better model of what's happening.
I changed (rv[x[key]] = rv[x[key]] || []).push(x); to:

var group = item[key];
storage[group] = storage[group] || [];
storage[group].push(item);
@precisiongroup

This comment has been minimized.

Copy link

@precisiongroup precisiongroup commented Dec 17, 2019

How would you introduce multiple keys to group by?

@youyi218

This comment has been minimized.

Copy link

@youyi218 youyi218 commented Mar 20, 2020

How would you introduce multiple keys to group by?

var groupBy = function (xs, key1, key2) {
    return xs.reduce(function (rv, x) {
        (rv[x[key1] + ',' + x[key2]] = rv[x[key1] + ',' + x[key2]] || []).push(x);
        return rv;
    }, {});
};

this piece of code can do the job

@HectorBritoDev

This comment has been minimized.

Copy link

@HectorBritoDev HectorBritoDev commented Apr 29, 2020

Thank you SO MUCH, for creating this readable version of the code. It helps me a lot.<3

@Pohatta

This comment has been minimized.

Copy link

@Pohatta Pohatta commented May 29, 2020

Just spent significant amount of time trying to understand this before I noticed your comment in Stack Overflow. Very useful, Thank you!

@fortwo

This comment has been minimized.

Copy link

@fortwo fortwo commented Jun 22, 2020

Thanks for sharing this! I've created another version that supports multiple keys https://gist.github.com/one89/395a3ffb2df4d0648ec7b0caf6cf5d37

@kwambokaB

This comment has been minimized.

Copy link

@kwambokaB kwambokaB commented Jul 17, 2020

Thank you for this. Very helpful

@GeorgeIpsum

This comment has been minimized.

Copy link

@GeorgeIpsum GeorgeIpsum commented Oct 12, 2020

How would you introduce multiple keys to group by?

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:

/**
 * @param {any[]} arr
 * @param {string[]} keys
 */
const groupBy = (arr, keys) => {
  return arr.reduce((storage, item) => {
    const objKey = keys.map(key => `${ item[key] }`).join(':'); //should be some unique delimiter that wont appear in your keys
    if (storage[objKey]) {
      storage[objKey].push(item);
    } else {
      storage[objKey] = [item];
    }
    return storage;
  }, {});
};
@DarkLite1

This comment has been minimized.

Copy link

@DarkLite1 DarkLite1 commented Dec 7, 2020

It would be great to have a TypeScript version of this.

@GeorgeIpsum

This comment has been minimized.

Copy link

@GeorgeIpsum GeorgeIpsum commented Dec 7, 2020

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[] });
}
@DarkLite1

This comment has been minimized.

Copy link

@DarkLite1 DarkLite1 commented Dec 8, 2020

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.

@GeorgeIpsum

This comment has been minimized.

Copy link

@GeorgeIpsum GeorgeIpsum commented Dec 8, 2020

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

@jose2007kj

This comment has been minimized.

Copy link

@jose2007kj jose2007kj commented Dec 22, 2020

thanks,really helpfull

@PolyUnityCTO

This comment has been minimized.

Copy link

@PolyUnityCTO PolyUnityCTO commented Feb 26, 2021

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
		{}
	);
};
@AndreasHerz

This comment has been minimized.

Copy link

@AndreasHerz AndreasHerz commented May 28, 2021

thank you all for these great solutions. Really helped me 😄

@ryderwishart

This comment has been minimized.

Copy link

@ryderwishart ryderwishart commented Jun 14, 2021

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment