Is it possible to sort a ES6 map object?
Asked Answered
A

17

178

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 }
}
Appellation answered 1/7, 2015 at 10:34 Comment(3)
Maps are inherently not ordered (except the are iterated insertion order, which requires sorting and then [re-]adding).Tetreault
Sort the entries when you iterate over the map (ie. turn it into an array)Immensity
map = new Map([...map].sort()) OR map = new Map([...map].sort((a,b)=>a-b))Allowable
G
218

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.

Glimpse answered 1/7, 2015 at 10:54 Comment(12)
you can shorten that function(a,b) stuffs to: var mapAsc = new Map([...map.entries()].sort((a,b) => a[0] > b[0])); using arrow function (lambda)Awad
In fact, it acutally lexically compares 2-1,foo to 0-1,bar and 3-1,bazMercaptopurine
@JimmyChandra: Don't use (a,b) => a[0] > b[0]!Mercaptopurine
@Mercaptopurine yes, the correct usage is: var mapAscByKey = new Map([...map.entries()].sort((a,b) => a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0));. As I said taking the order according to the value converted into a string. In this case if the keys are unique there will be no problem to order it that way.Glimpse
I'm using Babel with es2015-loose and stage-1. I had to also use the babel-plugin-transform-es2015-spread in order to get this working.Imprudent
The ... dots are important otherwise you are trying to sort a MapIteratorVerminous
The MDN doc actually states: "It should be noted that a Map which is a map of an object, especially a dictionary of dictionaries, will only map to the object's insertion order—which is random and not ordered." - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Carrousel
If the map keys are numbers, you will get invalid results with numbers like 1e-9 being put after 100 in sorted map. Code that works with numbers: new Map([...map.entries()].sort((e1, e2) => e1[0] - e2[0]))Bashuk
Unfortunately I get a GulpUglifyError because of the .... Is there another way?Wee
@Wee a Gulp error is not a JS error, why a vote down? :(. Maybe in the Gulp documentation you will find a topic related about the spread operatorGlimpse
This only logs {} to the console for me when I run the Stack Snippet (Chrome).Induct
If you're going to be sorting all the time, may as well stick to an Array instead of converting from a Map to an Array to a Map. That has CPU cost.Software
S
155

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.

Sackey answered 9/7, 2018 at 9:30 Comment(3)
What an answer, we gotta fix S.O. discovery mechanisms. Something this good shouldn't be lost down here.Lodovico
Just wanted to add for whom wants to sort this kind of map: Map<String,String[]> console.log('By keys:', new Map([...your_map].sort(([a]:[string, string[]], [b]:[string, string[]]) => String(a[0]).localeCompare(b[0])))) console.log('By values:', new Map([...your_map].sort(([,a]:[string, string[]], [,b]:[string, string[]]) => String(a[0]).localeCompare(b[0]))))Desireah
@Lodovico yeah that happens when a good answer shows up after the accepted answer, no way to force the asker to reconsider which answer is rightCurbing
T
39

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];
    })
)
Truncheon answered 22/5, 2017 at 9:15 Comment(2)
[...map.values()].sort() didn't work for me, but Array.from(map.values()).sort() didHerminahermine
This actually helped me, without Array.from, Typescript gave me a compile error.Break
D
8

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.

Diaeresis answered 6/6, 2021 at 20:52 Comment(1)
Interesting answer. It's worth mentioning that the map will only sort its output on iterations that are directly on the map itself, like [...map] and for (… of map). It won't sort iterations of map.keys(), map.entries() or map.values(). For example, someone might expect [...map.values()] to have the same output as [...map].map(([,value]) => value), but with this applied, the former would have insertion order, while the latter would apply this sort.Sackey
P
6

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

[...map].sort(/* etc */);
Phlox answered 8/4, 2016 at 19:24 Comment(2)
And what? You get an array not a map or objectSpacial
and you lose the keyEstas
S
6

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);
Smoothtongued answered 16/1, 2017 at 22:50 Comment(1)
The unsorted keys can be determined simply by using unsortedMap.keys(). Also keys.sort().map... should be keys.sort().forEach....Ritornello
D
4

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

Diatomaceous answered 15/11, 2015 at 12:5 Comment(0)
N
4

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);
})
Nudicaul answered 22/8, 2018 at 7:7 Comment(0)
A
4

2 hours spent to get into details.

Note that the answer for question is already given at https://mcmap.net/q/141773/-is-it-possible-to-sort-a-es6-map-object

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.

Allowable answered 29/2, 2020 at 16:9 Comment(2)
how to sort by values using the same style of approach?Zenaidazenana
please share an example for better understandingAllowable
D
2

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'}];

Decadent answered 30/10, 2017 at 13:28 Comment(0)
B
1

If the question were: Can an ES6 Map be sorted by a custom comparator? The concise answer is: No, in alignment with previous responses.

However, if the underlying question behind this inquiry were: Are there sorted Maps in JavaScript? the answer is: Yes, there are now. Explore the @ut8pia/classifier library.

Supposing:

const entries = [['b', 0], ['a', 1]]

For sorting entries by key:

