Jak posortować tablicę asocjacyjną według jej wartości w JavaScript?

84

Mam tablicę asocjacyjną:

array["sub2"] = 1;
array["sub0"] = -1;
array["sub1"] = 0;
array["sub3"] = 1;
array["sub4"] = 0;

Jaki jest najbardziej elegancki sposób sortowania (malejąco) według wartości, gdzie wynikiem byłaby tablica z odpowiednimi indeksami w następującej kolejności:

sub2, sub3, sub1, sub4, sub0
John Smith
źródło
1
Ponieważ właściwości obiektów nie mają kolejności zdefiniowanej w języku - nie możesz (z wyjątkiem, być może, w niektórych silnikach JS zależnych od konkretnego sposobu, w jaki zaimplementowały właściwości).
Quentin

Odpowiedzi:

113

JavaScript nie ma „tablic asocjacyjnych” w sposób, w jaki o nich myślisz. Zamiast tego masz po prostu możliwość ustawiania właściwości obiektu przy użyciu składni podobnej do tablicy (jak w twoim przykładzie), a także możliwość iteracji po właściwościach obiektu.

W rezultacie nie ma gwarancji kolejności, w jakiej iterujesz po właściwościach, więc nie ma dla nich żadnego rodzaju. Zamiast tego będziesz musiał przekonwertować właściwości obiektu na „prawdziwą” tablicę (która gwarantuje porządek). Oto fragment kodu służący do konwersji obiektu na tablicę dwóch krotek (tablice dwuelementowe), sortowania go zgodnie z opisem, a następnie iterowania po nim:

var tuples = [];

for (var key in obj) tuples.push([key, obj[key]]);

tuples.sort(function(a, b) {
    a = a[1];
    b = b[1];

    return a < b ? -1 : (a > b ? 1 : 0);
});

for (var i = 0; i < tuples.length; i++) {
    var key = tuples[i][0];
    var value = tuples[i][1];

    // do something with key and value
}

Może się okazać, że bardziej naturalne będzie zawinięcie tego w funkcję, która pobiera wywołanie zwrotne:

function bySortedValue(obj, callback, context) {
  var tuples = [];

  for (var key in obj) tuples.push([key, obj[key]]);

  tuples.sort(function(a, b) {
    return a[1] < b[1] ? 1 : a[1] > b[1] ? -1 : 0
  });

  var length = tuples.length;
  while (length--) callback.call(context, tuples[length][0], tuples[length][1]);
}

bySortedValue({
  foo: 1,
  bar: 7,
  baz: 3
}, function(key, value) {
  document.getElementById('res').innerHTML += `${key}: ${value}<br>`
});
<p id='res'>Result:<br/><br/><p>

Ben Blank
źródło
1
funkcja krotki.sort może zostać wyczyszczona do krotek.sort (function (a, b) {return a [1] - b [1];});
godzina
2
@stot - jeśli wszystkie twoje wartości są liczbami (jak w przykładzie pytającego), absolutnie. Wygląda na to, że w roztargnieniu dostarczyłem funkcję porównawczą, która działa również ze stringami. :-)
Ben Blank
@Ben: tak, masz rację, podana wersja jest bardziej ogólna ;-)
od
@Ben, bardzo dziękuję za ten wpis. Jeśli chodzi o funkcję porównywania ciągów, czy używa ona porównania wartości ASCII?
jjwdesign,
@jjwdesign, zakładając, że ciąg nie został zakodowany, zostanie posortowany według punktu kodowego Unicode.
Ben Blank,
85

Zamiast poprawiać semantykę `` tablicy asocjacyjnej '', myślę, że tego chcesz:

function getSortedKeys(obj) {
    var keys = keys = Object.keys(obj);
    return keys.sort(function(a,b){return obj[b]-obj[a]});
}

w przypadku naprawdę starych przeglądarek użyj tego:

function getSortedKeys(obj) {
    var keys = []; for(var key in obj) keys.push(key);
    return keys.sort(function(a,b){return obj[b]-obj[a]});
}

