Wie sortiere ich ein assoziatives Array nach seinen Werten in Javascript?

84

Ich habe das assoziative Array:

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

Was ist die eleganteste Art, nach ihren Werten zu sortieren (absteigend), wobei das Ergebnis ein Array mit den jeweiligen Indizes in dieser Reihenfolge wäre:

sub2, sub3, sub1, sub4, sub0

?

John Smith
quelle
1
Da Objekteigenschaften keine sprachdefinierte Reihenfolge haben, können Sie dies nicht (außer vielleicht in einigen JS-Engines, je nachdem, wie sie Eigenschaften implementiert haben).
Quentin

Antworten:

114

Javascript hat keine "assoziativen Arrays", wie Sie sie sich vorstellen. Stattdessen haben Sie einfach die Möglichkeit, Objekteigenschaften mithilfe einer Array-ähnlichen Syntax (wie in Ihrem Beispiel) festzulegen und die Eigenschaften eines Objekts zu durchlaufen.

Das Ergebnis davon ist, dass es keine Garantie für die Reihenfolge gibt, in der Sie die Eigenschaften durchlaufen, sodass es für sie nichts Vergleichbares gibt . Stattdessen müssen Sie Ihre Objekteigenschaften in ein "echtes" Array konvertieren (was die Reihenfolge garantiert). Hier ist ein Code-Snippet zum Konvertieren eines Objekts in ein Array von zwei Tupeln (Arrays mit zwei Elementen), zum Sortieren wie beschrieben und zum erneuten Durchlaufen:

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
}

Möglicherweise ist es natürlicher, dies in eine Funktion zu verpacken, die einen Rückruf entgegennimmt:

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
quelle
1
Die Funktion tuples.sort kann in tuples.sort bereinigt werden (Funktion (a, b) {return a [1] - b [1];});
Stot
2
@stot - Wenn Ihre Werte alle Zahlen sind (wie im Beispiel des Fragestellers), absolut. Ich habe anscheinend abwesend eine Vergleichsfunktion bereitgestellt, die auch mit Zeichenfolgen funktioniert. :-)
Ben Blank
@ Ben: Ja, Sie haben Recht, die bereitgestellte Version ist allgemeiner ;-)
Stot
@ Ben, vielen Dank für diesen Beitrag. Verwendet die String-Vergleichsfunktion einen ASCII-Wertevergleich?
JJWDesign
@jjwdesign, vorausgesetzt, die Zeichenfolge wurde nicht codiert, wird nach Unicode-Codepunkt sortiert.
Ben Blank
85

Anstatt Sie in Bezug auf die Semantik eines 'assoziativen Arrays' zu korrigieren, denke ich, dass Sie Folgendes wollen:

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

Verwenden Sie für wirklich alte Browser stattdessen Folgendes:

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

Sie speichern ein Objekt (wie Ihr Objekt) und erhalten ein Array der Schlüssel - eh-Eigenschaften - zurück, sortiert nach dem (numerischen) Wert des, eh, der Werte des, eh, -Objekts.

Dies funktioniert nur, wenn Ihre Werte numerisch sind. Tweek das kleine drin function(a,b), um den Sortiermechanismus so zu ändern, dass er aufsteigend arbeitet oder für stringWerte arbeitet (zum Beispiel). Links als Übung für den Leser.

Commonpike
quelle
1
Dies funktioniert perfekt und ist weniger Code als die obige Antwort
jshill103
1
Ich sollte beachten, dass die meisten Browser heutzutage nur Object.keys () unterstützen - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
commonpike
Warum ist object.keys besser?
John Ktejik
1
@johnktejik diese Antwort war von 2012 :-) Object.keys () ist heutzutage ein Standard und es ist kürzer und wahrscheinlich schneller.
Commonpike
2
... :) Ich war müde, lass mich das löschen
manu
16

Fortsetzung der Diskussion und andere Lösungen unter Wie sortiere ich ein (assoziatives) Array nach Wert? Die beste Lösung (für meinen Fall) ist saml ( siehe unten).

Arrays können nur numerische Indizes haben. Sie müssen dies entweder als Objekt oder als Array von Objekten umschreiben.

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

