WernerK: Array Max Min ermitteln und zusammensetzen

Hallo zusammen,

Ich habe ein Array das wie folgt aussieht:

[myname] => Array 
        (        
		[27] => 2019-03-04 03:45:00.000  
		[28] => 2019-03-04 05:50:00.000      
		[29] => 2019-03-04 06:25:00.000    
		) 

Ich muss nun das niedrigste Datum und höchste Datum finden und für diese dann jeweils 60 min addieren, bzw. subtrahieren. Ich bekomme es zwar so hin:

$maxtime = max($time_arr[$key]);
$mintime = min($time_arr[$key]);

Aber wie kann man diese Werte nun wieder der ID und dem User zuordnen? Ich bräuchte also im Ergebnis: myname => [27] => 2019-03-04 02:45:00.000
[28] => 2019-03-04 05:50:00.000
[29] => 2019-03-04 07:25:00.000

ich hoffe es war einigermaßen verständlich 😀

Gruss Werner

  1. Tach!

    Aber wie kann man diese Werte nun wieder der ID und dem User zuordnen?

    min()/max() sind dafür also offenbar nicht geeignet. Es sei denn, die Datümer sind so eindeutig, dass man darüber den Key findet.

    Eine Möglichkeit, den kleinsten und größten Wert zu finden und die Zuordnung zu den Keys nicht zu verlieren, ist, das Array mit einer Funktion zu sortieren, die auch die Key-Zuordnung bewahrt. Dann kannst du mit reset()/end() und key()/current() und auch mit array_key_first()/array_key_last() auf die gewünschten Elemente zugreifen.

    dedlfix.

  2. Hallo WernerK,

    das geht mit min und max nicht. Muss die Reihenfolge der Einträge in $time_arr[$key] unverändert bleiben? Ist es tatsächlich so, dass die Indexe nicht bei 0 beginnen?

    Zuerst mal einfache Lösungen mit Sortieren - die allerdings die Reihenfolge der Einträge im Timestamp-Array zu myname verändern. Je nach dem, wie man sortiert, könnte das aber kein Problem sein. Das musst Du beurteilen.

    1. Verwende sort($time_arr[$key]). Nachteil: Die Indexe lauten nachher nicht 27,28,29, sondern 0,1,2. Du findest den kleinsten Wert am Index 0, den größten Wert am Index 2 (also count($time_arr[$key])-1), und kannst die Stunde subtrahieren/addieren.

    2. Verwende asort($time_arr[$key]). Der sortiert die Indexe mit. Ja, richtig gelesen. Ein PHP Array hat eine Reihenfolge, die von den Indexwerten unabhängig ist (-> Doku). Wenn Du vorher Indexe 27,28,29 hattest, der kleinste Wert auf 28 steht und der größte auf 27, ist die Indexreihenfolge im Array nachher 28,29,27. Wenn Du das Array in Sortierfolge verarbeiten willst, nimmst Du foreach ($time_arr[$key] as $i => $ts). Den ersten Index bekommst Du mit array_key_first, den letzten mit array_key_last, und kannst dort die Stunde subtrahieren/addieren

    3. Mach es von Hand - dann bleiben die Indexe so wie sie sind, und nur der kleinste und größte Wert werden verändert:

    Da ich nicht weiß, wie deine Timestamps tatsächlich gespeichert sind, habe ich die Addition/Subtraktion der Stunde als Kommentar hingeschrieben, das wirst Du selbst können.

    // Code aus der Hüfte geschossen, bitte selber testen.
    function expand_min_max(&$arr) {
       $minPos = 0;
       $lastIndex = count($arr)-1;
       $maxPos = $lastIndex;
       for ($i=0; $i<=$lastIndex; $i++) {
          if ($arr[$i] < $arr[$minPos])
             $minPos = $i;
          else if ($arr[$i] > $arr[$maxPos])
             $maxPos = $i;
       }
       $arr[$minPos] = // $arr[$minPos] minus eine Stunde
       $arr[$maxPos] = // $arr[$maxPos] plus eine Stunde
    }
    
    // Und an der Stelle, wo Du es tun musst:
    
    expand_min_max($fooArray[$username]);
    

    Die Funktion empfängt einen Referenzparameter, d.h. wenn in $fooArray[$username] dein myname Array gespeichert ist, dann bekommt die aufgerufene Funktion die Möglichkeit, die Werte dieses Arrays zu ändern. Normalerweise lässt PHP das nicht zu, es übergibt eine Kopie[1]. D.h. wenn die Funktion zurückkehrt, ist das Array in $fooArray[$username] passend verändert.

    Wenn Du $fooArray[$username] nicht verändern willst, lass das & vor dem Parameter weg und gib das veränderte Array mit return zurück.

    Rolf

    --
    sumpsi - posui - clusi

    1. Okay, so etwas ähnliches wie eine Kopie. Übergeben wird ein Verweis auf das Array, das geht schnell und ohne Overhead, und der erste schreibende Zugriff erzeugt die Kopie. Das & vor dem Parameter von expand_min_max verhindert diesen Copy-On-Write Mechanismus und erlaubt die Manipulation des übergebenen Arrays. ↩︎

    1. Hallo Rolf,

      ganz herzlichen Dank für deine tolle Erklärung und Hilfe.

      Ja die Indexe 27,28,29 bräuchte ich tatsächlich so, weil diese eine Referenz zu einer anderen DB Tabelle sind.

      Wenn ich deinen Code richtig verstanden habe, gehst du nach dem ersten und letzten Eintrag des Arrays? Meine Idee war ursprünglich wirklich das reine Datum nach dem niedrigsten und höchsten zu untersuchen. Was würde z.b. passieren, wenn das Datum nicht aussteigend sortiert aus der DB kommt?

      Diese neuen Werte müssten dann so in der DB wieder gespeichert werden: -----------------------------------------—

      Name ID Datum

      myname 27 2019-03-04 02:45:00.000

      myname 28 2019-03-04 05:50:00.000

      myname 29 2019-03-04 07:25:00.000

      viele Grüße

      Werner

      1. Hallo WernerK,

        wenn Du den Index brauchst, ist mein Vorschlag 1 schon mal hinfällig.

        Vielleicht sollten wir aber mal die Voraussetzungen klar stellen. $time_arr[$key] ist also basierend auf einer DB-Abfrage entstanden, und in diesem Array sind die Keys irgendwelche IDs in der DB und die Values die Timestamps. Du brauchst zu den modifizierten Timestamps die unveränderten IDs.

        Und du fragst, was wäre, wenn die Timestamps nicht aufsteigend aus der DB kämen. Da möchte ich einhaken: Ob etwas aufsteigend aus der DB kommt oder nicht, das hast Du zumeist selbst in der Hand. Die ORDER BY Klausel in einer SQL Abfrage ist kein Hexenwerk. Du würdest ohne ORDER BY beispielsweise dieses DB-Ergebnis bekommen (Time statt Timestamp für kürzeren Text):

        Name     Key      Time
        Bar       25      17:00
        Foo       27      12:00
        Foo       28      19:00
        Bar       26      04:00
        Foo       29      05:00
        

        mit einem ORDER BY Name, Time wäre das

        Name     Key      Time
        Bar       26      04:00
        Bar       22      17:00
        Foo       29      05:00
        Foo       27      12:00
        Foo       28      19:00
        

        D.h. du hättest

        $time_arr = ARRAY(
              'Bar' => ARRAY( 26 => '04:00', 22 => '17:00' ),
              'Foo' => ARRAY( 29 => '05:00', 27 => '12:00', 28 => '19:00')
           );
        

        Die Frage ist, ob Dir diese Reihenfolge irgendwo Probleme macht. Wenn Du $time_arr['Foo'] mit einer foreach-Schleife durchläufst, besucht er die Keys 29, 27 und 28, in dieser Reihenfolge. Wenn das für Dich OK ist, kannst Du meinen Vorschlag 2 von oben verwenden und den asort einfach weglassen - das Ergebnis des asort ist ja bereits aus der DB gekommen.

        Mein Vorschlag 3 bleibt relevant, falls Du keinen ORDER BY und auch keinen asort machen kannst, d.h. die Einträge in anderer Reihenfolge in den Arrays brauchst. Er muss aber geändert werden, ich hatte übersehen dass Du die Keys für minimalen und maximalen Timestamp brauchst.

        function locate_min_max($arr) {
           $minKey = array_key_first($arr);
           $maxKey = $minKey;
           foreach ($arr as $key => $val) {
              if ($val < $arr[$minKey])
                 $minKey = $key;
              else if ($val > $arr[$maxKey])
                 $maxKey = $i;
           }
           return ARRAY('minKey' => $minKey, 'maxKey' => $maxKey);
        }
        

        Die Funktion rufst Du für ein $time_arr[...] auf und bekommst ein Assoziatives Array zurück mit den Schlüsseln für kleinsten und größten Timestamp. Damit kannst Du die Addition/Subtraktion durchführen, und auch den Update in die DB machen.

        $minmax = locate_min_max($time_arr[$name]);
        
        $time_arr[$name][$minmax['minKey']] = // 1 Stunde subtrahieren
        $time_arr[$name][$minmax['maxKey']] = // 1 Stunde addieren
        

        Du kannst auch auf die Funktion verzichten und den Code darin einfach an die Stelle des Aufrufs kopieren.

        $minKey = array_key_first($time_arr[$name]);
        $maxKey = $minKey;
        foreach ($$time_arr[$name] as $key => $val) {
           if ($val < $time_arr[$name][$minKey])
              $minKey = $key;
           else if ($val > $time_arr[$name][$maxKey])
              $maxKey = $i;
        }
        
        $time_arr[$name][$minKey] = // 1 Stunde subtrahieren
        $time_arr[$name][$maxKey] = // 1 Stunde addieren
        
        // 1. SQL Update für minKey ausführen
        // 2. SQL Update für maxKey ausführen
        

        Wenn Du die Ergebnisse gar nicht im Speicher brauchst, sondern nur in der DB, könnte man auch noch überlegen, es mit reinem SQL zu machen. Ich denke, dass das gehen könnte - aber es könnte einiges an SQL Akrobatik erfordern.

        Rolf

        --
        sumpsi - posui - clusi