Zrzucasz obiekt (taki jak twój) i otrzymujesz tablicę kluczy - eh właściwości - z powrotem, posortowanych malejąco według (numerycznej) wartości, eh, wartości, eh, obiektu.

Działa to tylko wtedy, gdy wartości są liczbowe. Popraw trochę function(a,b)tam, aby zmienić mechanizm sortowania, aby działał rosnąco lub pracował dla stringwartości (na przykład). Pozostawione jako ćwiczenie dla czytelnika.

pospolity
źródło
1
Działa to doskonale i jest mniej kodu niż powyższa odpowiedź
jshill103
1
Powinienem zauważyć, że obecnie większość przeglądarek obsługuje tylko Object.keys () - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
commonpike
dlaczego object.keys jest lepszy?
john ktejik
1
@johnktejik to odpowiedź z 2012 roku :-) Object.keys () jest obecnie standardem, jest krótszy i prawdopodobnie szybszy.
Commonpike
2
... :) Byłem zmęczony, skasuję to
manu
16

Ciągła dyskusja i inne rozwiązania omówione w temacie Jak sortować (asocjacyjną) tablicę według wartości? najlepszym rozwiązaniem (w moim przypadku) jest saml (cytowane poniżej).

Tablice mogą mieć tylko indeksy numeryczne. Musiałbyś przepisać to jako obiekt lub tablicę obiektów.

var status = new Array();
status.push({name: 'BOB', val: 10});
status.push({name: 'TOM', val: 3});
status.push({name: 'ROB', val: 22});
status.push({name: 'JON', val: 7});

Jeśli podoba Ci się ta status.pushmetoda, możesz ją posortować za pomocą:

status.sort(function(a,b) {
    return a.val - b.val;
});
Papież Jan Paweł II
źródło
Świetny! Do zrobienia i sortowania alfabetycznego według nazwy: zwraca a.name> b.name.
mpemburn
1
W przypadku sortowania alfabetycznego porównaj wartości ciągów w tym samym przypadku, ponieważ sort()traktuje je inaczej. Wkurzało mnie przez godzinę, aż znalazłem ten stackoverflow.com/questions/6712034/… Przykład; a.name.toLowerCase() > b.name.toLowerCase()
Dylan Valade
5

Naprawdę nie ma czegoś takiego jak „tablica asocjacyjna” w JavaScript. To, co tam masz, to zwykły stary przedmiot. Działają one oczywiście podobnie jak tablice asocjacyjne, a klucze są dostępne, ale nie ma semantyki wokół kolejności kluczy.

Możesz przekształcić swój obiekt w tablicę obiektów (pary klucz / wartość) i posortować to:

function sortObj(object, sortFunc) {
  var rv = [];
  for (var k in object) {
    if (object.hasOwnProperty(k)) rv.push({key: k, value:  object[k]});
  }
  rv.sort(function(o1, o2) {
    return sortFunc(o1.key, o2.key);
  });
  return rv;
}

Następnie nazwałbyś to funkcją komparatora.

Spiczasty
źródło
+1 pokonasz mnie. Pisałem bardzo podobne wyjaśnienie i fragment kodu.
gonchuki,
4

Oto odmiana odpowiedzi Bena Blanka, jeśli nie lubisz krotek.

Oszczędza to kilka znaków.

var keys = [];
for (var key in sortme) {
  keys.push(key);
}

keys.sort(function(k0, k1) {
  var a = sortme[k0];
  var b = sortme[k1];
  return a < b ? -1 : (a > b ? 1 : 0);
});

for (var i = 0; i < keys.length; ++i) {
  var key = keys[i];
  var value = sortme[key];
  // Do something with key and value.
}
Don Kichot
źródło
4

Bez zbędnych komplikacji ...

function sortMapByValue(map)
{
    var tupleArray = [];
    for (var key in map) tupleArray.push([key, map[key]]);
    tupleArray.sort(function (a, b) { return a[1] - b[1] });
    return tupleArray;
}
bop
źródło
Wynikiem tego jest tablica tablic, a nie mapa
Daniel,
var stuff = {"a":1 , "b":3 , "c":0 } sortMapByValue(stuff) [Array[2], Array[2], Array[2]]
Daniel,
Myślę, że to jest najczystsze. Po prostu dodaj konwersję do mapy na koniec
Daniel,
1