Wenn Ihnen die status.pushMethode gefällt , können Sie sie sortieren mit:

status.sort(function(a,b) {
    return a.val - b.val;
});
Papst Johannes Paul II
quelle
Ausgezeichnet! Aufgaben und Alpha-Sortierung nach Namen: Geben Sie a.name> b.name zurück.
mpemburn
1
Vergleichen Sie für die alphabetische Sortierung die Zeichenfolgenwerte im selben Fall, da sort()sie unterschiedlich behandelt werden. Es hat mich eine Stunde lang durcheinander gebracht, bis ich dieses Beispiel gefunden habe: stackoverflow.com/questions/6712034/… ; a.name.toLowerCase() > b.name.toLowerCase()
Dylan Valade
5

In JavaScript gibt es wirklich kein "assoziatives Array". Was Sie dort haben, ist nur ein einfaches altes Objekt. Sie funktionieren natürlich ähnlich wie assoziative Arrays, und die Schlüssel sind verfügbar, aber es gibt keine Semantik in Bezug auf die Reihenfolge der Schlüssel.

Sie können Ihr Objekt in ein Array von Objekten (Schlüssel / Wert-Paare) verwandeln und Folgendes sortieren:

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

Dann würden Sie das mit einer Komparatorfunktion aufrufen.

Spitze
quelle
+1 du hast mich geschlagen. Ich habe eine sehr ähnliche Erklärung und einen Code geschrieben.
Gonchuki
4

Hier ist eine Variation der Antwort von Ben Blank, wenn Sie keine Tupel mögen.

Dies erspart Ihnen einige Zeichen.

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.
}
Donquijote
quelle
4

Keine unnötigen Komplikationen erforderlich ...

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
quelle
Die Ausgabe davon ist ein Array von Arrays, keine Karte
Daniel
var stuff = {"a":1 , "b":3 , "c":0 } sortMapByValue(stuff) [Array[2], Array[2], Array[2]]
Daniel
Ich denke das ist am saubersten. Fügen Sie am Ende einfach eine Konvertierung zu einer Karte hinzu
Daniel
1

Ich benutze $ .each von jquery, aber Sie können es mit einer for-Schleife machen. Eine Verbesserung ist folgende:

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

Jetzt können Sie es also tun

    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);
Demosthenes
quelle
1

Der beste Ansatz für den speziellen Fall hier ist meiner Meinung nach der vorgeschlagene Commonpike . Eine kleine Verbesserung, die ich in modernen Browsern vorschlagen würde, ist:

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

Dies kann leicht zutreffen und im speziellen Fall hier hervorragend funktionieren, sodass Sie Folgendes tun können:

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]]
}

Außerdem biete ich hier eine "allgemeinere" Funktion, mit der Sie auch in einem größeren Bereich von Situationen sortieren können, und die die gerade vorgeschlagene Verbesserung mit den Ansätzen der Antworten von Ben Blank (Sortieren auch von Zeichenfolgenwerten) und PopeJohnPaulII ( Sortieren nach bestimmten Objektfeldern / Eigenschaften) und lässt Sie entscheiden, ob Sie eine aufsteigende oder absteigende Reihenfolge wünschen, hier ist es:

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

Sie können die Funktionen testen, indem Sie den folgenden Code ausprobieren:

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

Wie ich bereits sagte, können Sie sortierte Schlüssel durchlaufen, wenn Sie Dinge erledigen müssen

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]]
}

Zu guter Letzt einige nützliche Verweise auf Object.keys und Array.sort

Danicotra
quelle
0

Nur damit es da draußen ist und jemand nach Tupel-basierten Sorten sucht. Dadurch wird das erste Element des Objekts im Array mit dem zweiten Element usw. verglichen. dh im folgenden Beispiel wird zuerst mit "a", dann mit "b" usw. verglichen.

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

OUPUTS

 [ { 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.
quelle
-4

@ commonpikes Antwort ist "die richtige", aber als er weiter kommentiert ...

Die meisten Browser unterstützen heutzutage nur noch Object.keys()

Ja ... Object.keys()ist viel besser .

Aber was ist noch besser ? Duh, es ist soweit 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
quelle