skip to Main Content

I have an array of objects. From this array I want to create a sorted map by the key foo. The values shall be arrays with the corresponding objects, sorted by the key bar.

// input data
const arr = [{'foo': 42, 'bar': 7}, {'foo': 1, 'bar': 2}, {'foo': 1, 'bar': 1}];

// insert magic here

// This is the result (not sure how to correctly display a map)
{
  1 -> [{'foo': 1, 'bar': 1}, {'foo': 1, 'bar': 2}],
  42 -> [{'foo': 42, 'bar': 7}]
}

It’s not too difficult to find a working solution. But I’m looking for a simple and fast solution, preferrably a one-liner. How can this be done?

My current endeavors

Currently I’m stuck with this line of code. It creates a map with arrays. Is there a way to at least include the sorting functionality for the keys in there? I could then do a second step and sort the arrays separately.

const result = new Map(arr.map(i => [i.foo, i]));

2

Answers


  1. Step by step:

    const arr = [{'foo': 42, 'bar': 7}, {'foo': 1, 'bar': 2}, {'foo': 1, 'bar': 1}];
    
    // Map by foo:
    
    const result = arr.reduce((acc, cur) => {
      if (!acc[cur.foo]) acc[cur.foo] = []
      
      acc[cur.foo].push(cur)
      
      return acc;
    }, {})
    
    // Sort by bar:
    
    Object.values(result).forEach((arr) => arr.sort((a, b) => a.bar - b.bar))
    
    // Final result:
    
    console.log(result)

    You can also make it a one-liner and still use built-in methods, but performance would be worse as this is creating intermediate objects and arrays and sorting the arrays continuously (which is totally unnecessary):

    const arr = [{'foo': 42, 'bar': 7}, {'foo': 1, 'bar': 2}, {'foo': 1, 'bar': 1}];
    const result = arr.reduce((acc, cur) => ({ ...acc, [cur.foo]: [...(acc[cur.foo] || []), cur].sort((a, b) => a.bar - b.bar) }), {})
    
    console.log(result)

    If you need the keys of the map to be sorted, I recommend you use an actual Map instead of using a regular Object. This is what the MDN Map docs say about this:

    You might also want to take a look at this other question (pay attention to the date of the answers): Does ES6 introduce a well-defined order of enumeration for object properties?

    Although the keys of an ordinary Object are ordered now, this was not always the case, and the order is complex. As a result, it’s best not to rely on property order.

    The keys in Map are ordered in a simple, straightforward way: A Map object iterates entries, keys, and values in the order of entry insertion.

    Therefore, you just need to sort the array before using reduce, and replace the initialValue param with new Map():

    const arr = [{'foo': 42, 'bar': 7}, {'foo': 1, 'bar': 2}, {'foo': 1, 'bar': 1}];
    
    // Map by foo:
    
    const result = arr.sort((a, b) => a.foo - b.foo).reduce((acc, cur) => {
      const arr = acc.get(cur.foo)
      
      acc.set(cur.foo, arr ? [...arr, cur] : [cur])
      
      return acc;
    }, new Map())
    
    // Sort by bar:
    
    for (const [_, arr] of result) {
      arr.sort((a, b) => a.bar - b.bar)
    }
    
    // Final result:
    
    console.log(Object.fromEntries(result.entries()))

    Just keep in mind the keys in the Map are ordered by insertion order, so if you have keys A, B and C and add D later, D will be the last one (4th) not the first.

    Login or Signup to reply.
  2. A fast one-liner:

    const arr = [{'foo': 42, 'bar': 7}, {'foo': 1, 'bar': 2}, {'foo': 1, 'bar': 1}];
    
    const result = arr.toSorted((a, b) => a.bar - b.bar).reduce((r, item) => (r[item.foo] ??= []).push(item) && r, {});
    
    console.log(result)
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search