używam $ .each z jquery, ale możesz to zrobić za pomocą pętli for, poprawa jest następująca:

        //.ArraySort(array)
        /* Sort an array
         */
        ArraySort = function(array, sortFunc){
              var tmp = [];
              var aSorted=[];
              var oSorted={};

              for (var k in array) {
                if (array.hasOwnProperty(k)) 
                    tmp.push({key: k, value:  array[k]});
              }

              tmp.sort(function(o1, o2) {
                    return sortFunc(o1.value, o2.value);
              });                     

              if(Object.prototype.toString.call(array) === '[object Array]'){
                  $.each(tmp, function(index, value){
                      aSorted.push(value.value);
                  });
                  return aSorted;                     
              }

              if(Object.prototype.toString.call(array) === '[object Object]'){
                  $.each(tmp, function(index, value){
                      oSorted[value.key]=value.value;
                  });                     
                  return oSorted;
              }               
     };

Więc teraz możesz to zrobić

    console.log("ArraySort");
    var arr1 = [4,3,6,1,2,8,5,9,9];
    var arr2 = {'a':4, 'b':3, 'c':6, 'd':1, 'e':2, 'f':8, 'g':5, 'h':9};
    var arr3 = {a: 'green', b: 'brown', c: 'blue', d: 'red'};
    var result1 = ArraySort(arr1, function(a,b){return a-b});
    var result2 = ArraySort(arr2, function(a,b){return a-b});
    var result3 = ArraySort(arr3, function(a,b){return a>b});
    console.log(result1);
    console.log(result2);       
    console.log(result3);
demostenes
źródło
1

Najlepszym rozwiązaniem dla przypadku szczególnego tutaj, moim zdaniem, jest jednym commonpike sugerowane. Małe ulepszenie, które proponuję, które działa w nowoczesnych przeglądarkach to:

// aao is the "associative array" you need to "sort"
Object.keys(aao).sort(function(a,b){return aao[b]-aao[a]});

Można to łatwo zastosować i świetnie działać w konkretnym przypadku tutaj, więc możesz:

let aoo={};
aao["sub2"]=1;
aao["sub0"]=-1;
aao["sub1"]=0;
aao["sub3"]=1;
aao["sub4"]=0;

let sk=Object.keys(aao).sort(function(a,b){return aao[b]-aao[a]});

// now you can loop using the sorted keys in `sk` to do stuffs
for (let i=sk.length-1;i>=0;--i){
 // do something with sk[i] or aoo[sk[i]]
}

Poza tym, podaję tutaj bardziej "ogólną" funkcję, której możesz użyć do sortowania nawet w szerszym zakresie sytuacji i która łączy poprawę, którą właśnie zasugerowałem, z podejściem do odpowiedzi Bena Blanka (sortowanie również wartości ciągów) i PopeJohnPaulII ( sortowanie według określonego pola / właściwości obiektu) i pozwala zdecydować, czy chcesz uzyskać kolejność rosnącą, czy podrzędną, oto ona:

// aao := is the "associative array" you need to "sort"
// comp := is the "field" you want to compare or "" if you have no "fields" and simply need to compare values
// intVal := must be false if you need comparing non-integer values
// desc := set to true will sort keys in descendant order (default sort order is ascendant)
function sortedKeys(aao,comp="",intVal=false,desc=false){
  let keys=Object.keys(aao);
  if (comp!="") {
    if (intVal) {
      if (desc) return keys.sort(function(a,b){return aao[b][comp]-aao[a][comp]});
      else return keys.sort(function(a,b){return aao[a][comp]-aao[a][comp]});
    } else {
      if (desc) return keys.sort(function(a,b){return aao[b][comp]<aao[a][comp]?1:aao[b][comp]>aao[a][comp]?-1:0});
      else return keys.sort(function(a,b){return aao[a][comp]<aao[b][comp]?1:aao[a][comp]>aao[b][comp]?-1:0});
    }
  } else {
    if (intVal) {
      if (desc) return keys.sort(function(a,b){return aao[b]-aao[a]});
      else return keys.sort(function(a,b){return aao[a]-aao[b]});
    } else {
      if (desc) return keys.sort(function(a,b){return aao[b]<aao[a]?1:aao[b]>aao[a]?-1:0});
      else return keys.sort(function(a,b){return aao[a]<aao[b]?1:aao[a]>aao[b]?-1:0});
    }
  }
}

