usort

usort war mir zwar bisher schon bekannt, wirklich benutzt habe ich es aber nicht – was ich jetzt rückblickend bereue. Kurz gefasst ist usort die Waffe der Wahl, wenn man Arrays sortieren möchte die entweder heftig verschachtelt sind oder für die es keine trivialen Sortierkriterien gibt (wie etwa größer / kleiner). Für beides kommen später Beispiele. Bisher kam ich zwar auch ohne usort um die Runden, habe aber ersatzweise dann ziemlich aufwändige Ersatzkonstruktionen bauen müssen, für die usort die eindeutig bessere Wahl gewesen wäre.

Ein Beispiel

Man nehme folgendes Array:

$data = array(
"P1915" => array("name" => "Manfred Schmidt", 
				 "work" => array("income" => 61000, 
				 "pos" => "Abteilungsleiter")),
"P2193" => array("name" => "Wilfried Mueller", 
			     "work" => array("income" => 42300, 
				 "pos" => "Entwickler")),
"P0813" => array("name" => "Herbert Mann", 
			     "work" => array("income" => 93500, 
				 "pos" => "Chef")),
"P3913" => array("name" => "Klaus Kaiser", 
			     "work" => array("income" => 42700, 
				 "pos" => "Entwickler")),
"P2881" => array("name" => "Juergen Koenig", 
			     "work" => array("income" => 43300, 
				 "pos" => "Marketing")));

Dabei haben wir eine Zuordnung der Personalnummer zu einem Unterarray, welches den Name beinhaltet und weitere Verzweigung auf ein „work“-Unterarray mit Position und Gehalt hat. Aufgabe: Das Array soll unter Beibehaltung der Personalnummer (also des Array-Keys) absteigend nach dem Gehalt sortiert werden. Ohne usort wäre das extrem hakelig und aufwändig.

Die Lösung

uasort($data,function($a, $b) 
{
    /*echo "Comparing ".print_r($a,true).
		   " to ".print_r($b,true)."<br />";*/
	//equal -> return 0
	if ($a["work"]["income"] == $b["work"]["income"]) return 0;
	//highest income at first
	if ($a["work"]["income"] < $b["work"]["income"]) return 1; 
	//lowest income last
	return -1; 
});

Das Ergebnis:

Array
(
    [P0813] => Array
        (
            [name] => Herbert Mann
            [work] => Array
                (
                    [income] => 93500
                    [position] => Geschaeftsfuehrer
                )
        )
    [P1915] => Array
        (
            [name] => Manfred Schmidt
            [work] => Array
                (
                    [income] => 61000
                    [position] => Abteilungsleiter
                )
        )
    [P2881] => Array
        (
            [name] => Juergen Koenig
            [work] => Array
                (
                    [income] => 43300
                    [position] => Marketing
                )
        )
    [P3913] => Array
        (
            [name] => Klaus Kaiser
            [work] => Array
                (
                    [income] => 42700
                    [position] => Entwickler
                )
        )
    [P2193] => Array
        (
            [name] => Wilfried Mueller
            [work] => Array
                (
                    [income] => 42300
                    [position] => Entwickler
                )
        )
)

Hierbei sind folgende Aspekte erwähnenswert:

  • Die beiden Variablen $a und $b in der Callback-Funktion sind beliebige Elemente aus dem Array.
  • usort ist nicht stabil. Heißt: Sind 2 Werte gleich, kann man sich nicht darauf verlassen, dass sie ihre Reihenfolge behalten
  • Die Callback-Funktion muss -1, 0 (Beide Werte gleich) oder 1 zurückgeben
  • Ich verwende eine anonyme Callback-Funktion. Das muss nicht sein (siehe nächstes Beispiel). Ist auch erst seit PHP 5.3 möglich
  • Ich verwende uasort, um die Beziehung zum Array-Key bestehen zu lassen. usort löscht bestehende Keys und nummeriert von 0 an aufsteigend neu.
  • usort (und natürlich auch uasort) verwenden intern das Quicksort-Verfahren. Wie der Vergleich intern durchgeführt wird, lässt sich gut betrachten wenn man sich im Callback paar Informationen ausgeben lässt.

Garnicht so schwer, oder?

Und noch ein Beispiel

Wir wollen Instanzen einer Klasse in einem Array sortieren. Und zwar Autos nach ihrer PS-Zahl absteigend. Die Klasse:

class Car
{
	protected $_hp;
	protected $_name;
	
	public function getHp()
	{
		return $this->_hp;
	}
	
	public function getName()
	{
		return $this->_name;
	}
	
	public function __construct($name,$hp)
	{
		$this->_hp = $hp;
		$this->_name = $name;
	}
}

Die Instanzen wandern nun in ein Array.

$cars = array(new Car("BMW M5",517),
			  new Car("Audi S3",220),
			  new Car("Mercedes S600",620));

…und werden verglichen:

			  
usort($cars,"carcompare");

function carcompare($a, $b)
{
	if ($a->getHp() == $b->getHp()) return 0;
	if ($a->getHp() < $b->getHp()) return 1;
	return -1;
}

Der Output ist wie gewünscht:

foreach ($cars as $c) 
	echo "<br />".$c->getName().": ".$c->getHp();

/*
Mercedes S600: 620
BMW M5: 517
Audi S3: 220
*/

Diesmal wird die altbewährte Callback-Variante ohne anonyme Funktion verwendet.

Weitere Posts:

Dieser Beitrag wurde unter php, webdev veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.