Group Array Items Using Object

How can I group an array of objects by key?

Timo's answer is how I would do it. Simple _.groupBy, and allow some duplications in the objects in the grouped structure.

However the OP also asked for the duplicate make keys to be removed. If you wanted to go all the way:

var grouped = _.mapValues(_.groupBy(cars, 'make'),
clist => clist.map(car => _.omit(car, 'make')));

console.log(grouped);

Yields:

{ audi:
[ { model: 'r8', year: '2012' },
{ model: 'rs5', year: '2013' } ],
ford:
[ { model: 'mustang', year: '2012' },
{ model: 'fusion', year: '2015' } ],
kia:
[ { model: 'optima', year: '2012' } ]
}

If you wanted to do this using Underscore.js, note that its version of _.mapValues is called _.mapObject.

How to group array of objects by certain property values

A back to the future answer :

Not yet supported by lot of browsers but will come soon (Stage 3 for TC39) and already available in polyfill core-js) is the new groupBy method on the array object.

This will allows you to do it directly like this :

employees.groupBy(employee => employee.company);

or even :

employees.groupBy(({company}) => company);

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/groupBy

Most efficient method to groupby on an array of objects

If you want to avoid external libraries, you can concisely implement a vanilla version of groupBy() like so:

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

console.log(groupBy(['one', 'two', 'three'], 'length'));

// => {"3": ["one", "two"], "5": ["three"]}

In an array of objects how to group objects which have same value and include the values that differ

This looks like something reduce() should be used for.
Use find() to find in the existing array element based on some condition.
If element exists, push into colors property of the element.
Else push into the array a new object.

const arr = [
{
name: 'A',
color: 'blue',
},
{
name: 'A',
color: 'purple',
},
{
name: 'B',
color: 'Yellow',
},
{
name: 'B',
color: 'Green',
},
];

let ans = arr.reduce((agg,curr) => {
let found = agg.find((x) => x.name === curr.name);
if(found){
found.colors.push(curr.color);
}
else{
agg.push({
name : curr.name,
colors : [curr.color]
});
}
return agg;
},[]);

console.log(ans);

Group array items using object

First, in JavaScript it's generally not a good idea to iterate over arrays using for ... in. See Why is using "for...in" with array iteration a bad idea? for details.

So you might try something like this:

var groups = {};
for (var i = 0; i < myArray.length; i++) {
var groupName = myArray[i].group;
if (!groups[groupName]) {
groups[groupName] = [];
}
groups[groupName].push(myArray[i].color);
}
myArray = [];
for (var groupName in groups) {
myArray.push({group: groupName, color: groups[groupName]});
}

Using the intermediary groups object here helps speed things up because it allows you to avoid nesting loops to search through the arrays. Also, because groups is an object (rather than an array) iterating over it using for ... in is appropriate.

Addendum

FWIW, if you want to avoid duplicate color entries in the resulting arrays you could add an if statement above the line groups[groupName].push(myArray[i].color); to guard against duplicates. Using jQuery it would look like this;

if (!$.inArray(myArray[i].color, groups[groupName])) {
groups[groupName].push(myArray[i].color);
}

Without jQuery you may want to add a function that does the same thing as jQuery's inArray:

Array.prototype.contains = function(value) {
for (var i = 0; i < this.length; i++) {
if (this[i] === value)
return true;
}
return false;
}

and then use it like this:

if (!groups[groupName].contains(myArray[i].color)) {
groups[groupName].push(myArray[i].color);
}

Note that in either case you are going to slow things down a bit due to all the extra iteration, so if you don't need to avoid duplicate color entries in the result arrays I would recommend avoiding this extra code. There

Array of objects - how to group items by property and squash data from each of grouped items into 1

Simply spread the Array#map result each time:

const restructureArr = () => {
const restructuredArr = arrToRestructure.reduce((prevVal, nextVal) => ({
...prevVal,
[nextVal["y"]]: [
...(prevVal[nextVal["y"]] || []),
...nextVal.data.map((nextVal) => nextVal.label), // fix
]
}) , []);
return restructuredArr;
}

