1

I have an array of objects such as this:

var arr = [
    {name:"item1", category:0},
    {name:"item2", category:3},
    {name:"item3", category:1},
    {name:"item4", category:1},
]

I want to produce a multi-dimensional array based on category values such as this: (We can assume fixed length, i.e. no category 4)

var arr2 = [
    [{name:"item1", category:0}],
    [{name:"item3", category:1},{name:"item4", category:1}],
    [],
    [{name:"item2", category:3}]
]

My current solution is this:

var arr2 = [[],[],[],[]];
arr.forEach(x => {
    arr2[x.category].push(x);
});

But I'm looking for a more JavaScript-y way (with map, filter etc) and preferrably a one-liner.

Thanks for any help!

wololoo
  • 147
  • 1
  • 1
  • 9
  • It would help if the downvoter explained the reason :) I searched all the resources I can.. Couldn't find an example of this. – wololoo Aug 01 '23 at 10:34
  • The empty sub-array for the missing category 2 will not manifest by itself, so you will have to initialize your result array in one form or another anyway. – CBroe Aug 01 '23 at 10:35
  • @CBroe I guess so. So my solution is as short as it gets? I could still use a solution like `arr2 = [[],[],[],[]].map(something);` – wololoo Aug 01 '23 at 10:36
  • 2
    Small note: one-liners generally make for difficult-to-read and harder-to-maintain code. – Andy Aug 01 '23 at 10:37
  • 1
    @Andy you are right but this can be considered an atomic operation, I repeat it in many functions so I still wanna reduce the number of lines. Thanks for your warning though, appreciated! – wololoo Aug 01 '23 at 10:38
  • 1
    There are lots of answers on here about "grouping" which [you may like](https://stackoverflow.com/questions/74820892/javascript-grouping) to [search for](https://stackoverflow.com/questions/40774697/how-can-i-group-an-array-of-objects-by-key). Here's [some code using `reduce`](https://jsfiddle.net/vasndth8/). – Andy Aug 01 '23 at 10:44
  • 1
    _"I could still use a solution like `arr2 = [[],[],[],[]].map(something);`"_ - but you would only be iterating over those four empty arrays with that ... So `something` would then have to access the original array itself, and filter out the relevant items, based on category = index of current arr2 item. Doesn't sound that much more elegant to me. – CBroe Aug 01 '23 at 10:46
  • 1
    _"but this can be considered an atomic operation, I repeat it in many functions"_ - so make that itself into a function then ...? _That_ would be your "one-liner" to use in those places then, and with a properly chosen function name, that would also be "understandable" in those places, without going and looking up the actual code behind it ... – CBroe Aug 01 '23 at 10:47
  • @CBroe yes I think thats what I will -and should- do. Thanks for all the help! – wololoo Aug 01 '23 at 10:48

2 Answers2

1

you can try this code

var arr = [
    {name:"item1", category:0},
    {name:"item2", category:3},
    {name:"item3", category:1},
    {name:"item4", category:1},
]

let hashMap = new Map()

arr.forEach(e => {
    if(hashMap.get(e.category) === undefined){
        hashMap.set(e.category, [e])
    }
    else{
        // because array will refered by address
        hashMap.get(e.category).push(e)
    }
});

console.log(hashMap)

console.log(hashMap.values())

console.log( Array.from(hashMap.values()) )

Output

Map(3) {
  0 => [ { name: 'item1', category: 0 } ],
  3 => [ { name: 'item2', category: 3 } ],
  1 => [ { name: 'item3', category: 1 }, { name: 'item4', category: 1 } ]
}
--------------
[Map Iterator] {
  [ { name: 'item1', category: 0 } ],
  [ { name: 'item2', category: 3 } ],
  [ { name: 'item3', category: 1 }, { name: 'item4', category: 1 } ]
}
--------------
[
  [ { name: 'item1', category: 0 } ],
  [ { name: 'item2', category: 3 } ],
  [ { name: 'item3', category: 1 }, { name: 'item4', category: 1 } ]
]
1

Your one-liner: create an array with Array.from() with length of maximum category ID plus 1, and map elements with filtered arrays by the index category :

var arr = [
    {name:"item1", category:0},
    {name:"item2", category:3},
    {name:"item3", category:1},
    {name:"item4", category:1},
]

const result = Array.from({length: Math.max(...arr.map(item => item.category)) + 1}, (_, idx) => arr.filter(({category}) => category === idx));

console.log(result);
Alexander Nenashev
  • 8,775
  • 2
  • 6
  • 17
  • Thank you! For future reference, I modified it for my case (fixed length): `Array.from({length: 4}, (_, idx) => arr.filter(({category}) => category === idx));` – wololoo Aug 01 '23 at 10:51