• Jetzt anmelden. Es dauert nur 2 Minuten und ist kostenlos!

usort() mit callback

Tronjer

Senior HTML'ler
Folgendes Script gibt den Inhalt des Arrays $fruit in umgekehrter Reihenfolge aus:
PHP:
$fruit = array('apfel', 'banane', 'zitrone');

usort($fruit, function($a, $b) {
    if($a > $b) {
        return -1;
    }
    if ($a < $b) {
        return 1;
    }
    return 0;
});

var_dump($fruit);

Wozu man usort() verwendet, habe ich dem PHP-Manual entnommen, aber nicht wirklich verstanden,

Ich versuche es mal zu interpretieren. usort() hat zwei Argumente: String und Vergleichsfunktion. Für letzteres wird eine callback Funktion mit den Parametern $a und $b aufgerufen. Wenn $a > $b ist, haben wir einen Rückgabewert von -1; ist $a < $b, beträgt der Rückgabewert 1; ansonsten 0.

Im Manual heißt es dazu:
Die Vergleichsfunktion muss einen Integerwert kleiner als, gleich oder größer als 0 zurückgeben je nachdem ob das erste übergebene Argument kleiner als, gleich oder größer als das zweite ist.

Was wird hier eigentlich verglichen? Für die Variablen $a und $b existieren doch gar keine Werte.
Und wie erklärt sich die Ausgabe: -1 gleich absteigend, 1 gleich aufsteigend, 0 keine Ausgabe(false)?
 
Zuletzt bearbeitet:
Also prinzipiell wird usort dazu verwenden ein Array nach benutzerdefinierten Kriterien zu sortieren.
In dem Beispiel das du gepostet hast wird das Array einfach alphabetisch geordnet, von hinten nach vorne.

Parameter 1 ist hierbei das zu sortierende Array und Parameter 2 die Sortierfunktion.

Hat die Sortierfunktion den Rueckgabewert -1 so wird $a im Array weiter vorne als $b platziert.

$a und $b sind einfach Werte des Arrays.
Vllt hilft dir das als einfache verdeutlichung.

PHP:
<?php 
$fruits = array("birne", "mandarine","apfel","zitrone","erdbeere");

function cmp($a,$b) {
    echo $a." wird verglichen mit ".$b."<br />";
    if($a >$b) return -1;
    if($a < $b) return 1;
    return 0;
}
usort($fruits,"cmp");
var_dump($fruits);
?>
 
Danke, das macht Sinn - aber noch zwei, drei Punkte an dieser Stelle.

- Wieso wird die Funktion mit return 0 beendet? Das Script läuft doch auch, wenn man die Zeile auskommentiert.

- In meinem oberen Beispiel habe ich zwar eine Funktion angelegt, aber hinterher nicht aufgerufen. Warum wird das Array trotzdem sortiert?

Und dann noch etwas, was ich schon immer wissen, aber mich nie zu fragen getraut habe: callback functions. :oops:
Ich weiß, dass es sich dabei um Funktionsreferenzen handelt, die aus anderen Funktionen aufgerufen werden. Verwendet habe ich sie schon in jQuery und Ajax, aber immer nur nach Tutorial. Ohne allerdings zu wissen, warum lediglich eine Referenz anstelle der Funktion selber angegeben wird. Wo liegt der Unterschied, ob ich:

Code:
function sndReq() {
    resObject.open('get', 'blabla.txt', true);
    resObject.onreadystatechange = handleResponse;
    resObject.send(null);
}

oder
Code:
function sndReq() {
    ......
    resObject.onreadystatechange = handleResponse();
    .....
}

schreibe?
 
- Wieso wird die Funktion mit return 0 beendet? Das Script läuft doch auch, wenn man die Zeile auskommentiert.

Die Funktion hat aber in diesem Fall keinen Rückgabewert. Das heißt, sie gibt NULL zurück.

PHP:
<?php

function f() {}

var_dump(f()); // NULL

usort ist an der Stelle dann wohl einfach kulant genug, das als 0 zu werten.

Ist aber schlechter Stil.

Randbeobachtung:

PHP:
<?php

$a = array('b', 'c', 'd', 'a');

usort($a, function ($a, $b) {});

var_dump($a); // a, d, c, b

Die Reihenfolge „gleicher“ Einträge ist nicht definiert.

- In meinem oberen Beispiel habe ich zwar eine Funktion angelegt, aber hinterher nicht aufgerufen. Warum wird das Array trotzdem sortiert?

Was ist eine Closure in PHP?

PHP:
<?php

$fruit = array('apfel', 'banane', 'zitrone');

class MySortFunction
{
    public function __invoke($a, $b)
    {
        if($a > $b) {
            return -1;
        }
        if ($a < $b) {
            return 1;
        }
        return 0;
    }
}

$msf = new MySortFunction();

usort($fruit, $msf);

var_dump($fruit); // zitrone, banane, apfel

Eine PHP-Closure ist intern im Grunde nichts anderes als ein Objekt mit „magischer“ __invoke-Methode.

- PHP: Magic Methods - Manual

Diese Schreibweise erzeugt ein Objekt:

PHP:
<?php

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

var_dump($f);                     // object(Closure)

var_dump($f->__invoke('x', 'y')); // int(1)

Was du usort übergibst, ist also eine Instanz der Klasse Closure.

Ich denke, diese Implementation ist PHP-spezifisch. Aber sie macht es meines Erachtens gedanklich einfacher, nachzuvollziehen, was genau passiert.

usort nimmt sich nun also – anschaulich gesprochen – dieses Objekt und ruft für je zwei Elemente des zu sortierenden Arrays die __invoke-Methode auf und vertauscht sie im Array je nach Rückgabewert.

Naiv tut die Funktion das hier:

PHP:
<?php

function myusort(array &$array, Closure $sortLogic)
{
    $c = count($array);

    // Selection sort (http://de.wikipedia.org/wiki/Selectionsort)
    for ($i = 0; $i < $c - 1; $i++) {
        $smallest = $i;

        for ($j = $i + 1; $j < $c; $j++) {
            if ($sortLogic->__invoke($array[$smallest], $array[$j]) > 0) {
                $smallest = $j;
            }
        }

        // Vertausche Element an $i mit Element an $smallest
        $tmp = $array[$i];
        $array[$i] = $array[$smallest];
        $array[$smallest] = $tmp;
    }
}

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

$data = array('apfel', 'birne', 'zitrone');

myusort($data, $sortFunc);

var_dump($data);

(Disclaimer: Ich habe den Code nur einmal kurz per trial & error getestet. usort nutzt außerdem einen schlaueren Algorithmus. Ich glaube Quick sort.)

Wo liegt der Unterschied, ob ich: […] oder […] schreibe?

Um in dem Bild „eine Closure ist ein Objekt“ zu bleiben: Im ersten Fall weist du die Instanz zu, im zweiten Fall weist du den Rückgabewert der __invoke-Methode mit leerer Argumentliste zu.

Ich weiß, das ist nicht so ganz leicht nachzuvollziehen.
 
Zuletzt bearbeitet:
Danke für die ausführliche Erklärung. Closures und Lambda Funktionen sind noch Neuland für mich. Magische Funktionen kannte ich bisher nur in Form von __construct() und __toString().

Ich trete ab kommender Woche ein Praktikum als PHP-Entwickler an, und da wollte ich vorher noch einige Lücken schließen.
 
Zurück
Oben