Some enhancements:

const restructureArr = (arrToRestructure = []) => 
arrToRestructure.reduce((acc, { y, data = [] }) => ({
...acc,
[y]: [
...(acc[y] || []),
...data.map(({ label }) => label),
]
}), []);

const arrToRestructure = [
{ x: "test-x1", y: "test-y1", data: [ {id: 1, label: "label-y1-1"}, {id: 2, label: "label-y1-2"} ] },
{ x: "test-x2", y: "test-y1", data: [ {id: 1, label: "label-y1-3"}, {id: 2, label: "label-y1-4"} ] },
{ x: "test-x2", y: "test-y2", data: [ {id: 1, label: "label-y2-1"}, {id: 2, label: "label-y2-2"} ] }
];
console.log(restructureArr(arrToRestructure));

How to group items in an array by property using reduce and return an array of new objects

You should be able to do this in a few lines using reduce, we create a map using the Subdomain name as the key, then we'll use Object.values to turn the resulting object into an array.

For example:

const relatedSites = [ { "SubdomainName": "client1", "ClientName": "Eastern Region", "ClientAlias": "eastern-region" }, { "SubdomainName": "client1", "ClientName": "City of Knox", "ClientAlias": "knox" }, { "SubdomainName": "client2", "ClientName": "Eastern Region", "ClientAlias": "eastern-region" }, { "SubdomainName": "client2", "ClientName": "City of Knox", "ClientAlias": "knox" } ]; 

const result = Object.values(relatedSites.reduce((acc, el) => {
acc[el.SubdomainName] = acc[el.SubdomainName] || { title: el.SubdomainName, links: [] };
acc[el.SubdomainName].links.push({ url: `https://${el.SubdomainName}.com/${el.ClientAlias}`, displayText: el.ClientName });
return acc;
}, {}))

console.log(result)

How to group array with multiple properties

You've clarified that every item can only have one of the three properites is_sweet/is_spicy/is_bitter equal to true.

Given this assumption, one approach is that we can group the items based on the value of the quantity x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1 for the item x. This quantity is equal to 1 for those who have only is_bitter: true, 2 for item having only is_spicy: true, and 4 for item having only is_sweet: true.

Once you group according to that quantity, you can use _.values to get rid of the keys created by _.groupBy:

_.values(_.groupBy(foodsData, x => x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1))

Obviously you can give a descriptive name to the predicate:

const flavour = food => food.is_sweet*4 + food.is_spicy*2 + food.is_bitter*1;
let groupedFoods = _.values(_.groupBy(foodsData, flavour))

Notice that in case items are allowed to have more than one of those three keys true, the code above will still give sensible results, in the sense that you will have the eight groups relative to [is_sweet, is_spicy, is_bitter] equal to [0,0,0], [0,0,1], [0,1,0], [0,1,1], [1,0,0], [1,0,1], [1,1,0], and [1,1,1].

In hindsight, we don't need that the predicate flavour pulls out a number for each entry food; based on last paragraph, we are just happy if it pulls the three values food.is_sweet, food.is_spicy, and food.is_bitter, and puts them in an array. Lodash has _.over for this, so we could define

const flavour = _.over([is_sweet, is_spicy, is_bitter]);

