Using GroupBy on an array of objects in JavaScript(wisdomgeek.com)
wisdomgeek.com
Using GroupBy on an array of objects in JavaScript
https://www.wisdomgeek.com/development/web-development/javascript/using-groupby-on-an-array-of-objects-in-javascript/
33 comments
It looks like they are also creating a "groupByToMap" for that reason. See: https://github.com/tc39/proposal-array-grouping
Property keys can also be symbol objects and it seems like that's supported by the groupBy function in addition to strings, see: https://tc39.es/proposal-array-grouping/
Property keys can also be symbol objects and it seems like that's supported by the groupBy function in addition to strings, see: https://tc39.es/proposal-array-grouping/
I wonder if groupBy should have had some sort of into argument. Otherwise, we may end up with four groupBy methods. groupBy, groupByToMap, groupByToWeakMap and groupByToRecord (should records land).
Edit: this has already been discussed https://github.com/tc39/proposal-array-grouping/issues/3
Edit: this has already been discussed https://github.com/tc39/proposal-array-grouping/issues/3
updated the post with groupByToMap, Thanks for pointing that out.
Ah fair enough!
I found this article a bit verbose (maybe because I'm already familiar with the topic) and yet missing three "key" (ah ah) points:
- one can get the keys of an object by using Object.keys or a for-in loop and notice that keys are always strings, even when using arrays.
- the object definition syntax [1] is set to define property strings by the standard (and symbols). You can (and probably should) experiment, but the spec will tell you what happens anyway with 100% certainty (except for implementation bugs, but that's not what we are looking for).
- the definitive answer to "what kind of value an object key can be in Javascript?" is given by the section "The Object Type" of the ECMAScript standard [2]:
> Properties are identified using key values. A property key value is either an ECMAScript String value or a Symbol value. All String and Symbol values, including the empty String, are valid as property keys. A property name is a property key that is a String value.
Experimentation helps anchoring things in memory and building intuition, as well as getting into things. Inviting people to experiment by themselves and not blindly trust what they read is a fantastic advice. I do experiment a lot myself. However, experimentation is not the best way to "prove" things (contrary to what the article states). Definitive answers should be found in specifications and documentations (or implementation sources). No amount of experimentation will be enough to convince me of "See? I did this this and this, and it's always strings".
The article is well written, the same thing with these references to the spec would make it strong and irrefutable.
[1] https://tc39.es/ecma262/#sec-object-initializer-static-seman...
[2] https://tc39.es/ecma262/#sec-object-type
- one can get the keys of an object by using Object.keys or a for-in loop and notice that keys are always strings, even when using arrays.
- the object definition syntax [1] is set to define property strings by the standard (and symbols). You can (and probably should) experiment, but the spec will tell you what happens anyway with 100% certainty (except for implementation bugs, but that's not what we are looking for).
- the definitive answer to "what kind of value an object key can be in Javascript?" is given by the section "The Object Type" of the ECMAScript standard [2]:
> Properties are identified using key values. A property key value is either an ECMAScript String value or a Symbol value. All String and Symbol values, including the empty String, are valid as property keys. A property name is a property key that is a String value.
Experimentation helps anchoring things in memory and building intuition, as well as getting into things. Inviting people to experiment by themselves and not blindly trust what they read is a fantastic advice. I do experiment a lot myself. However, experimentation is not the best way to "prove" things (contrary to what the article states). Definitive answers should be found in specifications and documentations (or implementation sources). No amount of experimentation will be enough to convince me of "See? I did this this and this, and it's always strings".
The article is well written, the same thing with these references to the spec would make it strong and irrefutable.
[1] https://tc39.es/ecma262/#sec-object-initializer-static-seman...
[2] https://tc39.es/ecma262/#sec-object-type
A «group by» function requires keys to be comparable (for equality). You can see this when you look at the type signatures of «group by» functions in any strongly typed language or library. They either require a comparator to be provided, or infer it from the context (type classes in Haskell). JavaScript has quite intuitive equality for primitive types (strings, symbols), but nothing that would be natural for complex objects.
Consider this array: [{ key: {}, foo: 1 }, { key: {}, foo: 2 }], there are multiple answers how a Map can look when you group by 'key'. Both comparing the keys using '===' and deep equality are valid options, depending on needs.
Consider this array: [{ key: {}, foo: 1 }, { key: {}, foo: 2 }], there are multiple answers how a Map can look when you group by 'key'. Both comparing the keys using '===' and deep equality are valid options, depending on needs.
JS maps already have a well defined way that they do key equality: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... It's basically just strict equality (===), except NaN === NaN is false, while they're considered equal for map keys. So in your example, you'd have 2 entries, since {} !== {}.
It sounds like they're going to implement `groupByToMap` as well as `groupBy`, and I assume `groupByToMap` will just use the normal map key equality rules, and I guess the "first" key matching that equality will be used for the grouping.
It sounds like they're going to implement `groupByToMap` as well as `groupBy`, and I assume `groupByToMap` will just use the normal map key equality rules, and I guess the "first" key matching that equality will be used for the grouping.
My point is that you can't override what equality implementation groupByToMap will use, which makes it much less useful.
See for example https://gcanti.github.io/fp-ts/modules/NonEmptyArray.ts.html..., where you can supply your own equality function…
See for example https://gcanti.github.io/fp-ts/modules/NonEmptyArray.ts.html..., where you can supply your own equality function…
> Otherwise seems like a reasonable API, very similar to groupBy in any other moderately functional language
Though lots of them implement the less useful but stream-able groupBy which only groups siblings matching the predicate (or key function).
Though lots of them implement the less useful but stream-able groupBy which only groups siblings matching the predicate (or key function).
Unfortunately the typescript definitions for these two methods haven't been written yet [1].
[1] https://github.com/microsoft/TypeScript/issues/47171
[1] https://github.com/microsoft/TypeScript/issues/47171
Oh lord… so you group by a Number, but the map-key becomes a String. Sounds like idiomatic JavaScript
[deleted]
This isn't a map, Javascript has a dedicated map type. In JavaScript, Objects keys can only be strings or symbols.
groupByToMap would be useful in that case as pointed out in another comment. Updated the post with that.
Javascript has been so widely used for so long, there are two types of languages one that people hate and one that people don’t use
Great - lodash needed a little less now.
Though I do often see people using a combination of maps, filters, reduces, lodash functions etc. one by one, rather than doing everything in a single for in loop. In most cases it feels that For In is still the most performant option, as you want to manipulate the data in more ways than just grouping
Though I do often see people using a combination of maps, filters, reduces, lodash functions etc. one by one, rather than doing everything in a single for in loop. In most cases it feels that For In is still the most performant option, as you want to manipulate the data in more ways than just grouping
For what it's worth, the Laravel PHP framework (no affiliation) has the best introduction to higher-order methods for imperative programmers used to Javascript/C style of anything that I've come across:
https://laravel.com/docs/master/collections
Most of these methods are also available as part of the Eloquent ORM, for filtering/reshaping queries before they're executed:
https://laravel.com/docs/master/eloquent
Since being exposed to this way of working, I rarely use foreach() anymore, much less for(). The main downside being that I find most other languages tedious to work in now. LINQ in .NET/C# is nice, there might be others.
https://laravel.com/docs/master/collections
Most of these methods are also available as part of the Eloquent ORM, for filtering/reshaping queries before they're executed:
https://laravel.com/docs/master/eloquent
Since being exposed to this way of working, I rarely use foreach() anymore, much less for(). The main downside being that I find most other languages tedious to work in now. LINQ in .NET/C# is nice, there might be others.
Hardly shorter than just writing the for loop, i.e. the example in python
groupByAge = collections.defaultdict(list)
for person in people:
groupByAge[person["age"]].append(person)In JS you would also need to check if the key exists first (so you can add it). I think the minimal example would be:
const groupByAge = {}
for (const person in people) {
if (!groupByAge[person.age]) {
groupByAge[person.age] = []
}
groupByAge[person.age].push(person)
}
IMO it is a very nice change to now be able to write: const groupByAge = people.groupBy(p => p.age)A reduce version seems quite okay to me though, and keeps it pretty functional-styled.
const groupByAge = people.reduce((previous, current) => {
if (!previous[current.age]) previous[current.age] = []
previous[current.age].push(current)
return previous
}, {})I normally just write
const groupBy = (fn, arr) => {
return arr.reduce((o, e) => {
const k = fn(e)
if (o[k] !== undefined) o[k].push(e)
else o[k] = [e]
return o
} Object.create(null))
}while there is nothing wrong with that, it's always good to have these things built-in as native functions instead.
[deleted]
I worked on a Ruby gem to add group_by and aggregate_by functions to collections/enumerables. I finished about 1/2 of the functionality and found the implementation to be hateful and thought it would be received poorly. Now seeing this I am second guessing myself.
Enumerable#group_by is in Ruby core https://ruby-doc.org/core-3.0.2/Enumerable.html#method-i-gro...
Ruby already has group_by, unless I'm misunderstanding what you're saying
Also, for those thinking JS objects can have non-string keys, they can’t, it just sometimes appears that way due to JS type coercion: https://www.becomebetterprogrammer.com/can-javasscript-objec...
Edit: as pointed out by shawnz, this isn’t entirely accurate, JS object keys can also be symbols.