Rolf B: Assoziatives, mehrdimensionales Array sortieren

Beitrag lesen

Hallo ebody,

array_multisort bekommt 1-N Arrays. Jedes Array wird als eine Spalte einer Matrix betrachtet. Und diese Matrix wird nach Zeilen sortiert. Du kannst Dir das so vorstellen, dass in jedem Array das i-te Element als Element der Matrixzeile Nr. i betrachtet wird, und dann bei der Entscheidung, ob zwei Zeilen vertauscht werden müssen, deren Elemente vom ersten bis zum letzten verglichen werden.

Das i-te Element ist aber nicht das Element mit dem Index i. Schön wär's. Das ist nur dann der Fall, wenn Du die Arrays mit numerischem Index in Indexreihenfolge erzeugst. Ich habe das in der PHP Sandbox ausprobiert:

$a = ARRAY( 2=>"Rolf", 0=>"Golf", 3=>"Wolf", 1=>"Wolf");
$b = ARRAY( 3=>47    , 1=>111     ,     2=>38, 0=>37]);

array_multisort($a, $b);

Rate mal, was da rauskommt. Ohne es auszuprobieren...

Tatsächlich hat PHP eine interne Array-Ordnung, die mit den Indexen nichts zu tun hat. Die Elemente sind - solange Du nicht umsortierst - in Zugangsreihenfolge. Und die Elementnummer in DIESER Reihenfolge ist das i, das array_multisort für die Zeilenzuordnung zu Grunde legt.

Für deine Datenstruktur also gruselig ungeeignet.

Dass Du die Autos vielleicht besser flach speichern solltest, hatten wir schonmal gesagt. Das Sortieren wäre dann einfacher. Um ein Array aus Matrixzeilen zu sortieren, bietet PHP Sortierfunktionen mit Callback an, die dann zwei Autos vergleichen können.

Ich habe ein bisschen gespielt. Denn für diesen Zweck bieten sich eigentlich Objekte an.

// Ein simples Auto-Objekt mit Konstruktor, String-Aufbereitung und Sortierer
class Auto {
    public $Marke, $Modell, $Farbe, $Preis;
    public function __construct($marke, $modell, $farbe, $preis) {
        $this->Marke  = $marke;
        $this->Modell = $modell;
        $this->Farbe  = $farbe;
        $this->Preis  = $preis;
    }
    public function __toString() {
        return "$this->Marke $this->Modell $this->Farbe $this->Preis";
    }
    // Liefert den Vergleichswert für aufsteigende Sortierung nach Marke, Modell, Farbe
    public static function compare($a1, $a2) {
        return $a1->Marke <=> $a2->Marke
            ?: $a1->Modell <=> $a2->Modell
            ?: $a1->Farbe <=> $a2->Farbe;
    }
}

// Test: 5 Autos erzeugen
$a = [
   new Auto("Mercedes", "200", "rot", 100001),
   new Auto("Mercedes", "200", "grau", 100002),
   new Auto("Mercedes", "Vaio", "weiß", 100003),
   new Auto("Dacia", "Foo", "gelb", 10001),
   new Auto("Dacia", "Bar", "gelb", 10002),
   new Auto("Dacia", "Conan", "gelb", 10003)
];

// Sortieren mit der compare-Methode als Sorter
uasort($a, [ "Auto", "compare"]);

foreach ($a as $u=>$v) 
   echo "$u: $v\n";

Die Compare-Methode der Auto-Klasse enthält einen Leckerbissen, den ich bei Stackoverflow gefunden habe. Wenn man nach mehreren Spalten sortieren will, muss man ja solange Spalten vergleichen bis man eine findet, wo die Objekte verschieden sind. Dabei hilft zuerst mal der "Spaceship" Operator <=>, der -1, 0 oder +1 liefert, je nachdem, ob der linke Operand kleiner, gleich oder größer als der rechte ist. Und dann gibt es ?:, was eine Verkürzung des If/Then/Else Operators ist. Normalerweise hat man bedingung ? wert_für_true : wert_für_false. Die „Bedingung“ muss ohnehin nichts boolesches sein, da ist PHP ähnlich drauf wie JavaScript. Um wahr zu sein, muss sie nur wahr-artig (true-ish) sein; was true-ish ist, steht im PHP Handbuch. Und das Konstrukt $a ?: $b prüft, ob der Inhalt von $a true-ish ist, und hat dann den Wert $a. Andernfalls den Wert $b. D.h. eine Kette $a ?: $b ?: $c, in der $a, $b und $c jeweils die Werte -1, 0 oder 1 annehmen können, liefert den ersten Wert der nicht 0 ist.[1]

Rolf

--
sumpsi - posui - clusi

  1. PHP ist hier etwas anders als JavaScript. Dort hätte man einfach das logische OR (||) nehmen können, weil in JavaScript (a || b === a) gilt wenn a truthy ist). In PHP erzeugt || immer TRUE oder FALSE. ↩︎