Est-il possible de trier un objet de carte ES6?

98

Est-il possible de trier les entrées d'un objet de carte es6?

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

résulte en:

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

Est-il possible de trier les entrées en fonction de leurs clés?

map.entries = {
    0: {"0-1", bar },
    1: {"2-1", foo }
}
Ivan Bacher
la source
4
Les cartes ne sont pas ordonnées par nature (sauf l'ordre d'insertion itéré, qui nécessite un tri puis une [ré] ajout).
user2864740
1
Triez les entrées lorsque vous parcourez la carte (c.-à-d. Transformez-la en tableau)
Halcyon

Réponses:

140

Selon la documentation MDN:

Un objet Map itère ses éléments dans l'ordre d'insertion.

Vous pouvez le faire de cette façon:

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)

En utilisant .sort(), rappelez-vous que le tableau est trié en fonction de la valeur du point de code Unicode de chaque caractère, en fonction de la conversion de chaîne de chaque élément. Alors 2-1, 0-1, 3-1sera trié correctement.

Walter Chapilliquen - wZVanG
la source
7
vous pouvez raccourcir cette fonction (a, b) à: en var mapAsc = new Map([...map.entries()].sort((a,b) => a[0] > b[0]));utilisant la fonction de flèche (lambda)
Jimmy Chandra
2
En fait, il se compare lexicalement 2-1,fooà 0-1,baret3-1,baz
Bergi
19
@JimmyChandra: N'utilisez pas (a,b) => a[0] > b[0]!
Bergi
3
Les ...points sont importants sinon vous essayez de trier un MapIterator
muttonUp
2
Si les clés de la carte sont des nombres, vous obtiendrez des résultats invalides avec des nombres comme 1e-9être mis après 100dans la carte triée. Code qui fonctionne avec les nombres:new Map([...map.entries()].sort((e1, e2) => e1[0] - e2[0]))
cdalxndr
58

Réponse courte

 new Map([...map].sort((a, b) => 
   // Some sort function comparing keys with a[0] b[0] or values with a[1] b[1]
   // Be sure to return -1 if lower and, if comparing values, return 0 if equal
 ))

Par exemple, en comparant des chaînes de valeurs, qui peuvent être égales, nous transmettons une fonction de tri qui accède à [1] et a une condition d'égalité qui renvoie 0:

 new Map([...map].sort((a, b) => (a[1] > b[1] && 1) || (a[1] === b[1] ? 0 : -1)))

En comparant des chaînes de clé, qui ne peuvent pas être égales (des clés de chaîne identiques se remplaceraient), nous pouvons ignorer la condition d'égalité. Cependant, nous devrions toujours retourner explicitement -1, car le retour d'un paresseux a[0] > b[0]donne incorrectement false (traité comme 0, c'est-à-dire égal) lorsque a[0] < b[0]:

 new Map([...map].sort((a, b) => a[0] > b[0] ? 1 : -1))

En détail avec des exemples

Le .entries()dans[...map.entries()] (suggéré dans de nombreuses réponses) est redondant, ajoutant probablement une itération supplémentaire de la carte à moins que le moteur JS ne l'optimise pour vous.

Dans le cas de test simple, vous pouvez faire ce que la question demande avec:

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

... qui, si les clés sont toutes des chaînes, compare les chaînes de valeur-clé jointes par des virgules écrasées et forcées comme '2-1,foo'et '0-1,[object Object]', en renvoyant une nouvelle carte avec le nouvel ordre d'insertion:

Remarque: si vous ne voyez que {}dans la sortie de la console de SO, regardez dans la console de votre navigateur réel

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

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

CEPENDANT , ce n'est pas une bonne pratique de s'appuyer sur la coercition et la stringification comme celle-ci. Vous pouvez avoir des surprises comme:

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())
}

Des bogues comme celui-ci sont vraiment difficiles à déboguer - ne le risquez pas!

Si vous souhaitez trier sur des clés ou des valeurs, il est préférable d'y accéder explicitement avec a[0]et b[0]dans la fonction de tri, comme ceci. Notez que nous devrions retourner -1et 1pour avant et après, pas falseou 0comme avec raw a[0] > b[0]car cela est traité comme égal:

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']
])

// For keys, we don't need an equals case, because identical keys overwrite 
const sortStringKeys = (a, b) => a[0] > b[0] ? 1 : -1 