const
    classifier = new Classifier()
        .letAll(entries);

assert.deepEqual(classifier.toArray(), [['a', 1], ['b', 0]])

Yet, the Classifier extends beyond a typical Map. Entries can possess varying lengths, and any component of an entry can serve as a key. Additionally, multiple entries can share the same key.

Given that every entry component can be used as a key, sorting by value and accessing entries by key become feasible:

const 
    classifier = new Classifier()
        .letAll(entries.map(([key, value]) => [value, key])),
    entriesByKey = classifier.class(undefined, 'a'),
    entriesByValue = classifier.class(0);
  
assert.deepEqual(classifier.toArray(), [[0, 'b'], [1, 'a']]);
assert.deepEqual(entriesByKey.toArray(), [[1, 'a']]);
assert.deepEqual(entriesByValue.toArray(), [[0, 'b']])

Each entry [key, value] is inverted in [value, key] so that, within the Classifier, the entries get sorted by value. The Classifier sorts in ascending order by default, but it allows the use of any custom comparator as well.

The method classifier.class(...keys) returns an iteration of all the entries matching the given, also scattered, keys. Such an iteration offers methods to retrieve the first entry of the class, filter the entries, transform them, compose them with further iterations ...

Budge answered 3/12, 2023 at 6:35 Comment(0)
A
0

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>
                    )
Aliber answered 14/11, 2018 at 17:54 Comment(0)
O
0

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.

Odontoblast answered 21/11, 2019 at 11:17 Comment(0)
T
0

Here is function that sort Map() by decreasing.

function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
        const key = keyGetter(item);
        const collection = map.get(key);
        if (!collection) {
            map.set(key, [item]);
        } else {
            collection.push(item);
        }
    });

    const sortedMap = new Map();
    [...map].sort((a, b) => b[1].length - a[1].length).forEach(e => sortedMap.set(e[0], e[1]));

    return sortedMap;
}
const test = groupBy(array, item => item.fieldName);  
Thionate answered 22/6, 2022 at 20:21 Comment(0)
B
0

Based off user56's answer here is a simplified version:

export const sorted_map = <Key, Type>(map: Map<Key, Type>): Map<Key, Type> =>
    new Map([...map].sort(([a], [b]) =>
        (typeof a == 'number') ? a - (b as number) : String(a).localeCompare(String(b))
    ))
Bamboozle answered 4/4 at 6:44 Comment(0)
K
-1

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())
Kauffman answered 10/12, 2020 at 3:33 Comment(0)
V
-1
  1. Yes, you can sort an ES6 Map object, but it requires a few steps as Map itself does not have a built-in sort function. The process involves converting the Map to an array, sorting the array, and then creating a new Map from the sorted array. Here's how you can do it:

Convert the Map to an Array: Use the Array.from() method or the spread operator ... to convert the Map entries to an array. Each element of the array will be a key-value pair.

const myMap = new Map();
myMap.set('c', 3);
myMap.set('a', 1);
myMap.set('b', 2);

const mapArray = Array.from(myMap);
// or using spread operator
// const mapArray = [...myMap];

Sort the Array: Use the Array.prototype.sort() method to sort the array. You can define a custom sorting function if needed.

// Sorting by value
const sortedArray = mapArray.sort((a, b) => a[1] - b[1]);

Create a New Map from the Sorted Array: Use the sorted array to create a new Map.

const sortedMap = new Map(sortedArray);
console.log(sortedMap); // Map { 'a' => 1, 'b' => 2, 'c' => 3 }

In this example, the Map is sorted by its values. If you want to sort by keys, you can adjust the sorting function accordingly. Remember that the sorting function can be customized to sort the Map entries based on your specific needs.

While an ES6 Map cannot be sorted directly, this method effectively achieves a sorted Map by using arrays and their sorting capabilities. This approach is particularly useful when you need to order the entries of a Map in a specific manner.

SEPERATELY

  1. Sorting the entries of an ES6 Map object based on their keys by following these steps:

Convert the Map to an Array: First, convert the Map entries into an array. This can be done using Array.from(map) or [...map].

Sort the Array: Then, sort the array based on the keys. You can do this using the sort() method of the array.

Create a New Map from the Sorted Array: Finally, create a new Map from the sorted array.

Here's how you can implement this:

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

// Convert the Map to an Array and sort it based on the keys
var sortedArray = Array.from(map).sort((a, b) => a[0].localeCompare(b[0]));

// Create a new Map from the Sorted Array
var sortedMap = new Map(sortedArray);

// Displaying the Sorted Map
sortedMap.forEach((value, key) => {
    console.log(key + ' = ' + value);
});

This script will sort the Map entries based on their keys. In the comparison function (a, b) => a[0].localeCompare(b[0]), a[0] and b[0] represent the keys of the map entries. The localeCompare method is used to compare the keys, which works well for string keys as in your example.

If the keys were numbers or a mix of different types, you might need a different comparison logic to sort them correctly. For numeric keys, you would use a comparison like (a, b) => a[0] - b[0].

After sorting, the sortedMap will have its entries ordered as per the sorted keys.

Vaucluse answered 31/12, 2023 at 11:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.