derBernd: PHP - Kleines Problem bei Array-Vergleich

Hallo zusammen,

vorweg: ich bin noch ziemlich neu in der Programmiererei und in PHP, habt also u.U. etwas Nachsicht ;-)

Mein Problemchen:

für ein kleines Ratespiel möchte ich das Lösungswort mit dem Lösungsvorschlag des Spielenden vergleichen, das klappt soweit auch ganz gut bis auf eine kleine Unschönheit wenn im Lösungsvorschlag ein Buchstabe doppelt vorkommt. Hier mal der bisherige Quelltext:

<?
$solution_word = 'abcde';
$provided_solution = 'acbda';

// convert solution-word to array
$aLetters = str_split(strtoupper($solution_word));

// convert provided-solution to array
$aSolution = str_split(strtoupper($provided_solution));

// check solution against solution-word
foreach ($aSolution as $ks => $vs) {
   if ($aLetters[$ks] == $vs) {
      $aStatus[] = 'T';
   } else {
      if (in_array($vs, $aLetters)) {
         $aStatus[] = 'P';
      } else {
         $aStatus[] = 'F';
      }
   }
}

// show the resluts array
echo '<pre>';
print_r($aStatus);
echo '</pre>';
?>  

Das Script gibt mir also ein Array zurück in dem für jeden Buchstaben des Lösungswortes ein Status angegeben ist:

"T" - Buchstabe kommt im Lösungswort vor und ist an der richtigen Position

"F" - Buchstabe kommt nicht im Lösungswort vor

"P" - Buchstabe kommt im Lösungswort vor, ist aber an der falschen Position

Wenn nun ein Buchstabe im Lösungsvorschlag doppelt vorkommt (so wie in obigen Beispiel), dann wird das zweite Vorkommen fälschlicherweise als "P" markiert sollte aber "F" sein.

Mir ist schon klar, das ich in dem ersten else-Zweig noch irgendwas machen muß, komme aber nicht dahinter was und wie, wenn mich da mal jemand in die richtige Richtung schubsen könnte?

