Is it possible to sort a ES6 map object?

JavascriptEcmascript 6

Javascript Problem Overview


Is it possible to sort the entries of a es6 map object?

var map = new Map();
map.set('2-1', foo);
map.set('0-1', bar);

results in:

map.entries = {
    0: {"2-1", foo },
    1: {"0-1", bar }
}

Is it possible to sort the entries based on their keys?

map.entries = {
    0: {"0-1", bar },
    1: {"2-1", foo }
}

Javascript Solutions


Solution 1 - Javascript

According MDN documentation: > A Map object iterates its elements in insertion order.

You could do it this way:

var map = new Map();
map.set('2-1', "foo");
map.set('0-1', "bar");
map.set('3-1', "baz");

var mapAsc = new Map([...map.entries()].sort());

console.log(mapAsc)

Using .sort(), remember that the array is sorted according to each character's Unicode code point value, according to the string conversion of each element. So 2-1, 0-1, 3-1 will be sorted correctly.

Solution 2 - Javascript

Short answer

 new Map([...map].sort((a, b) => 
   // Some sort function comparing keys with a[0] b[0] or values with a[1] b[1]
 ))

If you're expecting strings: As normal for .sort you need to return -1 if lower and 0 if equal; for strings, the recommended way is using .localeCompare() which does this correctly and automatically handles awkward characters like ä where the position varies by user locale.

So here's a simple way to sort a map by string keys:

 new Map([...map].sort((a, b) => String(a[0]).localeCompare(b[0])))

...and by string values:

 new Map([...map].sort((a, b) => String(a[1]).localeCompare(b[1])))

These are type-safe in that they won't throw an error if they hit a non-string key or value. The String() at the start forces a to be a string (and is good for readability), and .localeCompare() itself forces its argument to be a string without hitting an error.


In detail with examples

tldr: ...map.entries() is redundant, just ...map is fine; and a lazy .sort() without passing a sort function risks weird edge case bugs caused by string coercion.

The .entries() in [...map.entries()] (suggested in many answers) is redundant, probably adding an extra iteration of the map unless the JS engine optimises that away for you.

In the simple test case, you can do what the question asks for with:

new Map([...map].sort())

...which, if the keys are all strings, compares squashed and coerced comma-joined key-value strings like '2-1,foo' and '0-1,[object Object]', returning a new Map with the new insertion order:

Note: if you see only {} in SO's console output, look in your real browser console

const map = new Map([
  ['2-1', 'foo'],
  ['0-1', { bar: 'bar' }],
  ['3-5', () => 'fuz'],
  ['3-2', [ 'baz' ]]
])

console.log(new Map([...map].sort()))

HOWEVER, it's not a good practice to rely on coercion and stringification like this. You can get surprises like:

const map = new Map([
  ['2', '3,buh?'],
  ['2,1', 'foo'],
  ['0,1', { bar: 'bar' }],
  ['3,5', () => 'fuz'],
  ['3,2', [ 'baz' ]],
])

// Compares '2,3,buh?' with '2,1,foo'
// Therefore sorts ['2', '3,buh?'] ******AFTER****** ['2,1', 'foo']
console.log('Buh?', new Map([...map].sort()))

// Let's see exactly what each iteration is using as its comparator
for (const iteration of map) {
  console.log(iteration.toString())
}

Bugs like this are really hard to debug - don't risk it!

If you want to sort on keys or values, it's best to access them explicitly with a[0] and b[0] in the sort function, like above; or with array destructuring in the function arguments:

const map = new Map([
  ['2,1', 'this is overwritten'],
  ['2,1', '0,1'],
  ['0,1', '2,1'],
  ['2,2', '3,5'],
  ['3,5', '2,1'],
  ['2', ',9,9']
])

// Examples using array destructuring. We're saying 'keys' and 'values'
// in the function names so it's clear and readable what the intent is. 
const sortStringKeys = ([a], [b]) => String(a).localeCompare(b)
const sortStringValues = ([,a], [,b]) => String(a).localeCompare(b)

console.log('By keys:', new Map([...map].sort(sortStringKeys)))
console.log('By values:', new Map([...map].sort(sortStringValues)))

