0

I have the following array:

const items = [
  { name: 'john', class: 'one', section: 'a' },
  { name: 'john2', class: 'one', section: 'b' },
  { name: 'john3', class: 'one', section: 'a' },
  { name: 'john4', class: 'two', section: 'a' },
  { name: 'john5', class: 'two', section: 'b' },
  { name: 'john6', class: 'two', section: 'b' },
  { name: 'john7', class: 'three', section: 'a' },
  { name: 'john8', class: 'four', section: 'a' },
  { name: 'john9', class: 'four', section: 'b' }
];

I would like to have it grouped in this way:

[
    {
        'oneA': [
            { name: 'john', class: 'one', section: 'a' },
            { name: 'john3', class: 'one', section: 'a' }
        ]
    },
    {
        'oneB': [
            { name: 'john2', class: 'one', section: 'b' }
        ]
    },
    {
        'twoA': [
            { name: 'john4', class: 'two', section: 'a' }
        ]
    },
    {
        'twoB': [
            { name: 'john5', class: 'two', section: 'b' },
            { name: 'john6', class: 'two', section: 'b' }
        ]
    },
    {
        'threeA': [
            { name: 'john7', class: 'three', section: 'a' }
        ]
    },
    {
        'fourA': [
            { name: 'john8', class: 'four', section: 'a' }
        ]
    },
    {
        'fourB': [
            { name: 'john9', class: 'four', section: 'b' }
        ]
    }
]

I have tried something like this:

items.sort(function (a, b) {
  if (a.class > b.class) return 1;
  if (a.class < b.class) return -1;

  if (a.section > b.section) return 1;
  if (a.section < b.section) return -1;
})

This orders the array as I want but it is not grouping as I described above. Do you know a way to reach that?

smartmouse
  • 13,912
  • 34
  • 100
  • 166
  • Why does your result need to contain an array of objects that each have a single key? Why not just one object with multiple keys? And what's the sorting logic? Are you trying to sort based on the text representation of a number? – Robby Cornelissen Jan 28 '20 at 09:03
  • @RobbyCornelissen I need an array of objects because I will loop on it and then I will loop in every array of items. – smartmouse Jan 28 '20 at 09:06
  • 1
    Possible duplicate of https://stackoverflow.com/a/14484332/5611099 – Mario Subotic Jan 28 '20 at 09:12
  • Does this answer your question? [Most efficient method to groupby on an array of objects](https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects) – AZ_ Jan 28 '20 at 09:18
  • Are you sure that the below node has 'three' class value also ? ``` { 'fourA': [ { name: 'john7', class: 'three', section: 'a' }, { name: 'john8', class: 'four', section: 'a' } ] }``` – HulkSapien Jan 28 '20 at 09:20
  • @HulkSapien My bad, I have fixed it. – smartmouse Jan 28 '20 at 09:47

5 Answers5

2

You could just group them by taking a Map.

It generates a key of class and uppercase section, tries to get the value from the map or take an empty array for spreding int a new array with the actual object. Then it sets this array as new value in the map.

Array.from gets all key/value pairs from the map and build new objects with a computed property name.