A few words about the readability of the solution above.

  • _.groupBy is a higher level abstraction than reduce-like (member or free) functions; in other words the latter functions are much more powerful than you need for the usecase in the question, and therefore they are less expressive than the former. Indeed, _.groupBy could well be implemented in terms of reduce. Here's a brutal implementation to show that it is possible:

    const groupBy = (things, f) => {
    return things.reduce((acc, item) => {
    if (acc[f(item)] == undefined)
    acc[f(item)] = [item];
    else
    acc[f(item)].push(item);
    return acc;
    }, {});
    }
    // both these give the same result:
    groupBy([1,2,3,4,4,4], x => x % 2 == 0); // mine
    _.groupBy([1,2,3,4,4,4], x => x % 2 == 0); // lodash's
  • The solution is textually extremely short: it doesn't matter how "complicate" or "fancy" it looks; once you get used to this way of doing things (aka the right way), you end up reading the code. Look at it again:

    _.values(_.groupBy(foodsData, flavour))

    That's already fairly readable, but if you use lodash/fp module, you can even improve it:

    _.values(_.groupBy(flavour, foodsData))

    How far is that from the following English sentence?

    "get the values of the result of grouping by flavor the foods"

    Saying that's not readable just means denying the reality of things.

  • Longer solutions using lower lever constructs like for loops or (a bit better) reduce-like utilities force you to parse the code, instead. There's no way you understand what those solutions do without carefully inspecting their bodies. Even if you decided to pull the function (acc, item) => { … } out of the call to reduce and give it a name, ending up with input.reduce(fun, acc) how would you even name it? It is a function which pushes elements on top of the arrays in an intermediate result. Except in trivial use cases, you're not gonna find a good name for such a mouthful. You're just moving the problem somewhere else. People reading this code will still be puzzled.

  • _.over is maybe a bit scary too:

    const flavour = _.over([is_sweet, is_spicy, is_bitter]);

    But really, what does it cost tha you just learn and accept what _.over means? I mean, it is just like learning a new language, nothing more. And the explanation of _.over is fairly simple:

    _.over([iteratees=[_.identity]])

    Creates a function that invokes iteratees with the arguments it receives and returns their results.

    It is so simple: _.over(['a', 'b', 'd'])({a: 1, b: 2, c: 3}) == [1, 2, undefined]

Group array of objects by date in javascript

This is a fairly standard 'group-by' but since you have multiple arrays and you'd like to use their variable names as properties you'll need a mechanism to do this.

The example below first creates an object from the three arrays using shorthand property names, then iterates the Object.entries() of this object to group by date assigning data by the variable names as keys. To ensure that all the objects have all the array properties even when they have no relevant entries it creates a template object from the Object.keys() of your combined object and spreading it into each accumulator in the group-by operation.

const Object1 = [{ "data": "1-1", "createdAt": "2022-03-01", }, { "data": "1-2", "createdAt": "2022-03-02", }, { "data": "1-4", "createdAt": "2022-03-04", }, { "data": "1-5", "createdAt": "2022-03-05", }, { "data": "1-6", "createdAt": "2022-03-06", }, { "data": "1-7", "createdAt": "2022-03-07", }];
const Object2 = [{ "data": "2-1", "createdAt": "2022-03-02", }, { "data": "2-2", "createdAt": "2022-03-03", }, { "data": "2-3", "createdAt": "2022-03-04", }, { "data": "2-4", "createdAt": "2022-03-05", }, { "data": "2-5", "createdAt": "2022-03-06", }, { "data": "2-6", "createdAt": "2022-03-07", }, { "data": "2-7", "createdAt": "2022-03-08", }];
const Object3 = [{ "data": "3-1", "createdAt": "2022-03-03", }, { "data": "3-2", "createdAt": "2022-03-04", }, { "data": "3-3", "createdAt": "2022-03-05", }, { "data": "3-4", "createdAt": "2022-03-06", }, { "data": "3-5", "createdAt": "2022-03-07", }, { "data": "3-6", "createdAt": "2022-03-08", }, { "data": "3-7", "createdAt": "2022-03-09", }];

const objects = { Object1, Object2, Object3 };
const template = Object.fromEntries(Object.keys(objects).map(k => [k, undefined]));

const grouped = {};

for (const [objGroup, objArr] of Object.entries(objects)) {
for (const { createdAt, data } of objArr) {
const group = grouped[createdAt] ??= { ...template, createdAt };
group[objGroup] = data;
}
}

const result = Object.values(grouped);

console.log(result);


Related Topics



Leave a reply



Submit