And if you need a different comparison than alphabetical order of strings, don't forget to always make sure you return -1 and 1 for before and after, not false or 0 as with raw a[0] > b[0] because that is treated as equals.

Solution 3 - Javascript

Convert Map to an array using Array.from, sort array, convert back to Map, e.g.

new Map(
  Array
    .from(eventsByDate)
    .sort((a, b) => {
      // a[0], b[0] is the key of the map
      return a[0] - b[0];
    })
)

Solution 4 - Javascript

The idea is to extract the keys of your map into an array. Sort this array. Then iterate over this sorted array, get its value pair from the unsorted map and put them into a new map. The new map will be in sorted order. The code below is it's implementation:

var unsortedMap = new Map();
unsortedMap.set('2-1', 'foo');
unsortedMap.set('0-1', 'bar');

// Initialize your keys array
var keys = [];
// Initialize your sorted maps object
var sortedMap = new Map();

// Put keys in Array
unsortedMap.forEach(function callback(value, key, map) {
    keys.push(key);
});

// Sort keys array and go through them to put in and put them in sorted map
keys.sort().map(function(key) {
    sortedMap.set(key, unsortedMap.get(key));
});

// View your sorted map
console.log(sortedMap);

Solution 5 - Javascript

You can convert to an array and call array soring methods on it:

[...map].sort(/* etc */);

Solution 6 - Javascript

Unfortunately, not really implemented in ES6. You have this feature with OrderedMap.sort() from ImmutableJS or _.sortBy() from Lodash.

Solution 7 - Javascript

One way is to get the entries array, sort it, and then create a new Map with the sorted array:

let ar = [...myMap.entries()];
sortedArray = ar.sort();
sortedMap = new Map(sortedArray);

But if you don't want to create a new object, but to work on the same one, you can do something like this:

// Get an array of the keys and sort them
let keys = [...myMap.keys()];
sortedKeys = keys.sort();

sortedKeys.forEach((key)=>{
  // Delete the element and set it again at the end
  const value = this.get(key);
  this.delete(key);
  this.set(key,value);
})

Solution 8 - Javascript

The snippet below sorts given map by its keys and maps the keys to key-value objects again. I used localeCompare function since my map was string->string object map.

var hash = {'x': 'xx', 't': 'tt', 'y': 'yy'};
Object.keys(hash).sort((a, b) => a.localeCompare(b)).map(function (i) {
            var o = {};
            o[i] = hash[i];
            return o;
        });

result: [{t:'tt'}, {x:'xx'}, {y: 'yy'}];

Solution 9 - Javascript

2 hours spent to get into details.

Note that the answer for question is already given at https://stackoverflow.com/a/31159284/984471

However, the question has keys that are not usual ones,
A clear & general example with explanation, is below that provides some more clarity:

.

let m1 = new Map();

m1.set(6,1); // key 6 is number and type is preserved (can be strings too)
m1.set(10,1);
m1.set(100,1);
m1.set(1,1);
console.log(m1);

// "string" sorted (even if keys are numbers) - default behaviour
let m2 = new Map( [...m1].sort() );
//      ...is destructuring into individual elements
//      then [] will catch elements in an array
//      then sort() sorts the array
//      since Map can take array as parameter to its constructor, a new Map is created
console.log('m2', m2);

// number sorted
let m3 = new Map([...m1].sort((a, b) => {
  if (a[0] > b[0]) return 1;
  if (a[0] == b[0]) return 0;
  if (a[0] < b[0]) return -1;
}));
console.log('m3', m3);

// Output
//    Map { 6 => 1, 10 => 1, 100 => 1, 1 => 1 }
// m2 Map { 1 => 1, 10 => 1, 100 => 1, 6 => 1 }
//           Note:  1,10,100,6  sorted as strings, default.
//           Note:  if the keys were string the sort behavior will be same as this
// m3 Map { 1 => 1, 6 => 1, 10 => 1, 100 => 1 }
//           Note:  1,6,10,100  sorted as number, looks correct for number keys

Hope that helps.

Solution 10 - Javascript

I would suggest to use a custom iterator for your map object to achieve a sorted access, like so:

