Felix Riesterer: usort() - Verzweiflung naht!

Liebes Forum,

ich möchte eine Personenliste mittels usort sortieren.

Jede Person ist als ein Array angelegt, das ich der Einfachheit halber hier so angebe:

$person[x] = Array ( [0] => Vorname
                     [1] => Nachname
                     [2] => Funktion )

Nun soll in der Callback-Funktion eigentlich nur nach Nachnamen und Vornamen sortiert werden, aber bei einer bestimmten Funktion der Person soll diese ganz nach oben in der Liste.

Meine Lösung bisher:

function sonstige_sortieren($person1, $person2)  
   {  
   // Nachnamen vergleichen  
   if($person1[1] != $person2[1]) return strcmp($person1[1], $person2[1]);  
   //Vornamen vergleichen  
   if($person1[0] != $person2[0]) return strcmp($person1[2], $person2[0]);  
   return 0;  
   }  
  
function spezial_sortieren($person1, $person2)  
   {  
   if(substr_count($person1[2], "Funktion") > 0 || substr_count($person1[2], "Funktion") > 0)  
      {  
      // "Funktionär" dabei?  
      if(substr_count($person1[2], "Funktion") > 0 && substr_count($person2[2], "Funktion") < 0) return 1; // ja, ersterer  
      if(substr_count($person1[2], "Funktion") < 0 && substr_count($person2[2], "Funktion") > 0) return -1; // ja, zweiterer  
      }  
   // weiter vergleichen  
   return sonstige_sortieren($person1, $person2);  
   }

Keine Ahnung, ob meine Idee an sich schon einen Denkfehler enthält... HILFE!

Liebe Grüße aus Ellwangen,