// For values, we do need an equals case
const sortStringValues = (a, b) => (a[1] > b[1] && 1) || (a[1] === b[1] ? 0 : -1)

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

user56reinstatemonica8
la source
11
Quelle réponse, il faut corriger les mécanismes de découverte SO. Quelque chose d'aussi bon ne devrait pas être perdu ici.
serraosays
30

Convertir Mapen un tableau en utilisant Array.from, trier un tableau, reconvertir en Map, par exemple

new Map(
  Array
    .from(eventsByDate)
    .sort((a, b) => {
      // a[0], b[0] is the key of the map
      return a[0] - b[0];
    })
)
Gajus
la source
1
[...map.values()].sort()n'a pas fonctionné pour moi, mais a Array.from(map.values()).sort()fait
Rob Juurlink
1
Cela m'a aidé, sans Array.from, Typescript m'a donné une erreur de compilation.
icSapper
7

L'idée est d'extraire les clés de votre carte dans un tableau. Triez ce tableau. Ensuite, parcourez ce tableau trié, obtenez sa paire de valeurs à partir de la carte non triée et placez-les dans une nouvelle carte. La nouvelle carte sera triée. Le code ci-dessous est sa mise en œuvre:

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);
Mikematic
la source
2
Les clés non triées peuvent être déterminées simplement en utilisant unsortedMap.keys(). Devrait également l' keys.sort().map...être keys.sort().forEach....
faintsignal
5

Vous pouvez convertir en un tableau et y appeler des méthodes de recherche de tableau:

[...map].sort(/* etc */);
wesbos
la source
3
Et quoi? Vous obtenez un tableau pas une carte ou un objet
Vert
5
et vous perdez la clé
freins
3

Une façon est d'obtenir le tableau d'entrées, de le trier, puis de créer une nouvelle carte avec le tableau trié:

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

Mais si vous ne voulez pas créer un nouvel objet, mais travailler sur le même, vous pouvez faire quelque chose comme ceci:

// 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);
})
Moshe Estroti
la source
1

L'extrait ci-dessous trie la carte donnée par ses clés et mappe à nouveau les clés sur des objets clé-valeur. J'ai utilisé la fonction localeCompare car ma carte était une carte d'objet string-> string.

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;
        });

résultat: [{t:'tt'}, {x:'xx'}, {y: 'yy'}];

Abdullah Gürsu
la source
1

D'après ce que je vois, il n'est actuellement pas possible de trier correctement une carte.

Les autres solutions où la carte est convertie en un tableau et triée de cette façon ont le bogue suivant:

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}

Le tri crée un nouvel objet et tous les autres pointeurs vers l'objet non trié sont cassés.

Lacho Tomov
la source
1

2 heures passées à entrer dans les détails.

Notez que la réponse à la question est déjà donnée à https://stackoverflow.com/a/31159284/984471

Cependant, la question a des touches qui ne sont pas habituelles,
un exemple clair et général avec l' explication, est ci - dessous qui fournit un peu plus de clarté:

.

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

J'espère que ça t'as aidé.

Manohar Reddy Poreddy
la source
0

Peut-être un exemple plus réaliste de ne pas trier un objet Map, mais de préparer le tri avant de faire la Map. La syntaxe devient en fait assez compacte si vous le faites comme ça. Vous pouvez appliquer le tri avant la fonction de carte comme ceci, avec une fonction de tri avant la carte (exemple d'une application React sur laquelle je travaille en utilisant la syntaxe JSX)

Notez que je définis ici une fonction de tri à l'intérieur en utilisant une fonction de flèche qui renvoie -1 si elle est plus petite et 0 sinon triée sur une propriété des objets Javascript dans le tableau que j'obtiens d'une 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>
                    )
Tore Aurstad
la source
-2
let map = new Map();
map.set('2-1', "foo");
map.set('0-1', "bar");
map.set('3-1', "baz");
let mapAsc = new Map([...map.entries()].sort());
console.log(mapAsc);

// Map(3) {"0-1" => "bar", "2-1" => "foo", "3-1" => "baz"}
Apoorva Gupta
la source
3
Bien que cela puisse répondre à la question des auteurs, il manque des mots explicatifs et des liens vers la documentation. Les extraits de code bruts ne sont pas très utiles sans quelques phrases autour. Veuillez modifier votre réponse.
hellow
2
En outre, c'est une question vieille de 3 ans. Que pensez-vous que votre réponse offre que d'autres réponses n'ont pas déjà couvertes?
hellow