map[Symbol.iterator] = function* () {
    yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}

Using an iterator has the advantage of that it has only to be declared once. After adding/deleting entries in the map, a new for-loop over the map would automatically reflect this changes using an iterator. Sorted copies as shown in most of the above answers would not as they only reflect the map's state at exactly one point in time.

Here's the complete working example using your initial situation.

var map = new Map();
map.set('2-1', { name: 'foo' });
map.set('0-1', { name: 'bar' });

for (let [key, val] of map) {
    console.log(key + ' - ' + val.name);
}
// 2-1 - foo
// 1-0 - bar

map[Symbol.iterator] = function* () {
    yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
}

for (let [key, val] of map) {
    console.log(key + ' - ' + val.name);
}
// 1-0 - bar
// 2-1 - foo

map.set('2-0', { name: 'zzz' });

for (let [key, val] of map) {
    console.log(key + ' - ' + val.name);
}
// 1-0 - bar
// 2-0 - zzz
// 2-1 - foo

Regards.

Solution 11 - Javascript

Perhaps a more realistic example about not sorting a Map object but preparing the sorting up front before doing the Map. The syntax gets actually pretty compact if you do it like this. You can apply the sorting before the map function like this, with a sort function before map (Example from a React app I am working on using JSX syntax)

Mark that I here define a sorting function inside using an arrow function that returns -1 if it is smaller and 0 otherwise sorted on a property of the Javascript objects in the array I get from an API.

report.ProcedureCodes.sort((a, b) => a.NumericalOrder < b.NumericalOrder ? -1 : 0).map((item, i) =>
						<TableRow key={i}>

							<TableCell>{item.Code}</TableCell>
							<TableCell>{item.Text}</TableCell>
							{/* <TableCell>{item.NumericalOrder}</TableCell> */}
						</TableRow>
					)

Solution 12 - Javascript

As far as I see it's currently not possible to sort a Map properly.

The other solutions where the Map is converted into an array and sorted this way have the following bug:

var a = new Map([[1, 2], [3,4]])
console.log(a);    // a = Map(2) {1 => 2, 3 => 4}

var b = a;
console.log(b);    // b = Map(2) {1 => 2, 3 => 4}

a = new Map();     // this is when the sorting happens
console.log(a, b); // a = Map(0) {}     b = Map(2) {1 => 2, 3 => 4}

The sorting creates a new object and all other pointers to the unsorted object get broken.

Solution 13 - Javascript

Slight variation - I didn't have spread syntax and I wanted to work on an object instead of a Map.

Object.fromEntries(Object.entries(apis).sort())

Solution 14 - Javascript

Additionally to the correct answer above:

If the sorted Map is just needed in an angular template it can be easily sorted using the KeyValuePipe which has a default sort included:

<div *ngFor="let item of map | keyvalue">
 {{ item.key }}: {{ item.value }}
</div>

It sorts by number or string alphabetically. See https://angular.io/api/common/KeyValuePipe

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIvan BacherView Question on Stackoverflow
Solution 1 - JavascriptWalter Chapilliquen - wZVanGView Answer on Stackoverflow
Solution 2 - Javascriptuser56reinstatemonica8View Answer on Stackoverflow
Solution 3 - JavascriptGajusView Answer on Stackoverflow
Solution 4 - JavascriptMikematicView Answer on Stackoverflow
Solution 5 - JavascriptwesbosView Answer on Stackoverflow
Solution 6 - JavascriptdevsideView Answer on Stackoverflow
Solution 7 - JavascriptMoshe KohaviView Answer on Stackoverflow
Solution 8 - JavascriptAbdullah GürsuView Answer on Stackoverflow
Solution 9 - JavascriptManohar Reddy PoreddyView Answer on Stackoverflow
Solution 10 - JavascriptSebastianView Answer on Stackoverflow
Solution 11 - JavascriptTore AurstadView Answer on Stackoverflow
Solution 12 - JavascriptLachoTomovView Answer on Stackoverflow
Solution 13 - JavascriptNick GrealyView Answer on Stackoverflow
Solution 14 - JavascriptC. GäkingView Answer on Stackoverflow