Felix Riesterer.

  1. Moin!

    ich möchte eine Personenliste mittels usort sortieren.

    Den Code dafür zeigst du aber nicht. :)

      
    
    > $person[x] = Array ( [0] => Vorname  
    >                      [1] => Nachname  
    >                      [2] => Funktion )  
    
    

    Numerische Indices scheinen mir irgendwie unpraktisch für die Aufgabe zu sein.

    Ich hätte es so gemacht:

      
    $person[x] = Array ( ['vorname'] => "Ein Vorname",  
                         ['nachname'] => "Ein Nachname",  
                         ['funktion'] => "eine Funktion" )  
    
    

    Ändert an der grundsätzlichen Sortierung aber nichts.

    Nun soll in der Callback-Funktion eigentlich nur nach Nachnamen und Vornamen sortiert werden, aber bei einer bestimmten Funktion der Person soll diese ganz

    nach oben in der Liste.

    Du hast also eine ganz normale dreistufige Sortierung:
    Stufe 1: Spezielle Funktionen ganz nach vorne, alles andere dahinter.
    Stufe 2: Innerhalb der Stufe "Funktion" die Nachnamen alphabetisch von vorn bis hinten.
    Stufe 3: Innerhalb der Stufe "Nachname" die Vornamen alphabetisch von vorn bis hinten.

      
    
    > function sonstige_sortieren($person1, $person2)  
    >    {  
    >    // Nachnamen vergleichen  
    >    if($person1[1] != $person2[1]) return strcmp($person1[1], $person2[1]);  
    >    //Vornamen vergleichen  
    >    if($person1[0] != $person2[0]) return strcmp($person1[2], $person2[0]);  
      
    // Hier ist ein Tippfehler, du vergleichst Index 2 und 0 miteinander.  
      
    
    >    return 0;  
    >    }  
      
    // Außerdem: Diese Funktion realisiert eine zweistufige Sortierung doch schon. Warum nicht Dreistufigkeit einbauen?  
      
    // Dann bräuchtest du auch dies hier nicht mehr:  
    
    > function spezial_sortieren($person1, $person2)  
    >    {  
    >    if(substr_count($person1[2], "Funktion") > 0 || substr_count($person1[2], "Funktion") > 0)  
    >       {  
    >       // "Funktionär" dabei?  
    >       if(substr_count($person1[2], "Funktion") > 0 && substr_count($person2[2], "Funktion") < 0) return 1; // ja, ersterer  
    >       if(substr_count($person1[2], "Funktion") < 0 && substr_count($person2[2], "Funktion") > 0) return -1; // ja, zweiterer  
    >       }  
    >    // weiter vergleichen  
    >    return sonstige_sortieren($person1, $person2);  
    >    }  
    
    

    - Sven Rautenberg

    --
    My sssignature, my preciousssss!
    1. Lieber Sven,

      Danke für Deine Antwort! Einige Deiner Empfehlungen habe ich jetzt umgesetzt. War zwar eine tiefergehende Modifikation meines Codes, aber es hat sich sicherlich schon gelohnt!

      Den Code dafür zeigst du aber nicht. :)

      $person[x] = Array ( [0] => Vorname
                           [1] => Nachname
                           [2] => Funktion )

      Naja, ich dachte das wäre klar: `usort($person, "speziel_sortieren")`{:.language-php}  
        
        
      
      > Numerische Indices scheinen mir irgendwie unpraktisch für die Aufgabe zu sein.  
      >   
      > Ich hätte es so gemacht:  
      > ~~~php
        
      
      > $person[x] = Array ( ['vorname'] => "Ein Vorname",  
      >                      ['nachname'] => "Ein Nachname",  
      >                      ['funktion'] => "eine Funktion" )
      
      

      Schon umgesetzt!

      Ändert an der grundsätzlichen Sortierung aber nichts.

      Aber erleichtert plötzlich andere Stellen in meinem Script!! Danke!!

      Du hast also eine ganz normale dreistufige Sortierung:
      Stufe 1: Spezielle Funktionen ganz nach vorne, alles andere dahinter.
      Stufe 2: Innerhalb der Stufe "Funktion" die Nachnamen alphabetisch von vorn bis hinten.
      Stufe 3: Innerhalb der Stufe "Nachname" die Vornamen alphabetisch von vorn bis hinten.

      Stimmt.

      // Außerdem: Diese Funktion realisiert eine zweistufige Sortierung doch schon. Warum nicht Dreistufigkeit einbauen?

      Berechtigter Einwand! Schon geändert!

      Meine aktuelle Version sieht jetzt wie folgt aus, führt aber noch immer nicht zum gewünschten Ergebnis. Anscheinend wird die "erste Stufe" der Überprüfung nicht (oder anders, als ich es beabsichtige) durchgeführt.

      Das Array sieht so aus:

      $person = Array (  
             [0] => Array( ['vorname'] => "Ein Vorname",  
                           ['nachname'] => "Ein Nachname",  
                           ['funktion'] => "eine Funktion" )  
             [1] => Array( ['vorname'] => "Ein weiterer Vorname",  
                           ['nachname'] => "Ein weiterer Nachname",  
                           ['funktion'] => "eine weitere Funktion" )  
        
        
      function personen_sortieren($person1, $person2)  
         {  
         // auf "Funktion" prüfen, denn die muss an oberster Stelle stehen!  
         if(substr_count($person1['aufgaben'], "Funktion") > 0 || substr_count($person1['aufgaben'], "Schulleitung") > 0)  
            {  
            if(substr_count($person1['aufgaben'], "Funktion") > 0 && substr_count($person2['aufgaben'], "Funktion") < 1) return 1;  
            if(substr_count($person1['aufgaben'], "Funktion") < 1 && substr_count($person2['aufgaben'], "Funktion") > 0) return -1;  
            if(substr_count($person1['aufgaben'], "stellvertretende") > 0 && substr_count($person2['aufgaben'], "stellvertretende") < 1) return -1;  
            if(substr_count($person1['aufgaben'], "stellvertretende") < 1 && substr_count($person2['aufgaben'], "stellvertretende") > 0) return 1;  
            }  
         // Nachnamen vergleichen  
         if($person1['nachname'] != $person2['nachname']) return strcmp($person1['nachname'], $person2['nachname']);  
         //Vornamen vergleichen  
         if($person1['vorname'] != $person2['vorname']) return strcmp($person1['vorname'], $person2['vorname']);  
         // Anreden vergleichen  
         if($person1['anrede'] != $person2['anrede']) return strcmp($person1['anrede'], $person2['anrede']);  
         return 0;  
         }
      

      Liebe Grüße aus Ellwangen,

      Felix Riesterer.

      1. Moin!

        Den Code dafür zeigst du aber nicht. :)

        Naja, ich dachte das wäre klar: usort($person, "speziel_sortieren")

        Der Teufel steckt manchmal im Detail. ;)

        Ändert an der grundsätzlichen Sortierung aber nichts.
        Aber erleichtert plötzlich andere Stellen in meinem Script!! Danke!!

        Ok, dann solltest du für deine Aufgabe dein Array noch weiter umstrukturieren, sonst wird es nämlich eklig unübersichtlich. :)

        Meine aktuelle Version sieht jetzt wie folgt aus, führt aber noch immer nicht zum gewünschten Ergebnis. Anscheinend wird die "erste Stufe" der Überprüfung nicht (oder anders, als ich es beabsichtige) durchgeführt.

        Das Array sieht so aus:

        $person = Array (

        [0] => Array( ['vorname'] => "Ein Vorname",
                             ['nachname'] => "Ein Nachname",
                             ['funktion'] => "eine Funktion" )
               [1] => Array( ['vorname'] => "Ein weiterer Vorname",
                             ['nachname'] => "Ein weiterer Nachname",
                             ['funktion'] => "eine weitere Funktion" )

        Ich stelle fest, dass du hier 'funktion' hast, weiter unten aber in Stufe 1 nach 'aufgaben' sortierst. Paßt nicht zusammen, würde ich meinen. :)

        Außerdem halte ich das Platzieren aller möglichen Aufgaben in einem String für sehr unschön. Da muß man dann immer Stringfunktionen anwenden, um einzelne Aufgaben herauszufinden, sie können vorne, in der Mitte oder ganz hinten stehen, schön durcheinander ("Stellvertretender Schulleiter Vertrauenslehrer" ist das jetzt der Stellvertretende Vertrauenslehrer?), und insgesamt ist die Handhabbarkeit nicht wirklich nett.

        Ich empfehle daher, die Aufgaben (oder Funktionen - aber bitte einheitlich nutzen) anders zu strukturieren, nämlich datenbankorientiert:

        Du hast ein Array "Aufgaben", in dem der Wichtigkeit nach (für die Sortierung) alle Aufgaben verzeichnet sind. Ein kleiner Index signalisiert hohe Wichtigkeit beim Sortieren:

        [code lang=php]
        $aufgaben = array('Schulleiter','Stellv. Schulleiter','Sekretariat','Lehrer')

          
        Und dein Personenarray nimmt jeweils auf diese Funktionen Bezug:  
        ~~~php
          
        $personen = array(  
               [0] => Array( ['vorname'] => "Ein Vorname",  
                             ['nachname'] => "Ein Nachname",  
                             ['funktion'] => array (0,3), //Schulleiter, Lehrer  
               [1] => Array( ['vorname'] => "Ein weiterer Vorname",  
                             ['nachname'] => "Ein weiterer Nachname",  
                             ['funktion'] => array(2), // Sekretariat  
           )  
        
        

        Deine Sortierung muß also nur den Minimalwert herausfinden, der im Array ['funktion'] gespeichert ist, und den kleineren Wert nach vorne sortieren.

        Eine Funktion für das Minimum im Array ist min().

          
        
        > function personen_sortieren($person1, $person2)  
        >    {  
        
              // Funktionen vergleichen  
              if (min($person1['funktion']) != min($person2['funktion']) return (min($person1['funktion']) < min($person2['funktion']) ? -1 : 1);  
              // Das Minimum (ranghöchste Funktion) vergleichen und je nachdem -1 oder 1 zurückgeben.  
        
        >    // Nachnamen vergleichen  
        >    if($person1['nachname'] != $person2['nachname']) return strcmp($person1['nachname'], $person2['nachname']);  
        >    //Vornamen vergleichen  
        >    if($person1['vorname'] != $person2['vorname']) return strcmp($person1['vorname'], $person2['vorname']);  
        >    // Anreden vergleichen  
        >    if($person1['anrede'] != $person2['anrede']) return strcmp($person1['anrede'], $person2['anrede']);  
        >    return 0;  
        >    }  
        
        

        - Sven Rautenberg

        --
        My sssignature, my preciousssss!
        1. Lieber Sven,

          in der Sortierreihenfolge müssen nur zwei Personen nach vorne sortiert werden, der Rest ist alphabetisch und nicht nach hierarchischen Ordnungen von anderen Aufgaben sortiert. Daher suche ich so "umständlich" in den Strings, weil ich sonst ein Unterarray (nach Deinem Vorschlag $person['aufgaben'][x]) durchforsten müsste. Da war mir die Umständlichkeit der String-Funktionen einfacher, als weitere Zeilen Code für das Zurechtbasteln eines geeigneten Unter-Arrays zu schreiben.

          Mittlerweile habe ich mit preg_replace() anstelle von substr_count wohl wegen des unterschiedlichen Rückgabewertes (Dank an dedlfix!) eine bessere Lösung gefunden. Es funzt[TM] jetzt so wie beabsichtigt.

          Endergebnis: ~~~php // Callback-Funktion für Sortierreihenfolge; müssen [-1|0|1] zurückgeben!

          function personen_sortieren($person1, $person2)
             {
             // auf Schulleitung prüfen, denn die muss an oberster Stelle stehen!
             if(preg_match("/Schulleitung/", $person1['aufgaben']) || preg_match("/Schulleitung/", $person2['aufgaben']))
                {
                if(preg_match("/Schulleitung/", $person1['aufgaben']) && !preg_match("/Schulleitung/", $person2['aufgaben'])) return -1;
                if(!preg_match("/Schulleitung/", $person1['aufgaben']) && preg_match("/Schulleitung/", $person2['aufgaben'])) return 1;
                if(preg_match("/stellvertretende/", $person1['aufgaben']) && !preg_match("/stellvertretende/", $person2['aufgaben'])) return 1;
                if(!preg_match("/stellvertretende/", $person1['aufgaben']) && preg_match("/stellvertretende/", $person2['aufgaben'])) return -1;
                }
             // Nachnamen vergleichen
             if($person1['nachname'] != $person2['nachname']) return strcmp(strtolower($person1['nachname']), strtolower($person2['nachname']));
             //Vornamen vergleichen
             if($person1['vorname'] != $person2['vorname']) return strcmp(strtolower($person1['vorname']), strtolower($person2['vorname']));
             // Anreden vergleichen
             if($person1['anrede'] != $person2['anrede']) return strcmp(strtolower($person1['anrede']), strtolower($person2['anrede']));
             return 0;
             }

            
          Deine Einwände waren sehr anregend und hilfreich für mich. Mit dem Umstellen auf eine assoziative Struktur meiner Arrays habe ich tatsächlich vieles vereinfachen können!  
            
          Danke!  
            
          Liebe Grüße aus [Ellwangen](http://www.ellwangen.de/),  
            
          Felix Riesterer.
          
          1. Moin!

            Mittlerweile habe ich mit preg_replace() anstelle von substr_count wohl wegen des unterschiedlichen Rückgabewertes (Dank an dedlfix!) eine bessere Lösung gefunden. Es funzt[TM] jetzt so wie beabsichtigt.

            Naja, das, was du in den Strings suchst, sollte auch mit strpos() zu finden sein. Wenn es nicht vorkommt, ist (strpos(...)===false) wahr. Wenn es vorkommt, ist (strpos(...)!==false) wahr.

            Und eine logische Reihenfolge kann man natürlich auch innerhalb der Suchfunktion herstellen, indem man gefundene Teilstrings in Werte übersetzt und die dann vergleicht, wie gezeigt.

              
                // Schulleitung im String ergibt 2 "Punkte", sonst 0.  
                $p1ord = (strpos($person1['aufgaben'],"Schulleitung")===false?0:2);  
                $p2ord = (strpos($person2['aufgaben'],"Schulleitung")===false?0:2);  
              
                // "stellvertretende" im String ergibt 1 "Punkt"  
                $p1ord = (strpos($person1['aufgaben'],"stellvertretende")===false?0:1);  
                $p2ord = (strpos($person2['aufgaben'],"stellvertretende")===false?0:1);  
              
                if($p1ord != $p2ord)  
                {  
                  return ($p1ord > $p2ord? -1 : 1);  
                )  
            
            >    // Nachnamen vergleichen  
            >    if($person1['nachname'] != $person2['nachname']) return strcmp(strtolower($person1['nachname']), strtolower($person2['nachname']));  
            >    //Vornamen vergleichen  
            >    if($person1['vorname'] != $person2['vorname']) return strcmp(strtolower($person1['vorname']), strtolower($person2['vorname']));  
            >    // Anreden vergleichen  
            >    if($person1['anrede'] != $person2['anrede']) return strcmp(strtolower($person1['anrede']), strtolower($person2['anrede']));  
            >    return 0;  
            >    }  
            
            

            - Sven Rautenberg

            --
            My sssignature, my preciousssss!
  2. echo $begrüßung;

    Keine Ahnung, ob meine Idee an sich schon einen Denkfehler enthält... HILFE!

    Ja, ich denke schon. substr_count() liefert keine negativen Werte zurück und wenn du zwei Funktionäre hast werden die nicht sortiert. So geht's einfacher:

    return strcmp(
      (int)!(bool)substr_count($person1[2], 'Funktion') . $person1[1] . $person1[0],
      (int)!(bool)substr_count($person2[2], 'Funktion') . $person2[1] . $person2[0]);

    Der erste Teil ermittelt die Anzahl der Vorkommen von "Funktion" im Feld 2 (sprechende Keys gefielen mir besser) und gibt nach den beiden Typecasts 0 oder 1 zurück. Der bool-Typecast wandelt 0 oder 1..n in die beiden Werte false oder true um, die der int-Typecast zu 0 oder 1 umwandelt. Ohne den int-Typecast gibts Leerstring und 1, also ein jeweils unterschiedlich langes Ergebnis. Ohne den bool-Typecast gibt es das auch, wenn mehr als 9 "Funktion" gefunden werden. Außerdem soll doch sicherlich nicht die Anzahl der "Funktion" zum Sortierkriterium werden. Dazwischen liegt noch eine Negation, die aus Funktionären eine 0 macht (wenn sie das nicht schon sowieso sind *g*). Die Umwandlung in String bekommt PHP durch die Stringverknüpfung mit Nachname und Vorname selbst hin.

    Durch die Großscheibung des ersten Buchstabens eines Namens und dessen Beachtung bei strcmp() werden auch MeiereiAnton und MeierEike richtig sortiert (letzterer zuerst).

    echo "$verabschiedung $name";

    1. Lieber dedlfix,

      return strcmp(
        (int)!(bool)substr_count($person1[2], 'Funktion') . $person1[1] . $person1[0],
        (int)!(bool)substr_count($person2[2], 'Funktion') . $person2[1] . $person2[0]);

      Danke für Deine Hilfe! Trotz Deiner ausführlichsten Erläuterung werde ich momentan noch nicht schlau aus Deinem Code... :-(
      Aber ich studiere ihn noch weiter! Schließlich will ich ja den Grundgedanken dahinter verstehen...

      Liebe Grüße aus Ellwangen,

      Felix Riesterer.

      1. echo $begrüßung;

        return strcmp(
          (int)!(bool)substr_count($person1[2], 'Funktion') . $person1[1] . $person1[0],
          (int)!(bool)substr_count($person2[2], 'Funktion') . $person2[1] . $person2[0]);

        Danke für Deine Hilfe! Trotz Deiner ausführlichsten Erläuterung werde ich momentan noch nicht schlau aus Deinem Code... :-(
        Aber ich studiere ihn noch weiter! Schließlich will ich ja den Grundgedanken dahinter verstehen...

        Na dann will ich mal noch ein paar Worte dazu verlieren.

        substr_count() zählt die Anzahl der Vorkommen eines bestimmten Strings (hier "Funktion") in einem anderen String ($personX[2]). Das Ergebnis ist eine Zahl (Integer-Wert).[*] Von Belang ist aber nur, zu wissen, ob "Funktion" vorhanden ist oder nicht. Wenn nicht => 0 (entspricht false) - wenn ja => >0 (entspricht true). Ein Typecast nach bool erledigt die Umwandlung.

        Zwischenstand:
        Funktionär = true (entspricht 1)
        Fußvolk = false (entspricht 0)

        Bei einer Sortierung würde 0 vor 1 kommen. Damit die Funktionäre "nach oben" kommen werden die durch die Negation zu false und das Fußvolk zur true. Der Typecast nach integer ergibt 0 bzw. 1.

        Zwischenstand:
        Funktionär = 0
        Fußvolk = 1

        [**]

        Diese Ziffer, die ja auch als einstelliger String dargestellt werden kann, stellt die erste Sortierstufe dar. 0(null)irgendwas ist immer kleiner als 1(eins)igendwas - die Nullen kommen an die Spitze, aber das hab ich ja schon gesagt.

        An die Ziffer wird direkt dahinter der Nachname und der Vorname gehängt, so dass sich solche Konstrukte ergeben:

        1RiestererFelix
        1RiesterErwin

        (Funktionäre sind schon "wegsortiert".) Riester ist kleiner als Riesterer, muss also zuerst kommen. Da Vornamen mit Großbuchstaben beginnen wird das hier trotz des gleichen Buchstabens (e<->E) erreicht.

        echo "$verabschiedung $name";

        [*] strpos() täte es auch, liefert aber eine Positionsangabe, die auch 0 sein kann, und damit auch als false angesehen werden kann. Um das vom false bei Nichtvorhandensein zu unterscheiden, braucht's ein etwas umfangreicheres Konstrukt als einen Typecast.

        [**] Statt (int)!(bool)substr_count($person1[2], 'Funktion') hätte man auch (substr_count($person1[2], 'Funktion') ? '0' : '1') nehmen können. Das ist nicht kürzer aber vielleicht besser verständlich.

        1. Lieber dedlfix,

          [**] Statt (int)!(bool)substr_count($person1[2], 'Funktion') hätte man auch (substr_count($person1[2], 'Funktion') ? '0' : '1') nehmen können. Das ist nicht kürzer aber vielleicht besser verständlich.

          AHAAAA!!! Das sagt mir allerdings etwas!

          Nochmals vielen herzlichen Dank für Deine sehr ausführliche Hilfe! Heute habe ich wieder etwas neues gelernt. Auch wenn ich nun statt substr_count() die Funktion preg_match() verwende (was wahrscheinlich weniger performant ist, aber egal!), hast Du mir wertvolle Denkanstöße für die Zukunft gegeben!

          Liebe Grüße aus Ellwangen,

          Felix Riesterer.