É possível classificar um objeto de mapa ES6?


100

É possível classificar as entradas de um objeto de mapa es6?

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

resulta em:

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

É possível classificar as entradas com base em suas chaves?

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

4
Os mapas não são ordenados inerentemente (exceto que são ordem de inserção iterada, que requer classificação e então [re] adição).
— user2864740 01 de

1
Classifique as entradas ao iterar no mapa (ou seja, transformá-lo em uma matriz)
— Halcyon

Respostas:


144

De acordo com a documentação MDN:

Um objeto Map itera seus elementos na ordem de inserção.

Você poderia fazer desta forma:

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)

Usando .sort(), lembre-se de que a matriz é classificada de acordo com o valor do ponto de código Unicode de cada caractere, de acordo com a conversão da string de cada elemento. Então 2-1, 0-1, 3-1, será classificado corretamente.


7
você pode encurtar essa função (a, b) para: var mapAsc = new Map([...map.entries()].sort((a,b) => a[0] > b[0]));usando a função de seta (lambda)
— Jimmy Chandra

2
Na verdade, ele se compara lexicamente 2-1,fooa 0-1,bare3-1,baz
— Bergi 01 de


4
Os ...pontos são importantes, caso contrário, você está tentando classificar um MapIterator
— muttonUp

2
Se as chaves do mapa forem números, você obterá resultados inválidos com números, como os que 1e-9foram colocados depois 100no mapa classificado. Código que funciona com números:new Map([...map.entries()].sort((e1, e2) => e1[0] - e2[0]))
— cdalxndr

62

Resposta curta

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

Por exemplo, comparando strings de valor, que podem ser iguais, passamos uma função de classificação que acessa [1] e tem uma condição de igual que retorna 0:

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

Comparando strings de chave, que não podem ser iguais (chaves de string idênticas se sobrescreveriam), podemos pular a condição de igual. No entanto, ainda devemos retornar explicitamente -1, porque retornar um preguiçoso a[0] > b[0]incorretamente resulta em falso (tratado como 0, ou seja, igual) quando a[0] < b[0]:

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

Em detalhes com exemplos

O .entries()in [...map.entries()](sugerido em muitas respostas) é redundante, provavelmente adicionando uma iteração extra do mapa, a menos que o mecanismo JS otimize isso para você.

No caso de teste simples, você pode fazer o que a pergunta pede com:

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

... que, se as chaves forem todas strings, compara strings de valores-chave unidos por vírgulas e comprimidas como '2-1,foo'e '0-1,[object Object]', retornando um novo Mapa com o novo pedido de inserção:

Nota: se você vir apenas {}na saída do console do SO, olhe no console do seu navegador real

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

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

NO ENTANTO , não é uma boa prática confiar em coerção e estringificação como esta. Você pode obter surpresas como:

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 como esse são realmente difíceis de depurar - não se arrisque!

Se você deseja classificar as chaves ou valores, é melhor acessá-los explicitamente com a[0]e b[0]na função de classificação, como este. Observe que devemos retornar -1e 1para antes e depois, não falseou 0como raw a[0] > b[0]porque isso é tratado como igual:

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


11
Que resposta, temos que consertar os mecanismos de descoberta do SO. Algo tão bom não deve ser perdido aqui.
— serraosays

32

Converter Mappara um array usando Array.from, classificar array, converter de volta para Map, por exemplo

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

1
[...map.values()].sort()não funcionou para mim, mas Array.from(map.values()).sort()funcionou
— Rob Juurlink

2
Isso realmente me ajudou, sem Array.from, Typescript me deu um erro de compilação.
— icSapper

7

A ideia é extrair as chaves do seu mapa em um array. Classifique esta matriz. Em seguida, itere sobre essa matriz classificada, obtenha seu par de valores do mapa não classificado e coloque-os em um novo mapa. O novo mapa estará em ordem. O código abaixo é sua implementação:

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

2
As chaves não classificadas podem ser determinadas simplesmente usando unsortedMap.keys(). Também keys.sort().map...deveria ser keys.sort().forEach....
— faintsignal

6

Você pode converter em uma matriz e chamar métodos de soring de matriz nela:

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

3
E o que? Você obtém uma matriz, não um mapa ou objeto
— Verde

5
e você perde a chave
— freios


3

Uma maneira é obter a matriz de entradas, classificá-la e, em seguida, criar um novo Mapa com a matriz classificada:

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

Mas se você não quiser criar um novo objeto, mas trabalhar no mesmo, você pode fazer algo assim:

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

1

O fragmento abaixo classifica o mapa fornecido por suas chaves e mapeia as chaves para objetos de valor-chave novamente. Usei a função localeCompare, pois meu mapa era 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;
        });

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


1

Pelo que vejo, atualmente não é possível classificar um mapa corretamente.

As outras soluções em que o Mapa é convertido em uma matriz e classificado dessa forma apresentam o seguinte 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}

A classificação cria um novo objeto e todos os outros ponteiros para o objeto não classificado são quebrados.


1

2 horas gastas para entrar em detalhes.

Observe que a resposta para a pergunta já foi fornecida em https://stackoverflow.com/a/31159284/984471

No entanto, a pergunta tem chaves que não são usuais.
Um exemplo claro e geral com explicação está abaixo, que fornece mais clareza:

.

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

Espero que ajude.


0

Talvez um exemplo mais realista sobre não classificar um objeto de mapa, mas preparar a classificação antes de fazer o mapa. A sintaxe fica realmente muito compacta se você fizer assim. Você pode aplicar a classificação antes da função de mapa desta forma, com uma função de classificação antes do mapa (exemplo de um aplicativo React no qual estou trabalhando usando a sintaxe JSX)

Marque que eu aqui defino uma função de classificação interna usando uma função de seta que retorna -1 se for menor e 0, caso contrário, classificada em uma propriedade dos objetos Javascript na matriz que obtenho de uma 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>
                    )

0

Ligeira variação - eu não espalhei a sintaxe e queria trabalhar em um em objectvez de em um Map.

Object.fromEntries(Object.entries(apis).sort())
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.