Vielen Dank derBernd

  1. Moin!

    für ein kleines Ratespiel möchte ich das Lösungswort mit dem Lösungsvorschlag des Spielenden vergleichen, das klappt soweit auch ganz gut bis auf eine kleine Unschönheit wenn im Lösungsvorschlag ein Buchstabe doppelt vorkommt.

    Hier mal der bisherige Quelltext:

    <?
    $solution_word = 'abcde';
    $provided_solution = 'acbda';
    
    // convert solution-word to array
    $aLetters = str_split(strtoupper($solution_word));
    
    // convert provided-solution to array
    $aSolution = str_split(strtoupper($provided_solution));
    
    // check solution against solution-word
    foreach ($aSolution as $ks => $vs) {
       if ($aLetters[$ks] == $vs) {
          $aStatus[] = 'T';
       } else {
          if (in_array($vs, $aLetters)) {
             $aStatus[] = 'P';
          } else {
             $aStatus[] = 'F';
          }
       }
    }
    
    // show the resluts array
    echo '<pre>';
    print_r($aStatus);
    echo '</pre>';
    ?>  
    

    Fangen wir mal an:

    
    > // convert solution-word to array
    > $aLetters = str_split(strtoupper($solution_word));
    > 
    > // convert provided-solution to array
    > $aSolution = str_split(strtoupper($provided_solution));
    
    

    Das Splitten ist hier überflüssig:

    <?php
    $s='Hallo';
    echo $s[0], $s[strlen($s)-1], "\n";
    

    Die Ausgabe von "Ho" besagt, dass ein String "Array genug" ist. Allerdings musst Du die Länge mit strlen fest stellen, weil count() nicht die Elemente des Strings zählen will. Ein String ist eben zugleich doch kein Aarray ...

    Das eigentliche Problem:

    
    > foreach ($aSolution as $ks => $vs) {
    
    

    Das ist die Ursache Deines Problems, denn es entsteht ein Hash. Kommt ein Buchstabe doppelt vor, wird das erste Element mit diesem Key nur überschrieben, kein weiteres angelegt.

    Benutze einen Iterator:

    <?php
    $aLetters='abcde';
    $aSolution='acbda';
    $aStatus=array();
    
    for ( $i=0; $i < strlen($aSolution); $i++ ) {
        if ($aLetters[$i] == $aSolution[$i]) {
           $aStatus[$i] = 'T';
        } elseif ( false !== strpos($aLetters, $aSolution[$i]) ) {
              $aStatus[$i] = 'P';
        } else {
              $aStatus[$i] = 'F';
        }
    }
    echo '<pre>', implode('', $aStatus), '</pre>', "\n";
    

    sollte also erst mal die gesuchte Lösung sein. Du musst aber noch was mit der Länge der Antwort machen ...

    Jörg Reinholz

    1. Tach!

      Die Ausgabe von "Ho" besagt, dass ein String "Array genug" ist.

      Das weiß er anscheinend schon, denn er greift ja mit $ks auf die einzelnen Buchstaben zu.

      Das eigentliche Problem:

      > foreach ($aSolution as $ks => $vs) {
      

      Das ist die Ursache Deines Problems, denn es entsteht ein Hash. Kommt ein Buchstabe doppelt vor, wird das erste Element mit diesem Key nur überschrieben, kein weiteres angelegt.

      Nein, das passiert nicht. Die Keys des Hashes, welches von str_split() erzeugt wird, sind fortlaufend numerisch, das entstehende Array ist genauso lang wie der String.

      Benutze einen Iterator:

      Hat er ja, foreach ist auch einer. Und in seinem Fall steht in $ks daselbe wie in deinem $i.

      [Code] sollte also erst mal die gesuchte Lösung sein. Du musst aber noch was mit der Länge der Antwort machen ...

      Nö, denn damit wird nur sein Code anders formuliert, nur nicht das Problem gelöst.

      Die Frage ist aber, warum das denn überhaupt ein Problem darstellt. Das T für die erste Fundstelle bleibt doch erhalten, das P für die zweite ist ebenfalls gerechtfertigt. Der Buchstabe kommt vor, aber nicht an dieser Position. Was soll denn stattdessen als Hinweis für den Ratenden gegeben werden? Ein F für gar nicht vorkommend wäre nach meiner Meinung definitiv falsch.

      dedlfix.

      1. Moin!

        Nö, denn damit wird nur sein Code anders formuliert,

        Dann prüfe doch mal gegen:

        $aLetters ='aabcde';
        $aSolution='abcdef';
        

        TTPPTF (Ergebnis von Bernd)
        TPPPPF (Mein Ergebnis)

        $aLetters ='aabcde';
        $aSolution='aabcde';
        

        TTPPTF (Ergebnis von Bernd)
        TTTTTT (Mein Ergebnis)

        Wäre das Skript von mir "nur anders formuliert" worden, dann müsste doch das Ergebnis das gleiche sein - oder?

        Jörg Reinholz

        1. Tach!

          Nö, denn damit wird nur sein Code anders formuliert,

          Dann prüfe doch mal gegen:

          $aLetters ='aabcde';
          $aSolution='abcdef';
          

          TTPPTF (Ergebnis von Bernd)
          TPPPPF (Mein Ergebnis)

          Dann ist dein oder mein PHP kaputt. Das ist mein Ergebnis:

          Array
          (
              [0] => T
              [1] => P
              [2] => P
              [3] => P
              [4] => P
              [5] => F
          )
          
          TPPPPF
          
          $aLetters ='aabcde';
          $aSolution='aabcde';
          

          TTPPTF (Ergebnis von Bernd)
          TTTTTT (Mein Ergebnis)

          Array
          (
              [0] => T
              [1] => T
              [2] => T
              [3] => T
              [4] => T
              [5] => T
          )
          
          TTTTTT
          

          Wäre das Skript von mir "nur anders formuliert" worden, dann müsste doch das Ergebnis das gleiche sein - oder?

          Also, bei mir ist es das.

          dedlfix.

          1. Hallo,

            Dann ist dein oder mein PHP kaputt.

            wieso oder? Da du das gleiche Ergebnis wie Jörg rauskriegst, ist entweder euer beider PHP genau gleich kaputt, oder, viel wahrscheinlicher, unkaputt.

            Gruß
            Kalk

            1. Tach!

              Dann ist dein oder mein PHP kaputt.

              wieso oder? Da du das gleiche Ergebnis wie Jörg rauskriegst, ist entweder euer beider PHP genau gleich kaputt, oder, viel wahrscheinlicher, unkaputt.

              Hab ich mich etwa unklar ausgedrückt? Ich bekomme mit Bernd und Jörgs Lösung jeweils daselbe Ergebnis, Jörg behauptet, mit seiner käme was anderes raus. Ich bekomme also nicht daselbe Resultat wie Jörg.

              dedlfix.

              1. Hallo,

                Hab ich mich etwa unklar ausgedrückt?

                zumindest für mich.

                Ich bekomme mit Bernd und Jörgs Lösung jeweils daselbe Ergebnis, Jörg behauptet, mit seiner käme was anderes raus. Ich bekomme also nicht daselbe Resultat wie Jörg.

                Du zitierst sowohl Bernds als auch Jörgs Ergebnisse, die unterschiedlich sind und stellst dann dein Ergebnis dazu:

                TTPPTF (Ergebnis von Bernd)
                TPPPPF (Mein Ergebnis) [Anm: Jörgs Ergebnis]

                Dann ist dein oder mein PHP kaputt. Das ist mein Ergebnis:
                TPPPPF

                TTPPTF (Ergebnis von Bernd)
                TTTTTT (Mein Ergebnis) [Anm: Jörgs Ergebnis]

                TTTTTT

                So wie du es darstellst, hast du das gleiche Ergebnis wie Jörg, der ein anderes Ergebnis als Bernd darstellt. Du schreibst aber, dass das PHP von Jörg oder dein eigenes kaputt sein soll. Diese Schlussfolgerung erschließt sich mir nicht.

                Gruß
                Kalk

                PS.: Ja, ich habe das andere Posting gelesen, in dem Jörg dir Recht gibt.

          2. Moin!

            Also, bei mir ist es das.

            Verdammt. Ich sollte wohl besser nichts machen, bevor der Morgenkaffee seine Schuldigkeit getan hat. Die Nachprüfung (mit Herstellung der Sicherheit, dass ich definitiv in beiden Skripten jeweils die gleiche Werte verwende) ergab: Du hast Recht.

            Das hätte ich mir auch schenken können, denn das Nachlesen der Skripte führte dann zum selben Ergebnis. Deine Behauptung, es wäre nur anders formuliert, kann man durchaus nachvollziehen, auch wenn ich die Logik doch recht grundlegend geändert ("entschachtelt", also optimiert) habe. Also hast Du auch damit Recht.

            Jörg Reinholz

  2. Lieber derBernd,

    Deine Problembeschreibung klingt nach dem Spielprinzip von Mastermind. Aber dort sind mehrfache Vorkommen einer Farbe möglich, bei Deiner Vorgabe wäre das aber nicht der Fall.

    // check solution against solution-word
    foreach ($aSolution as $ks => $vs) {
       if ($aLetters[$ks] == $vs) {
          $aStatus[] = 'T';
       } else {
          if (in_array($vs, $aLetters)) {
             $aStatus[] = 'P';
          } else {
             $aStatus[] = 'F';
          }
       }
    }
    

    Du prüfst im else-Zweig, der dann zutrifft, wenn das Zeichen nicht an der richtigen Stelle steht, ob es wenigstens vorkommt, egal ob es schon einmal verwendet wurde. Auch prüfst Du nicht, ob es bei einer früheren Verwendung bereits "nicht an der richtigen Stelle" war. Beispiele:

    Lösung: abcde

    • Eingabe1: edcba - Ergebnis: PPTPP
    • Eingabe2: abcce - Ergebnis: TTTFT
    • Eingabe3: accbe - Ergebnis: TPFPT

    Um Versuch2 von Versuch3 unterscheiden zu können, bräuchte es eine Art "Arbeits-Array", in dem nur die bisher schon verwendeten Eingaben enthalten sind. Dann kann man mit in_array() prüfen, ob der gerade überprüfte Buchstabe überhaupt schon verwendet wurde (Eingabe3) und deshalb grundsätzlich als falsch zu bewerten ist.

    $solution = 'abcde';
    $input = 'acbda';
    $aStatus = [];
    
    // convert solution-word to array
    $aSolution = str_split(strtoupper($solution));
    
    // convert provided-solution to array
    $aInput = str_split(strtoupper($input));
    
    // check input against solution
    $aTested = array();
    
    foreach ($aSolution as $iKey => $sValue) {
    
        $already_used = (in_array($sValue, $aTested) !== false);
    
        $aTested[$iKey] = $sValue;
    
        if (!$already_used) {
    
            // correct char and position?
            if ($aInput[$iKey] === $sValue) {
    
                $aStatus[$iKey] = 'T';
    
            } else {
    
                // char exists in solution at all?
                if (in_array($sValue, $aSolution)) {
    
                    $aStatus[$iKey] = 'P';
    
                } else {
    
                    $aStatus[$iKey] = 'F';
                }
            }
    
        } else {
    
            // char has already been used -> error
            $aStatus[$iKey] = 'F';
        }
    }
    
    printf('<pre>%s</pre>', print_r($aStatus, true));
    

    Das Testen überlasse ich Dir. ;-)

    Liebe Grüße,

    Felix Riesterer.

  3. Sodele,

    erstmal vielen Dank für die Unterstützung. Ich habe jetzt mal ausführlich mit den verschiedenen Scripten rumgespielt:

    Die Lösung von Jörg Reinholz ergibt in allen Fällen die gleiche Ausgabe wie die meine. Die Lösung von Felix Riesterer hingegen hat irgendwie ein Problem mit der Erkennung von "F".

    Mmmh ... also noch nicht wirklich weiter. Allerdings hab ich doch schon mal was dazu gelernt:

    Ich kann mir die Umwandlung in Array ersparen - das wußte ich nicht, das man auf die einzelnen Elemente eines Strings zugreifen kann als wäre es ein Array. Ebenso kannte ich die Ausgabe des Ergebnis-Arrays mittels "printf" noch nicht. (Hab da wohl noch 'ne Menge zu lernen...)

    Bleibt noch die "philosophische" Frage von dedlfix:

    Die Frage ist aber, warum das denn überhaupt ein Problem darstellt. Das T für die erste Fundstelle bleibt doch erhalten, das P für die zweite ist ebenfalls gerechtfertigt. Der Buchstabe kommt vor, aber nicht an dieser Position. Was soll denn stattdessen als Hinweis für den Ratenden gegeben werden? Ein F für gar nicht vorkommend wäre nach meiner Meinung definitiv falsch.

    Meine Meinung wäre halt das es "F" ist, denn der Buchstabe kommt ja kein zweites mal vor. Wäre er im Lösungswort zwei mal vorhanden und stünde im Lösungsvorschlag an der falschen Stelle, dann ganz klar "P".

    Viele Grüße Bernd

    1. Tach!

      Bleibt noch die "philosophische" Frage von dedlfix:

      Die Frage ist aber, warum das denn überhaupt ein Problem darstellt. Das T für die erste Fundstelle bleibt doch erhalten, das P für die zweite ist ebenfalls gerechtfertigt. Der Buchstabe kommt vor, aber nicht an dieser Position. Was soll denn stattdessen als Hinweis für den Ratenden gegeben werden? Ein F für gar nicht vorkommend wäre nach meiner Meinung definitiv falsch. Meine Meinung wäre halt das es "F" ist, denn der Buchstabe kommt ja kein zweites mal vor. Wäre er im Lösungswort zwei mal vorhanden und stünde im Lösungsvorschlag an der falschen Stelle, dann ganz klar "P".

      "Kommt kein zweites Mal vor" ist aber nicht gleich "kommt gar nicht vor". Dann musst du die F-Bedingung anders formulieren: "Ist im Lösungsvorschlag öfter enthalten als im gesuchten Wort." Klingt etwas umständlich, aber genau das führt dann auch zur Lösung deines Problems. Du musst die Anzahl der Vorkommen ermitteln (zum Beispiel mit count_chars()) und dir zusätzlich zu jedem Buchstaben des Vorschlags merken, wie oft der schon behandelt wurde.

      Dabei kommt übrigens auch F raus, wenn eigentlich T richtig wäre, aber bereits die Anzahl der Ps eines Buchstabens größer oder gleich der Anzahl dieses Buchstabens im gesuchten Wort ist.

      Du müsstest dann erstmal die T-Stellen suchen und diese aus der weiteren Betrachtung für F und P ausklammern. Ich will dir jetzt nicht den Spaß am Ausknobeln der Lösung nehmen, deshalb beschränke ich das auf diese Betrachtung und unterlasse vorerst die weitere Ausformulierung des Lösungsweges.

      dedlfix.

    2. Moin!

      Meine Meinung wäre halt das es "F" ist, denn der Buchstabe kommt ja kein zweites mal vor. Wäre er im Lösungswort zwei mal vorhanden und stünde im Lösungsvorschlag an der falschen Stelle, dann ganz klar "P".

      Geheimtipp:

      Die Regeln erst mal in natürlicher Sprache formulieren, dazu auch Beispiele notieren und erst dann programmieren.

      Jörg Reinholz

      1. Hallo Jörg Reinholz,

        Die Regeln erst mal in natürlicher Sprache formulieren, dazu auch Beispiele notieren und erst dann programmieren.

        Und dabei ganz besonders auf Unterschiede zwischen natürlicher und Programmiersprache achten, etwa bei ‚oder‘.

        Bis demnächst
        Matthias

        --
        Signaturen sind bloed (Steel) und Markdown ist mächtig.