const
    getKey = o => `${o.class}${o.section.toUpperCase()}`,
    items = [{ name: 'john', class: 'one', section: 'a' }, { name: 'john2', class: 'one', section: 'b' }, { name: 'john3', class: 'one', section: 'a' }, { name: 'john4', class: 'two', section: 'a' }, { name: 'john5', class: 'two', section: 'b' }, { name: 'john6', class: 'two', section: 'b' }, { name: 'john7', class: 'three', section: 'a' }, { name: 'john8', class: 'four', section: 'a' }, { name: 'john9', class: 'four', section: 'b' }],
    result = Array.from(
        items.reduce(
            (m, o) => m.set(getKey(o), [...(m.get(getKey(o)) || []), o]),
            new Map
        ),
        ([k, v]) => ({ [k]: v })
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Could you please explain the code a little more in detail just for better understanding what it does? – SparkFountain Jan 28 '20 at 09:12
  • Omg, I hope you don't code in production like this) – svltmccc Jan 28 '20 at 09:20
  • @NinaScholz i think there are few things, for example: you define `items` and `result` like global. And why do you define `getKey` if you can just concatenate strings in loop body. I'm not sure but I think you can use `{}` instead if `new Map` because it's more familiar for our community. I think you unusually solved the problem, but not in the best way – svltmccc Jan 28 '20 at 09:42
  • **UPD**: `items` and `result` are not global, i'm sorry :) – svltmccc Jan 28 '20 at 09:46
  • 1
    Yes, it works, but it's not familiar as @SaveliTomac said. – smartmouse Jan 28 '20 at 09:52
  • `Map` is made for fast access to a key/value pair. the other advantege, it works in `reduce` directly, becaus it returns the accumulator and this is the paert, where the body of reduce does not need another return value, like an object would require after the assignemnt to a property. maybe this is actually a question of using well known structures, like object vs new (not really anymore) structure, like maps. – Nina Scholz Jan 28 '20 at 10:00
  • then the second part is to have an iterator of the map and get a custom formatted result set with `Array.from` and an own mapper of this method. – Nina Scholz Jan 28 '20 at 10:02
  • @NinaScholz i know that `Map` is more performance in case frequent adding and deleting keys, are you sure that access to elements is more performance too with `Map`? – svltmccc Jan 28 '20 at 10:15
  • Can you tell a little more about `Map` and `reduce`? What is mean "`Map` works in `reduce` directly"? – svltmccc Jan 28 '20 at 10:19
  • i am not sure about this, but is is not slower. anyway the question is not about performance, but to get things done. and i did it this way, today. – Nina Scholz Jan 28 '20 at 10:20
  • `Map.prototype.set` returns itself, the instance of `Map`. that is the accumulator of [`Array#reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). please foolow the links. – Nina Scholz Jan 28 '20 at 10:22
  • Accumulator of `reduce` can be anything: number, string, Map or object, or any custom type. In any case, thanks for your answer. – svltmccc Jan 28 '20 at 10:26
1
const items = [
    { name: 'john', class: 'one', section: 'a' },
    { name: 'john2', class: 'one', section: 'b' },
    { name: 'john3', class: 'one', section: 'a' },
    { name: 'john4', class: 'two', section: 'a' },
    { name: 'john5', class: 'two', section: 'b' },
    { name: 'john6', class: 'two', section: 'b' },
    { name: 'john7', class: 'three', section: 'a' },
    { name: 'john8', class: 'four', section: 'a' },
    { name: 'john9', class: 'four', section: 'b' }
];

let newArray = items.reduce((main, curr) => {
    let index = main.findIndex(obj => Object.keys(obj).includes(curr.class + curr.section))
    if (index == -1) main.push({ [curr.class + curr.section]: [curr] })
    else main[index][curr.class + curr.section].push(curr)
    return main
}, [])
console.log(newArray)
Sai Jeevan Balla
  • 135
  • 3
  • 10
0

I would suggest to omit the outer array of your result, because for me it does not make sense to have it if you want to create a set of objects.

To solve your problem, just iterate over all items and check if your result object already has the required key. If not, create it, and then insert the current item into the corresponding array.

const items = [
  { name: 'john', class: 'one', section: 'a' },
  { name: 'john2', class: 'one', section: 'b' },
  { name: 'john3', class: 'one', section: 'a' },
  { name: 'john4', class: 'two', section: 'a' },
  { name: 'john5', class: 'two', section: 'b' },
  { name: 'john6', class: 'two', section: 'b' },
  { name: 'john7', class: 'three', section: 'a' },
  { name: 'john8', class: 'four', section: 'a' },
  { name: 'john9', class: 'four', section: 'b' }
];

let result = {};

items.forEach(item => {
  let key = `${item.class}${item.section.toUpperCase()}`;
  if(!result.hasOwnProperty(key)) {
    result[key] = [];
  }
  result[key].push(item);
});

console.info(result);
SparkFountain
  • 2,110
  • 15
  • 35
  • Why is this answer downvoted? In my opinion it provides the best explanation to a solution, compared to the other answers that just pasted a bunch of code. – SparkFountain Jan 28 '20 at 10:56
0

You can use reduce function like:

const items = [
  { name: 'john', class: 'one', section: 'a' },
  { name: 'john2', class: 'one', section: 'b' },
  { name: 'john3', class: 'one', section: 'a' },
  { name: 'john4', class: 'two', section: 'a' },
  { name: 'john5', class: 'two', section: 'b' },
  { name: 'john6', class: 'two', section: 'b' },
  { name: 'john7', class: 'three', section: 'a' },
  { name: 'john8', class: 'four', section: 'a' },
  { name: 'john9', class: 'four', section: 'b' }
];

const groupedItems = items.reduce((result, current) => {
  const key = current.class + current.section.toUpperCase();
  
  if (Array.isArray(result[key])) {
    result[key].push(current);
  } else {
    result[key] = [current];
  }
  
  return result;
}, {});

const expectedResult = Array.from(Object.keys(groupedItems), (elem) => ({[elem]: groupedItems[elem]}));

console.log(expectedResult);
svltmccc
  • 1,356
  • 9
  • 25
0

If you are expecting answer in plain javascript for better understanding without using in built functions.

var items = [
    { name: 'john', class: 'one', section: 'a' },
    { name: 'john2', class: 'one', section: 'b' },
    { name: 'john3', class: 'one', section: 'a' },
    { name: 'john4', class: 'two', section: 'a' },
    { name: 'john5', class: 'two', section: 'b' },
    { name: 'john6', class: 'two', section: 'b' },
    { name: 'john7', class: 'three', section: 'a' },
    { name: 'john8', class: 'four', section: 'a' },
    { name: 'john9', class: 'four', section: 'b' }
];
var resultArray =[];
for(var i=0;i<items.length;i++){
    if(resultArray[items[i]['class']+items[i]['section'].toUpperCase()]){
        resultArray[items[i]['class']+items[i]['section'].toUpperCase()].push(items[i]);
    } else {
        var a ="";
        a = items[i]['class']+items[i]['section'].toUpperCase();
        resultArray[a]=[];
        resultArray[a].push(items[i])
    }
}

console.log(resultArray);

enter image description here

HulkSapien
  • 157
  • 2
  • 9