Viennamade: Mehrdimensionales Array sortieren

Hallo liebe Forumsteilnehmer!

Ich habe ein Array namens $trans, es sieht so aus:

Array
{
   [5] => Array
        (
            [PicNr] => 111
            [osname] => x8.gif
            [origname] => button_down.png
            ...
   [6] => Array
        (
            [PicNr] => 8
            [osname] => x2.gif
            [origname] => button_up.png
            ...

Ich dachte ich könnte eine Sortierung mit array_multisort() machen. Also ich hoffte,
array_multisort($trans,"osname") würde $trans[6] vor $trans[5] einreihen. OK, gebe zu das wäre ein bißchen gar einfach, komme aber nicht drauf was fehlt.

Bitte um Hilfe.
Viennamade

  1. Moin!

    Array
    {
       [5] => Array
            (
                [PicNr] => 111
                [osname] => x8.gif
                [origname] => button_down.png
                ...
       [6] => Array
            (
                [PicNr] => 8
                [osname] => x2.gif
                [origname] => button_up.png
                ...

    Ich dachte ich könnte eine Sortierung mit array_multisort() machen. Also ich hoffte,
    array_multisort($trans,"osname") würde $trans[6] vor $trans[5] einreihen. OK, gebe zu das wäre ein bißchen gar einfach, komme aber nicht drauf was fehlt.

    Ob welchen Grundes soll $trans[6] denn vor $trans[5] gelangen?

    array_multisort() ist in den wenigsten Fällen hilfreich. Du benötigst eine eigene Sortiefunktion, welche du mit usort() & Co. aufrufst. Denn du hast ja nicht multiple Arrays, sondern nur eines.

    - Sven Rautenberg

    1. Hallo Sven!
      Danke für Deine Antwort, komme leider nicht durch damit, bin in diesem Fall zu nicht genug self fähig.

      Array
      {
         [5] => Array
              (
                  [PicNr] => 111
                  [osname] => x8.gif
                  [origname] => button_down.png
                  ...
         [6] => Array
              (
                  [PicNr] => 8
                  [osname] => x2.gif
                  [origname] => button_up.png
                  ...

      Ich dachte ich könnte eine Sortierung mit array_multisort() machen. Also ich hoffte,
      array_multisort($trans,"osname") würde $trans[6] vor $trans[5] einreihen. OK, gebe zu das wäre ein bißchen gar einfach, komme aber nicht drauf was fehlt.

      Jedenfalls habe ich jetzt was funktionierendes beinander, aber ich verstehe es nicht:
      foreach ($trans as $val)
        {
          $sortarray[] = $val['osname'];
        }
      array_multisort($sortarray,$trans);

      OK, die foreach-Schleife erstellt den sortarray in der Reihenfolge wie $trans vorliegt.
      Wie es dann array_multisort schafft $trans wie gewünscht zu sortieren ist mir vollkommen schleierhaft. Ich bitte (Dich) um eine kurze Erklärung, kanns ja nicht einsetzen wenn ich es nicht verstehe.

      Viennamade

      1. Hi,

        Jedenfalls habe ich jetzt was funktionierendes beinander, aber ich verstehe es nicht:
        foreach ($trans as $val)
          {
            $sortarray[] = $val['osname'];
          }
        array_multisort($sortarray,$trans);

        OK, die foreach-Schleife erstellt den sortarray in der Reihenfolge wie $trans vorliegt.
        Wie es dann array_multisort schafft $trans wie gewünscht zu sortieren ist mir vollkommen schleierhaft. Ich bitte (Dich) um eine kurze Erklärung, kanns ja nicht einsetzen wenn ich es nicht verstehe.

        array_multisort() stellt die Einträge der beiden Arrays sozusagen in einer Tabelle spaltenweise gegenüber und sortiert zeilenweise nach der ersten Spalte - sind dort gleiche Werte vorhanden, greift es zur Sortierung auf die zweite Spalte zurück, usw. In deinem Fall also:

        $sortarray | $trans
        -----------------------------------------------------------
        'x8.gif'   | array([PicNr] => 111, [osname] => x8.gif, ...)
        'x2.gif'   | array([PicNr] => 8, [osname] => x2.gif, ...)
        ...

        nach der zeilenweisen Sortierung nach der ersten Spalte ist logischerweise auch das array $trans nach deinen Wünschen sortiert, da die erste Spalte genau nur die Werte enthält, die du zur Sortierung heranziehen möchtest.

        Dein Vorhaben ist allerdings ein klassischer Fall für usort(), welches auch schneller sein sollte. Genau dieser Fall wird im Beispiel 2 unter http://de2.php.net/manual/de/function.usort.php erklärt.

        Gruß,
        Andreas.

        1. Hallo!

          foreach ($trans as $val)
            {
              $sortarray[] = $val['osname'];
            }
          array_multisort($sortarray,$trans);

          OK, die foreach-Schleife erstellt den sortarray in der Reihenfolge wie $trans vorliegt.
          Wie es dann array_multisort schafft $trans wie gewünscht zu sortieren ist mir vollkommen schleierhaft.

          array_multisort() stellt die Einträge der beiden Arrays sozusagen in einer Tabelle spaltenweise gegenüber und sortiert zeilenweise nach der ersten Spalte - sind dort gleiche Werte vorhanden, greift es zur Sortierung auf die zweite Spalte zurück, also:

          $sortarray | $trans

          'x8.gif'   | array([PicNr] => 111, [osname] => x8.gif, ...)
          'x2.gif'   | array([PicNr] => 8, [osname] => x2.gif, ...)
          ...

          nach der zeilenweisen Sortierung nach der ersten Spalte ist logischerweise auch das array $trans nach deinen Wünschen sortiert, da die erste Spalte genau nur die Werte enthält, die du zur Sortierung heranziehen möchtest.

          Danke für Deine beherzte Antwort, dank ihrer habe ich das array_multisort() verstanden.

          Dein Vorhaben ist allerdings ein klassischer Fall für usort(), welches auch schneller sein sollte. Genau dieser Fall wird im Beispiel 2 unter http://de2.php.net/manual/de/function.usort.php erklärt.

          :-( Also ich habe versucht das Beispiel 2 auf meinen Array umzubauen, war jedoch erfolglos. Vielleicht geht das gar nicht, weil mein Array mehr "Ebenen" hat als von mir anfänglich dargestellt?
          Array
          {
             [5] => Array
                  (
                      [PicNr] => 111
                      [osname] => x8.gif
                      [origname] => button_down.png
                      [hoehe] => 400
                      [breite] => 600
                      ...
                      ...
                      ...

          Beste Grüße
          Viennamade

      2. Moin!

        Array
        {
           [5] => Array
                (
                    [PicNr] => 111
                    [osname] => x8.gif
                    [origname] => button_down.png
                    ...
           [6] => Array
                (
                    [PicNr] => 8
                    [osname] => x2.gif
                    [origname] => button_up.png
                    ...

        Ich dachte ich könnte eine Sortierung mit array_multisort() machen. Also ich hoffte, array_multisort($trans,"osname") würde $trans[6] vor $trans[5] einreihen.

        Ok, du willst also nach dem Feld "osname" deines Subarrays sortieren. Das ist, wie von Andreas Görtz schon gesagt, exakt das Anwendungsbeispiel 2 von usort().

        Du hast die einzelnen Elemente, die ja komplett verschoben sortiert werden sollen, in _einem_ Array. Deshalb ist array_multisort() falsch, weil das Dinge in _mehreren_ Arrays sortiert.

        Jedenfalls habe ich jetzt was funktionierendes beinander, aber ich verstehe es nicht:
        foreach ($trans as $val)
          {
            $sortarray[] = $val['osname'];
          }
        array_multisort($sortarray,$trans);

        Wenn du stattdessen das Beispiel 2 von usort() nimmst, und die Vergleichsfunktion von "fruit" auf "osname" umstellst, wirst du Erfolg haben.

        - Sven Rautenberg

        1. Hallo Sven!
          Danke für Deine Antwort.

          Du hast die einzelnen Elemente, die ja komplett verschoben sortiert werden sollen, in _einem_ Array. Deshalb ist array_multisort() falsch, weil das Dinge in _mehreren_ Arrays sortiert.

          Du hast sicher recht, aber es funktionert!

          Wenn du stattdessen das Beispiel 2 von usort() nimmst, und die Vergleichsfunktion von "fruit" auf "osname" umstellst, wirst du Erfolg haben.

          Stimmt, bei mir schaut das jetzt so aus:
              function cmp ($a, $b) {
                  return strcmp($a["osname"], $b["osname"]);
              }
              usort($trans, "cmp");

          Wobei ich damit seit gestern an 2 Problemen scheitere:
          1. Was, wenn ich nicht nach "osname", sondern nach $sort sortieren will (wenn ich die cmp-Funktion um einen Parameter - eben $sort - erweitere, dann beschwert sich PHP)?
          2. Wie kann ich der Funktion mitteilen, ob sie numerisch oder alphanumerisch sortieren soll (zur Zeit wird "111" vor "12" gereiht)?

          Danke für jede Hilfe.
          Viennamade

          1. Moin!

            Du hast die einzelnen Elemente, die ja komplett verschoben sortiert werden sollen, in _einem_ Array. Deshalb ist array_multisort() falsch, weil das Dinge in _mehreren_ Arrays sortiert.
            Du hast sicher recht, aber es funktionert!

            Ist aber superumständlich. Außerdem ist "es funktioniert" bekanntlich keine ausreichende Begründung für irgendwas. Im IE funktioniert ja auch vieles. :)

            function cmp ($a, $b) {
                    return strcmp($a["osname"], $b["osname"]);
                }
                usort($trans, "cmp");

            Wobei ich damit seit gestern an 2 Problemen scheitere:

            1. Was, wenn ich nicht nach "osname", sondern nach $sort sortieren will (wenn ich die cmp-Funktion um einen Parameter - eben $sort - erweitere, dann beschwert sich PHP)?

            Dann brauchst du eine andere Sortierfunktion. Der Name cmp ist ja nicht fest vorgegeben, sondern von dir frei gewählt.

            1. Wie kann ich der Funktion mitteilen, ob sie numerisch oder alphanumerisch sortieren soll (zur Zeit wird "111" vor "12" gereiht)?

            Auch das ist logisch, denn deine Vergleichsfunktion ist "strcmp", und die vergleicht eben alphanumerisch.

            Deine Vergleichsfunktion soll einen der drei Werte -1, 0 oder 1 zurückgeben, je nachdem, ob $a oder $b als "weiter nach vorn sortieren" zu betrachten ist.

            Dazu kannst du beliebig komplexe Operationen anstellen, Abfragen machen etc. Auch nach mehreren Feldern nacheinander sortieren geht natürlich. Als Beispiel: Wenn in einem Datensatz (nicht deinen, sondern irgendwelchen) ein Firmenname sowie Nachname und Vorname angegeben sein können, wäre es kein Problem, eine Sortierfunktion zu schreiben, die in der Reihenfolge "Firma, Nachname, Vorname" sortiert, wenn es einen Firmennamen gibt, aber auch nur "Nachname, Vorname" verwendet, wenn keine Firma angegeben ist, und als letzte Alternative auch einfach nur "Vorname" benutzt. So würden Vorname "Ralf", Nachname "Rautenberg" und Firmenname "RZ Uni" in dieser Reihenfolge sortiert werden.

            Natürlich benötigst du, wenn du unterschiedliche Sortierfunktionen benutzen willst, auch unterschiedliche usort()-Befehle, wenn du die Sortierfunktion fest angibst. Aber: Der Sortierfunktionsname ist im usort()-Aufruf ein String. Nutze das ggf. einfach aus. usort($array, $sortierfunktionsname) ist erlaubt.

            - Sven Rautenberg

            1. Hallo Sven!

              Recht herzlichen Dank für Deine Bemühungen.

              function cmp ($a, $b) {
                   return strcmp($a["osname"], $b["osname"]);
                   }
                 usort($trans, "cmp");

              Dann brauchst du eine andere Sortierfunktion. Der Name cmp ist ja nicht fest vorgegeben, sondern von dir frei gewählt.

              Eben! Und _das_ verstehe ich nicht.
              Schon gestern habe ich so begonnen die Funktion umzubenennen, um ein Argument zu erweitern und - um ganz sicher zu gehen - inhaltlich leeren. Also:
                  function bla ($a, $b, $c) {
                     }
              mit
                  usort ($trans,"bla");
              Belohnt werde ich vom PHP mit
                  Missing argument 3 for bla() in d:\programme\wamp\apache\htdocs\photonic\rspicslocal.php.
              OK, ich sehe nirgendwo ein Befüllen von a, b und c, aber im Früchte-Beispiel des Manuals auch nicht. Und daran komme ich nicht vorbei, hab ein Brett vorm Kopf, mir erscheint das mittlerweile wie die Suche einer Ecke am Hühnerei. Zeigt Du es mir?

              ... die in der Reihenfolge "Firma, Nachname, Vorname" sortiert, Ja!!!!

              Beste Grüße
              Viennamade

              1. Moin!

                Dann brauchst du eine andere Sortierfunktion. Der Name cmp ist ja nicht fest vorgegeben, sondern von dir frei gewählt.
                Eben! Und _das_ verstehe ich nicht.

                Zuerst die Namensgebung. Statt "function cmp... usort($trans, 'cmp')..." kannst du problemlos auch "function abc ... usort($trans, 'abc')..." machen.

                Du kannst also problemlos erstens deine Vergleichsfunktion beliebig nennen, als auch zwei verschiedene Vergleichsfunktionen "cmp1" und "cmp2" benutzen, und je nach Anforderung an das Sortieren usort($trans, "cmp1") oder usort($trans,"cmp2") aufrufen, wenn du je nach Code mal nach der einen oder der anderen Art sortieren willst.

                Ist die Namensgebung der Vergleichsfunktionen damit klar?

                Schon gestern habe ich so begonnen die Funktion umzubenennen, um ein Argument zu erweitern und - um ganz sicher zu gehen - inhaltlich leeren.

                Das sind ja gleich drei Dinge auf einmal - das geht nun wirklich nicht.

                Der wichtigste Punkt ist: Die Funktion kann nur zwei Parameter entgegennehmen. Punkt, Ende, Aus. Mehr sind nicht erlaubt. Wenn du also in die _eine_ Funktion noch die Information "mal so, mal anders sortieren" reinkriegen wolltest, blieben dir nurmehr globale Variablen oder das Verstecken dieser Information in einer der superglobalen Variablen. Beides ist eklig! Nicht machen! :)

                OK, ich sehe nirgendwo ein Befüllen von a, b und c, aber im Früchte-Beispiel des Manuals auch nicht. Und daran komme ich nicht vorbei, hab ein Brett vorm Kopf, mir erscheint das mittlerweile wie die Suche einer Ecke am Hühnerei. Zeigt Du es mir?

                Die Sache ist relativ einfach, wenn man weiß, wie in allen Programmiersprachen die Sortieralgorithmen realisiert werden. Deshalb als Grundlagenwissen für dich: Sortieren ist immer: Vergleichen zweier Elemente der zu sortierenden Menge, und ggf. ihr Vertauschen. Die Optimierungsmöglichkeiten eines Sortierungsalgorithmus liegen in der Wahl der zwei Elemente, aber das Vergleichen und Vertauschen muß jeder Algorithmus immer auf die gleiche Weise veranstalten.

                Und das Vergleichen zweier Elemente steuert dabei, wie die Menge sortiert wird: Alphabetisch, numerisch, nach Stringlänge, mit "natürlicher Sortierung", ...

                Die Funktion usort() nimmt nach einem optimierten Algorithmus also jeweils zwei Elemente aus deinem Array, und ruft mit diesen zwei Elementen als Parametern die Vergleichsfunktion auf. Andere sort-Funktionen rufen direkt intern existierende PHP-Funktionen auf, bei usort() hast du eben die totale Freiheit, das selbst zu bestimmen. Deswegen gibts z.B. auch kein usorti() für case-insensitives Sortieren - das mußt du im Bedarfsfall selbermachen.

                Die Vergleichsfunktion muß als Rückgabewert -1, 0 oder 1 zurückgeben, je nachdem, ob der erste übergebene Wert kleiner, gleich oder größer als der zweite übergebene Wert ist. Wenn der Wert 1 kleiner ist, bedeutet das für die Sortierung, dass er weiter nach vorne als Wert 2 gehört, ist er größer, dann gehört er weiter nach hinten, und bei Gleichheit sind sie eben gleich.

                Das bedeutet für dich als Programmierer der Vergleichsfunktion: Du kriegst die zwei Werte, die traditionell $a und $b genannt werden, und mußt nun entscheiden, welcher dieser Werte nach deinen Sortierkriterien weiter vorn in der sortierten Liste stehen soll.

                Wenn du numerisch aufsteigend sortieren willst, müssen die kleinen Zahlen vor den großen Zahlen kommen. Also gehört $a dann vor $b, wenn es kleiner ist - der Rückgabewert dafür ist -1.

                Als ganz banales Beispiel wäre das:
                function cmp ($a, $b)
                {
                  if ($a < $b) return -1;
                  if ($a > $b) return 1;
                  return 0;
                }
                usort($array, "cmp");

                Den gleichen Effekt (numerisches Sortieren) würdest du natürlich auch mit "sort($array, SORT_NUMERIC)" erreichen können (siehe http://de2.php.net/manual/de/function.sort.php).

                Aber sobald du ein verschachteltes Array hast, mußt du eben zu deiner eigenen Sortierfunktion greifen:
                function cmp ($a, $b)
                {
                  if ($a['intwert'] < $b['intwert']) return -1;
                  if ($a['intwert'] > $b['intwert']) return 1;
                  return 0;
                }

                Und wenn die Sortierfunktion was anderes tun soll, als nach Zahlenwerten zu sortieren, oder bei Gleichheit der Zahlenwerte noch andere Felder vergleichen, mußt du das eben nach deinen Wünschen programmieren. :)

                - Sven Rautenberg

                1. Hallo Sven!

                  Wiederum Danke!
                  Leider muß ich mich weiter als Problemfall beweisen.

                  Ich habe verstanden,
                   daß die Funktion von mir frei benannt werden kann.
                   daß ich "beliebig" viele Funktionen für unterschiedliche Sortierungsarten schreiben darf.
                   daß diese Funktionen jeweils nur zwei Argumente haben dürfen und was sie mit diesen beiden treiben.
                   daß eine globale Variable zur Steuerung der Funktion nicht schön ist - das hast du mir bereits vor 5 Monaten erklärt, http://forum.de.selfhtml.org/archiv/2003/12/65574/#m373791 und ich habe mich immer daran gehalten, wenngleich ich seitdem leider viel mehr mit HTML denn mit PHP beschäftigt war, das ändert sich jetzt aber.

                  Aber aus all dem komme ich nur auf eine mir umständliche erscheinende Konstruktion:

                  function cmposname ($a, $b)
                      {
                         return strcmp($a['osname'], $b['osname']);
                      }
                      function cmppxhoehe ($a, $b)
                      {
                        if ($a['pxhoehe'] < $b['pxhoehe']) return -1;
                        if ($a['pxhoehe'] > $b['pxhoehe']) return 1;
                        return 0;
                      }
                      function cmppxbreite ($a, $b)
                      {
                        if ($a['pxbreite'] < $b['pxbreite']) return -1;
                        if ($a['pxbreite'] > $b['pxbreite']) return 1;
                        return 0;
                      }

                  switch ($sort)
                      {
                        case "osname":
                          usort($trans, "cmposname");
                          break;
                        case "pxhoehe":
                          usort($trans, "cmppxhoehe");
                          break;
                        case "pxbreite":
                          usort($trans, "cmppxbreite");
                          break;
                      }

                  Jetzt hat ja der Array $trans nicht nur osname, pxhoehe, pxbreite, sondern noch weitere 6 Assoziationen - halb mit numerischen, halb mit alphanumerischen Werten - und das macht doch ziemlich viel Quelltext? So wie ich das jetzt sehe, wäre es nett im Script eine Funktion zu schreiben welche die 9 Funktionen erstellt:-)
                  Brr, einen Geistesblitz bräuchte ich.

                  Danke
                  Viennamade

                  1. Moin!

                    Leider muß ich mich weiter als Problemfall beweisen.

                    Macht nix. Ich bin Kummer gewohnt. :)

                    Ich habe verstanden,

                    Das klingt doch alles schon mal gut.

                    Aber aus all dem komme ich nur auf eine mir umständliche erscheinende Konstruktion:

                    Um die Definition von einzelnen Sortierfunktionen für jedes zu sortierende Feld kommst du nicht herum. Die Frage wäre, ob das Sortieren anderweitig von einer Datenbank übernommen werden könnte - dazu müßtest du ja aber eine verwenden. :)

                    switch ($sort)
                        {
                          case "osname":
                            usort($trans, "cmposname");
                            break;
                          case "pxhoehe":
                            usort($trans, "cmppxhoehe");
                            break;
                          case "pxbreite":
                            usort($trans, "cmppxbreite");
                            break;
                        }

                    Aber um diesen Switch kommst du herum. Da ja der Name der Sortierfunktion nur ein String in usort() ist, kannst du einfach so operieren:

                    usort($trans, "cmp".$sort);

                    Vorausgesetzt, dass in $sort immer nur die Strings drinstehen, die als Indexwerte des Arrays verwendet werden und die du als Funktionen definiert hast, und nichts anderes (ansonsten gibts Fehlermeldungen statt sortierten Arrays).

                    Jetzt hat ja der Array $trans nicht nur osname, pxhoehe, pxbreite, sondern noch weitere 6 Assoziationen - halb mit numerischen, halb mit alphanumerischen Werten - und das macht doch ziemlich viel Quelltext? So wie ich das jetzt sehe, wäre es nett im Script eine Funktion zu schreiben welche die 9 Funktionen erstellt:-)

                    Nun ja, im Prinzip kann dir geholfen werden: Du kannst zur Laufzeit des Skriptes Code generieren.

                    Du kannst mit Stringfunktionen den PHP-Code zusammenbasteln, den du jeweils benötigst, um zu sortieren. Siehe dazu http://www.php.net/create_function, Beispiel 3. Wobei dein Problem sich kaum mit einer einzelnen Programmzeile erschlagen lassen dürfte. Du mußt ja wissen, ob du auf String- oder Nummernbasis sortieren willst, also brauchst du zwingend eine Abfrage vorgeschaltet. Dann allerdings reichen zwei verschiedene usort() aus, einer mit numerischer Sortierung, und einer mit strcmp-Sortierung, in den jeweils das zu sortierende Arrayfeld als Stringvariable dynamisch eingefügt wird.

                    Ich kann mir aber nicht vorstellen, dass solch eine Aktion wahnsinnig übersichtlich wird. Solltest du den Überblich verlieren: Einfach die 9 Sortierfunktionen definieren und als String zusammengesetzt an usort() übergeben, wie ich oben angegeben habe.

                    - Sven Rautenberg

                    1. Hallo Sven!

                      Um die Definition von einzelnen Sortierfunktionen für jedes zu sortierende Feld kommst du nicht herum. Die Frage wäre, ob das Sortieren anderweitig von einer Datenbank übernommen werden könnte - dazu müßtest du ja aber eine verwenden. :)

                      Ich verwende eine DB und erweitere das Abfragearray um Informationen die nicht in der DB stehen. Daher kann ich SORT BY nicht verwenden.

                      Aber um diesen Switch kommst du herum. Da ja der Name der Sortierfunktion nur ein String in usort() ist, kannst du einfach so operieren:
                                         v
                      usort($trans, "cmp".$sort);

                      v
                                            v
                                            v
                                 -----------------------------
                                 Freude, schöner Götterfunken,
                                 Tochter aus Elysium,
                                 Wir betreten feuertrunken,
                                 Himmlische, deine Stringsyntax.
                                 Deine Zauber binden wieder,
                                 Was die Mode streng getheilt;

                      http://www.php.net/create_function, Beispiel 3.

                      Ich kann mir aber nicht vorstellen, dass solch eine Aktion wahnsinnig übersichtlich wird.

                      Ich auch nicht - aber: Was es nicht alles gibt!

                      Recht herzlichen Dank! Am liebsten würde ich ja noch Zierleisten machen :-)
                      Danke auch an Andreas Götz!

                      Beste Grüße
                      Viennamade