É 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 }
}
Ivan Bacher
fonte
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.

Walter Chapilliquen - wZVanG
fonte
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
21
@JimmyChandra: Não use (a,b) => a[0] > b[0]!
Bergi
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)))

user56reinstatemonica8
fonte
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];
    })
)
Gajus
fonte
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);
Mikematic
fonte
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 */);
wesbos
fonte
3
E o que? Você obtém uma matriz, não um mapa ou objeto
Verde
5
e você perde a chave
freios
4

Infelizmente, não é realmente implementado no ES6. Você tem esse recurso com OrderedMap.sort () de ImmutableJS ou _.sortBy () de Lodash.

Devside
fonte
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);
})
Moshe Estroti
fonte
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'}];

Abdullah Gürsu
fonte
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.

Tomov
fonte
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.

Manohar Reddy Poreddy
fonte
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>
                    )
Tore Aurstad
fonte
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())
Nick Grealy
fonte