Czy można sortować obiekt mapy ES6?


100

Czy można sortować wpisy obiektu mapy es6?

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

prowadzi do:

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

Czy można sortować wpisy na podstawie ich kluczy?

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

4
Mapy z natury nie są uporządkowane (z wyjątkiem iterowanej kolejności wstawiania, która wymaga sortowania, a następnie [ponownego] dodania).
user2864740

1
Sortuj wpisy podczas iteracji po mapie (tj. Zamieniaj ją w tablicę)
Halcyon

Odpowiedzi:


144

Według dokumentacji MDN:

Obiekt Map iteruje swoje elementy w kolejności wstawiania.

Możesz to zrobić w ten sposób:

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)

Używając .sort(), pamiętaj, że tablica jest sortowana zgodnie z wartością punktu kodowego Unicode każdego znaku, zgodnie z konwersją ciągów każdego elementu. Więc 2-1, 0-1, 3-1zostaną posortowane poprawnie.


7
można skrócić tę funkcję (a, b) pasz do: var mapAsc = new Map([...map.entries()].sort((a,b) => a[0] > b[0]));korzystania z funkcji strzałek (lambda)
Jimmy Chandra

2
W rzeczywistości, dokładnie leksykalnie porównuje się 2-1,fooz 0-1,bari3-1,baz
Bergi


4
Te ...punkty są ważne, w przeciwnym razie próbują posortować MapIterator
muttonUp

2
Jeśli klucze mapy są liczbami, otrzymasz nieprawidłowe wyniki z liczbami, takimi jak 1e-9umieszczenie po 100posortowanej mapie. Kod działający z liczbami:new Map([...map.entries()].sort((e1, e2) => e1[0] - e2[0]))
cdalxndr

62

Krótka odpowiedź

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

Na przykład, porównując ciągi wartości, które mogą być równe, przekazujemy funkcję sortowania, która uzyskuje dostęp do [1] i ma warunek równości, który zwraca 0:

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

Porównując ciągi kluczy, które nie mogą być równe (identyczne klucze łańcuchowe nadpisałyby się nawzajem), możemy pominąć warunek równości. Jednak nadal powinniśmy jawnie zwracać -1, ponieważ zwrócenie leniwego a[0] > b[0]niepoprawnie daje fałsz (traktowane jako 0, czyli równe), gdy a[0] < b[0]:

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

Szczegółowo z przykładami

.entries()W [...map.entries()](sugerowane w wielu odpowiedziach) jest zbędne, prawdopodobnie dodając dodatkowy iteracji mapie chyba że optymalizuje silnika JS że z dala dla ciebie.

W prostym przypadku testowym możesz zrobić to, o co chodzi w pytaniu, używając:

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

... który, jeśli wszystkie klucze są ciągami, porównuje spłaszczone i wymuszone ciągi klucz-wartość połączone przecinkami, takie jak '2-1,foo'i '0-1,[object Object]', zwracając nową mapę z nową kolejnością wstawiania:

Uwaga: jeśli widzisz tylko {}wyjście konsoli SO, spójrz na swoją prawdziwą konsolę przeglądarki

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

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

JEDNAK nie jest dobrą praktyką poleganie na takim przymusie i ograniczaniu. Możesz otrzymać niespodzianki, takie jak:

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

Takie błędy są naprawdę trudne do debugowania - nie ryzykuj!

Jeśli chcesz, aby posortować na klucze lub wartości, to najlepiej, aby uzyskać do nich dostęp z wyraźnie a[0]i b[0]w funkcji sortowania, jak to. Zauważ, że powinniśmy powrócić -1i 1przed i po, a nie falselub 0jak w przypadku surowego, a[0] > b[0]ponieważ jest to traktowane jako równe:

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
Co za odpowiedź, musimy naprawić mechanizmy wykrywania SO. Nie powinno się tu zgubić czegoś tak dobrego.
serraosays

32

Konwertuj Mapna tablicę używając Array.from, sortuj tablicę, konwertuj z powrotem do Mapnp

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()nie działa dla mnie, ale Array.from(map.values()).sort()tak
Rob Juurlink

2
To faktycznie pomogło mi, bez Array.from, Typescript dał mi błąd kompilacji.
icSapper

7

Chodzi o to, aby wyodrębnić klucze mapy do tablicy. Sortuj tę tablicę. Następnie iteruj po tej posortowanej tablicy, pobierz jej parę wartości z niesortowanej mapy i umieść ją w nowej mapie. Nowa mapa będzie posortowana. Poniższy kod to jego implementacja:

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
Nieposortowane klucze można określić po prostu za pomocą unsortedMap.keys(). Również keys.sort().map...powinno być keys.sort().forEach....
faintsignal

6

Możesz przekonwertować na tablicę i wywołać na niej metody sortowania tablicy:

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

3
I co? Otrzymujesz tablicę, a nie mapę lub obiekt
Green

5
i tracisz klucz
braks,


3

Jednym ze sposobów jest pobranie tablicy wpisów, posortowanie jej, a następnie utworzenie nowej mapy z posortowaną tablicą:

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

Ale jeśli nie chcesz tworzyć nowego obiektu, ale pracować nad tym samym, możesz zrobić coś takiego:

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

Poniższy fragment sortuje daną mapę według kluczy i ponownie mapuje klucze do obiektów klucz-wartość. Użyłem funkcji localeCompare, ponieważ moja mapa była typu 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;
        });

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


1

O ile widzę, obecnie nie jest możliwe prawidłowe sortowanie mapy.

Inne rozwiązania, w których mapa jest konwertowana na tablicę i sortowana w ten sposób, mają następujący błąd:

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}

Sortowanie tworzy nowy obiekt, a wszystkie inne wskaźniki nieposortowanego obiektu zostają zepsute.


1

2 godziny spędzone na poznaniu szczegółów.

Zwróć uwagę, że odpowiedź na pytanie jest już podana na https://stackoverflow.com/a/31159284/984471

Jednak pytanie ma klucze, które nie są zwykle te, jasny i ogólnie przykład z wyjaśnieniem, jest poniżej , który zapewnia jakąś większą jasność:

.

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

Mam nadzieję, że to pomoże.


0

Być może bardziej realistyczny przykład dotyczący nie sortowania obiektu mapy, ale przygotowania sortowania z góry przed wykonaniem mapy. Jeśli zrobisz to w ten sposób, składnia stanie się całkiem zwarta. Możesz zastosować sortowanie przed funkcją mapy, taką jak ta, z funkcją sortowania przed mapą (przykład z aplikacji React, nad którą pracuję przy użyciu składni JSX)

Zaznacz, że tutaj zdefiniowałem funkcję sortowania wewnątrz za pomocą funkcji strzałki, która zwraca -1, jeśli jest mniejsza, i 0 w inny sposób posortowana według właściwości obiektów JavaScript w tablicy, którą otrzymuję z interfejsu 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

Niewielkie odchylenie - nie rozpowszechniłem składni i chciałem popracować nad plikiem objectzamiast Map.

Object.fromEntries(Object.entries(apis).sort())
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.