¿Es posible ordenar un objeto de mapa ES6?

98

¿Es posible ordenar las entradas de un objeto de mapa es6?

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

resulta en:

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

¿Es posible ordenar las entradas según sus claves?

map.entries = {
    0: {"0-1", bar },
    1: {"2-1", foo }
}
Ivan Bacher
fuente
4
Los mapas no están intrínsecamente ordenados (excepto el orden de inserción iterado, que requiere ordenar y luego [re] agregar).
user2864740
1
Ordene las entradas cuando itere sobre el mapa (es decir, conviértalo en una matriz)
Halcyon

Respuestas:

140

Según la documentación de MDN:

Un objeto Map itera sus elementos en orden de inserción.

Puedes hacerlo de esta manera:

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)

Al usar .sort(), recuerde que la matriz se ordena de acuerdo con el valor del punto de código Unicode de cada carácter, de acuerdo con la conversión de cadena de cada elemento. Por 2-1, 0-1, 3-1lo que se ordenará correctamente.

Walter Chapilliquen - wZVanG
fuente
7
puede acortar esa función (a, b) rellena a: var mapAsc = new Map([...map.entries()].sort((a,b) => a[0] > b[0]));usando la función de flecha (lambda)
Jimmy Chandra
2
De hecho, realmente se compara léxicamente 2-1,foocon 0-1,bary3-1,baz
Bergi
19
@JimmyChandra: ¡ No lo uses (a,b) => a[0] > b[0]!
Bergi
3
Los ...puntos son importantes, de lo contrario, está intentando ordenar un MapIterator
muttonUp
2
Si las claves del mapa son números, obtendrá resultados no válidos con números como si 1e-9se colocan después 100en el mapa ordenado. Código que funciona con números:new Map([...map.entries()].sort((e1, e2) => e1[0] - e2[0]))
cdalxndr
59

Respuesta corta

 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 ejemplo, al comparar cadenas de valor, que pueden ser iguales, pasamos una función de ordenación que accede a [1] y tiene una condición igual que devuelve 0:

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

Comparando cadenas de claves, que no pueden ser iguales (claves de cadenas idénticas se sobrescriben entre sí), podemos omitir la condición de iguales. Sin embargo, deberíamos devolver explícitamente -1, porque devolver un perezoso a[0] > b[0]incorrectamente da falso (tratado como 0, es decir, igual) cuando a[0] < b[0]:

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

En detalle con ejemplos

El .entries()in [...map.entries()](sugerido en muchas respuestas) es redundante, probablemente agregue una iteración adicional del mapa a menos que el motor JS lo optimice para usted.

En el caso de prueba simple, puede hacer lo que pide la pregunta con:

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

... que, si las claves son todas cadenas, compara cadenas de valor-clave unidas por comas comprimidas y coaccionadas como '2-1,foo'y '0-1,[object Object]', devolviendo un nuevo mapa con el nuevo orden de inserción:

Nota: si solo ve {}la salida de la consola de SO, busque en la consola de su 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()))

SIN EMBARGO , no es una buena práctica confiar en la coerción y la cadena de caracteres como esta. Puede obtener sorpresas 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())
}

Errores como este son realmente difíciles de depurar, ¡no se arriesgue!

Si desea ordenar por claves o valores, es mejor acceder a ellos explícitamente con a[0]y b[0]en la función de ordenación, como esta. Tenga en cuenta que deberíamos regresar -1y 1para antes y después, no falseo 0como con raw a[0] > b[0]porque eso se trata como iguales:

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
fuente
11
Vaya respuesta, tenemos que arreglar los mecanismos de descubrimiento de SO. Algo tan bueno no debería perderse aquí.
serraosays
30

Convertir Mapa una matriz usando Array.from, ordenar matriz, convertir de nuevo a Map, por ejemplo

new Map(
  Array
    .from(eventsByDate)
    .sort((a, b) => {
      // a[0], b[0] is the key of the map
      return a[0] - b[0];
    })
)
Gajus
fuente
1
[...map.values()].sort()no funcionó para mí, pero Array.from(map.values()).sort()
Rob Juurlink
1
Esto realmente me ayudó, sin Array. De, Typescript me dio un error de compilación.
icSapper
7

La idea es extraer las claves de su mapa en una matriz. Ordene esta matriz. Luego, itere sobre esta matriz ordenada, obtenga su par de valores del mapa sin clasificar y colóquelos en un nuevo mapa. El nuevo mapa estará ordenado. El siguiente código es su implementación:

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
fuente
2
Las claves sin clasificar se pueden determinar simplemente usando unsortedMap.keys(). También keys.sort().map...debería serlo keys.sort().forEach....
señal de desmayo
5

Puede convertir a una matriz y llamar a los métodos de selección de matriz en ella:

[...map].sort(/* etc */);
Wesbos
fuente
3
¿Y qué? Obtienes una matriz, no un mapa u objeto
Verde
5
y pierde la llave
frena
3

Una forma es obtener la matriz de entradas, ordenarla y luego crear un nuevo mapa con la matriz ordenada:

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

Pero si no desea crear un nuevo objeto, sino trabajar en el mismo, puede hacer algo como esto:

// 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
fuente
1

El fragmento a continuación ordena el mapa dado por sus claves y asigna las claves a los objetos de valor-clave nuevamente. Usé la función localeCompare ya que mi 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
fuente
1

Por lo que veo, actualmente no es posible ordenar un mapa correctamente.

Las otras soluciones donde el mapa se convierte en una matriz y se ordena de esta manera tienen el siguiente error:

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}

La clasificación crea un nuevo objeto y todos los demás indicadores del objeto sin clasificar se rompen.

Lacho Tomov
fuente
1

2 horas dedicadas a entrar en detalles.

Tenga en cuenta que la respuesta a la pregunta ya se encuentra en https://stackoverflow.com/a/31159284/984471

Sin embargo, la cuestión tiene teclas que no son los habituales,
Un claro ejemplo de ello y en general con la explicación, se encuentra por debajo que proporciona un poco más de claridad:

.

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 ayude.

Manohar Reddy Poreddy
fuente
0

Quizás un ejemplo más realista sobre no ordenar un objeto Mapa, sino preparar la clasificación por adelantado antes de hacer el Mapa. La sintaxis se vuelve bastante compacta si lo haces así. Puede aplicar la clasificación antes de la función de mapa de esta manera, con una función de clasificación antes del mapa (Ejemplo de una aplicación React en la que estoy trabajando usando la sintaxis JSX)

Marque que aquí defino una función de clasificación en el interior usando una función de flecha que devuelve -1 si es más pequeña y 0, de lo contrario, ordenada en una propiedad de los objetos Javascript en la matriz que obtengo de una 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
fuente
-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
fuente
3
Si bien esto podría responder a la pregunta de los autores, carece de algunas palabras explicativas y enlaces a la documentación. Los fragmentos de código sin formato no son muy útiles sin algunas frases a su alrededor. Edite su respuesta.
hola
2
Además, esta es una pregunta de 3 años. ¿Qué crees que ofrece tu respuesta que otras respuestas aún no hayan cubierto?
hola