Możesz przetestować funkcje, próbując czegoś takiego jak poniższy kod:

let items={};
items['Edward']=21;
items['Sharpe']=37;
items['And']=45;
items['The']=-12;
items['Magnetic']=13;
items['Zeros']=37;
//equivalent to:
//let items={"Edward": 21, "Sharpe": 37, "And": 45, "The": -12, ...};

console.log("1: "+sortedKeys(items));
console.log("2: "+sortedKeys(items,"",false,true));
console.log("3: "+sortedKeys(items,"",true,false));
console.log("4: "+sortedKeys(items,"",true,true));
/* OUTPUT
1: And,Sharpe,Zeros,Edward,Magnetic,The
2: The,Magnetic,Edward,Sharpe,Zeros,And
3: The,Magnetic,Edward,Sharpe,Zeros,And
4: And,Sharpe,Zeros,Edward,Magnetic,The
*/

items={};
items['k1']={name:'Edward',value:21};
items['k2']={name:'Sharpe',value:37};
items['k3']={name:'And',value:45};
items['k4']={name:'The',value:-12};
items['k5']={name:'Magnetic',value:13};
items['k6']={name:'Zeros',value:37};

console.log("1: "+sortedKeys(items,"name"));
console.log("2: "+sortedKeys(items,"name",false,true));
/* OUTPUT
1: k6,k4,k2,k5,k1,k3
2: k3,k1,k5,k2,k4,k6
*/

Jak już powiedziałem, możesz zapętlić posortowane klucze, jeśli potrzebujesz coś zrobić

let sk=sortedKeys(aoo);
// now you can loop using the sorted keys in `sk` to do stuffs
for (let i=sk.length-1;i>=0;--i){
 // do something with sk[i] or aoo[sk[i]]
}

Na koniec kilka przydatnych odniesień do Object.keys i Array.sort

danicotra
źródło
0

Tak więc jest tam i ktoś szuka typów opartych na krotkach. Spowoduje to porównanie pierwszego elementu obiektu w tablicy, z drugim elementem i tak dalej. tj. w poniższym przykładzie porównuje najpierw „a”, potem „b” i tak dalej.

let arr = [
    {a:1, b:2, c:3},
    {a:3, b:5, c:1},
    {a:2, b:3, c:9},
    {a:2, b:5, c:9},
    {a:2, b:3, c:10}    
]

function getSortedScore(obj) {
    var keys = []; 
    for(var key in obj[0]) keys.push(key);
    return obj.sort(function(a,b){
        for (var i in keys) {
            let k = keys[i];
            if (a[k]-b[k] > 0) return -1;
            else if (a[k]-b[k] < 0) return 1;
            else continue;
        };
    });
}

console.log(getSortedScore(arr))

WYJŚCIA

 [ { a: 3, b: 5, c: 1 },
  { a: 2, b: 5, c: 9 },
  { a: 2, b: 3, c: 10 },
  { a: 2, b: 3, c: 9 },
  { a: 1, b: 2, c: 3 } ]
Shayan C.
źródło
-4

Odpowiedź @ commonpike jest „właściwa”, ale gdy komentuje ...

obecnie większość przeglądarek po prostu obsługuje Object.keys()

Tak .. Object.keys()jest DUŻO lepiej .

Ale co jest jeszcze lepsze ? Duh, już jest coffeescript!

sortedKeys = (x) -> Object.keys(x).sort (a,b) -> x[a] - x[b]

sortedKeys
  'a' :  1
  'b' :  3
  'c' :  4
  'd' : -1

[ 'd', 'a', 'b', 'c' ]

Alex Gray
źródło