Aus Buchstaben-Array einzigartige Strings generieren

Wow, das klingt genau wie ich es haben wollte. Vielen Dank!

Wenn ich nun alternativ zunächst 100.000 randomisierte Strings einpflegen wollte und später schrittweise 10.000 weitere, um - wie von einigen hier angemerkt - die Datenbank schlanker zu halten ... wäre das auch möglich, ohne zu kompliziert zu werden?

Wichtig ist dabei vor allem der Dublettenschutz - und zwar nicht nur innerhalb der ersten 100Tsd. Einträge, sondern auch später bei allen weiteren. Also auch das 10.000.000 Tier soll nicht mit einem anderen verwechselt werden können.

So wäre z. B. die erste ID "ABEEMP", die zweite "XGSVCC", etc. aber diese Strings dürften auf keinen Fall irgendwann nochmal auftauchen. Nicht beim ersten Durchgang und auch später nicht.
 
Auch in dem Fall würde ich nicht mit Zufallswerten arbeiten sondern mit aufeinander folgenden nach dem Strickmuster eines Zählers. Es wäre kein Problem, den Algorithmus so zu erweitern, dass nicht von 'AAAAAA' begonnen wird sondern von einem Startwert wie 'AAAZZZ', der an die Funktion übergeben wird. Auch dann würdest Du keine Probleme mit Dubletten bekommen
Alternativ könntest Du auch jeweils nur einen Wert ermitteln dann wenn er gebraucht wird.
 
Spaßeshalber habe ich mal das Generieren der IDs ausgehend vom letzten Wert der vorhandenen Folge implementiert und leider ist es ein wenig kompliziert geworden:
Code:
    $letters=['A','B','C','D'];
    $places=6;
    function addLetters($letters, $result, $idx, $startIdxArr)
    {
        // Loop through all letters:
        for ($j = $startIdxArr[$idx]; $j < count($letters); $j++) {
            // Add current letter to string:
            $newResult= $result . $letters[$j];
            // Last place not yet reached?
            if ($idx>0) {
                // Places left, add letters for next place:
                addLetters($letters, $newResult, $idx - 1, $startIdxArr);
            } else {
                // Resulting string ready, enter into DB:
                echo $newResult . '<br>';
            }
        }
    }
    function addLettersStartWith($letters, $places, $lastOne)
    {
        if (strlen($lastOne) != $places) {
            echo 'Wrong length for parameter $lastOne';
            return;
        }
        // Make array of indexes of chars in $lastOne:
        $startIdxArr=[];
        for ($i=0;$i<$places;$i++) {
            $char=substr($lastOne, $i, 1);
            $startIdx= array_search($char, $letters);
            $startIdxArr[]=$startIdx;
        }
        var_dump($startIdxArr);
        // These are the indexes of the last value,
        // we have to increment by one step in order
        // to get the start indexes.
        // As an overflow can result in an increment
        // of the next letter recursively,
        // we use a recursive procedure:
        function incIt($letters, &$startIdxArr, $i)
        {
            $startIdxArr[$i]++;
            if ($startIdxArr[$i] >= count($letters) && $i > 0) {
                $startIdxArr[$i] = 0;
                incIt($letters, $startIdxArr, $i-1);
            }
        }
        incIt($letters, $startIdxArr, $places-1);
        var_dump($startIdxArr);
        // The function addLetters starts at the leftmost position,
        // therefore we have to reverse the array:
        $startIdxArr = array_reverse($startIdxArr);
        echo $lastOne . '<br><br>';
        // Now we can create the IDs:
        addLetters($letters, '', $places - 1, $startIdxArr);
    }
    addLettersStartWith($letters, $places, 'AAADD');
    addLettersStartWith($letters, $places, 'AAAADD');
Dabei hat man aber alles was man braucht, um einen einzigen neuen Wert zu ermitteln:
Code:
    $letters=['A','B','C','D'];
    $places=6;
    function getNext($letters, $places, $lastOne)
    {
        if (strlen($lastOne) != $places) {
            return false;
        }
        // Make array of indexes of chars in $lastOne:
        $newIdxArr = [];
        for ($i = 0; $i < $places; $i++) {
            $char = substr($lastOne, $i, 1);
            $startIdx = array_search($char, $letters);
            $newIdxArr[] = $startIdx;
        }

        var_dump($newIdxArr);
        // These are the indexes of the last value,
        // we have to increment by one step in order
        // to get the start indexes.
        // As an overflow can result in an increment
        // of the next letter recursively,
        // we use a recursive procedure:
        function incIt($letters, &$newIdxArr, $i)
        {
            $newIdxArr[$i]++;
            if ($newIdxArr[$i] >= count($letters) && $i > 0) {
                $newIdxArr[$i] = 0;
                incIt($letters, $newIdxArr, $i-1);
            }
        }
        incIt($letters, $newIdxArr, $places-1);
        var_dump($newIdxArr);
        // Now we can create the new ID:
        $newId = '';
        for ($i = 0; $i < $places; $i++) {
            $newId .= $letters[$newIdxArr[$i]];
        }
        return $newId;
    }

    // Test wrong value for old ID:
    echo 'AAADD' . '<br>';
    $newId = getNext($letters, $places, 'AAADD');
    if ($newId !== false) {
        echo $newId;
    } else {
        echo 'Error when getting new ID' . '<br>';
    };

    // Test correct value for old ID:
    echo 'AAAADD' . '<br>';
    $newId = getNext($letters, $places, 'AAAADD');
    if ($newId !== false) {
        echo $newId;
    } else {
        echo 'Error when getting new ID' . '<br>';
    };
(To do: Prüfen ob alle Zeichen in $lastOne zulässig sind.)
Den höchsten vorhandenen Wert kannst Du leicht aus der Datenbank auslesen. Dann würdest Du keine IDs auf Vorrat in die Datenbank eintragen sondern immer nur die, die in Gebrauch sind. Wenn ich das richtig verstehe in etwa das gleiche wie es die Datenbank mit einer Autoincrement-ID macht.
 
Zuletzt bearbeitet:
Deshalb habe ich geschrieben "geschachtelte Loops" (jeder Loop mit seiner eigenen Iterator-Variablen) plus zzgl. eine "globale" Zähler-Variable

Pseudo-Code
Code:
string MeinLos="";
int Zähler;  //Globaler Zähler --> hier findest du raus, ob du z.B. schon 100.000 "Lose" erzeugt hast
Int a, b,c,d,e,f;  //Iterator-Variablen für die Schleifen
//Hier die Abfrage auf die Datenbank --> Hole Zähler, a, b, c, d, e, f aus der DB und weise sie den Variablen zu
Int NächsterBlock;
NächsterBlock=Zähler+50000; //Oder anstatt 50000 hartcodiert als Argument einer Funktion übergeben
While(Zähler<=NächsterBlock){
    If(a>20){a=0;}
    MeinLos=MeinLos+BuchstabenArray[a];
    While(Zähler<=NächsterBlock){
        If(b>20){b=0;}
        MeinLos=MeinLos+BuchstabenArray[b];
        While(Zähler<=NächsterBlock){
            If(c>20){c=0;}
            MeinLos=MeinLos+BuchstabenArray[c];
            While(Zähler<=NächsterBlock){
                If(d>20){d=0;}
                MeinLos=MeinLos+BuchstabenArray[d];
                While(Zähler<=NächsterBlock){
                    If(e>20){e=0;}
                    MeinLos=MeinLos+BuchstabenArray[e];
                    While(Zähler<=NächsterBlock){
                        If(f>20){f=0;}
                        MeinLos=MeinLos+BuchstabenArray[f];
                        Zähler++;
                        f++;}
                    e++;}
                d++;}
            c++;}
        b++;}
    a++;}
//Speicher Zähler, a,b,c,d,e,f in DB
 
Zuletzt bearbeitet:
OK, habs mal schnell in Excel-VBA zusammengklopft.
Sind Unterschiede zu obigem Pseudo-Code dabei
Visual Basic:
Sub Main()
Dim s(0 To 5) As String  'Ergebnis-String
Dim a As Long
Dim b As Long
Dim c As Long
Dim d As Long
Dim e As Long
Dim f As Long
Dim Zähler As Long
Dim Block As Long

'Zur Veranschaulichung alle 26 Buchstaben
Dim MeinArray(0 To 25) As String
Dim i As Long
    For i = 0 To 25
        MeinArray(i) = Chr(65 + i)
    Next

Zähler = 0  'Holt man aus der DB
'a,b,c,d,e,f holt man auch aus der DB
Block = Zähler + 100 'Kann per Funktionsargument übergeben werden
Do While Zähler <= Block
    If a > 25 Then
        a = 0
        Exit Do
    End If
    s(0) = MeinArray(a)
    Do While Zähler <= Block
        If b > 25 Then
            b = 0
            Exit Do
        End If
        s(1) = MeinArray(b)
        Do While Zähler <= Block
            If c > 25 Then
                c = 0
                Exit Do
            End If
            s(2) = MeinArray(c)
            Do While Zähler <= Block
                If d > 25 Then
                    d = 0
                    Exit Do
                End If
                s(3) = MeinArray(d)
                Do While Zähler <= Block
                    If e > 25 Then
                        e = 0
                        Exit Do
                    End If
                    s(4) = MeinArray(e)
                    Do While Zähler <= Block
                        If f > 25 Then
                            f = 0
                            Exit Do
                        End If
                        s(5) = MeinArray(f)
                        Debug.Print Join(s, "")  'Join in VB ist ein implode in PHP
                        Zähler = Zähler + 1
                        f = f + 1
                    Loop
                    If Zähler > Block Then Exit Do
                    e = e + 1
                Loop
                If Zähler > Block Then Exit Do
                d = d + 1
            Loop
            If Zähler > Block Then Exit Do
            c = c + 1
        Loop
        If Zähler > Block Then Exit Do
        b = b + 1
    Loop
    If Zähler > Block Then Exit Do
    a = a + 1
Loop
Debug.Print "Zähler=" & Zähler
Debug.Print "a=" & a
Debug.Print "b=" & b
Debug.Print "c=" & c
Debug.Print "d=" & d
Debug.Print "e=" & e
Debug.Print "f=" & f

End Sub
 
Zuletzt bearbeitet:
Ich habe auf die Schnelle auch eine Version erstellt, die ohne Datenbank auskommen würde.
PHP:
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Lösung zu: tutorials.de - Aus Buchstaben-Array einzigartige Strings generieren</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body {
        font-family: monospace;
        display: flex;
    }
    body div {
        margin: 30px;
        text-align: center;
    }
    body td, th {
        width: 100px;
        text-align: right;
    }
  </style>
</head>

<body>

<?php

// Eingabewerte:
$allowed_chars = array('A', 'B', 'C', 'D', 'E', 'G', 'H', 'K', 'M', 'N',
                       'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z');
$wordlength = 6;

$example_ids = array(0, 1, 2, 3, 4, 19, 20, 21, 4000, 63999998, 63999999);
$example_str = array();


function get_matching_string(array $chars, int $wl, int $index) {
    $chars_len = count($chars);
    $word = array();

    for ($i = 0; $i < $wl; $i++) {
        $power_10 = pow($chars_len, $wl - $i - 1);

        $digit_index = intdiv($index, $power_10);
        $index %= $power_10;

        array_push($word, $chars[$digit_index]);
    }

    return implode("", $word);
}

function get_matching_index(array $chars, int $wl, string $str) {
    $index = 0;
    $chars_len = count($chars);

    $counter = 0;
    foreach (str_split(strrev($str)) as $char) {
        $i = array_search($char, $chars);
        $index += $i * pow($chars_len, $counter);
        $counter += 1;
    }

    return $index;
}


function swapped_bits(int $i, int $a, int $b) {
    $b1 = 1 & ($i >> $a); // bit at position $a
    $b2 = 1 & ($i >> $b); // bit at position $b

    $result = $i;
    if ($b1 != $b2) {
        $result = $i ^ ((1 << $a) | (1 << $b));
    }

    return $result;
}

function pseudomize(int $i, int $max) {
    $highbit = floor(log($max, 2));

    $swapmask = array(
        0 => $highbit - 1,
        1 => $highbit - 2,
        2 => $highbit - 3,
    );

    foreach ($swapmask as $a => $b) {
        $i = swapped_bits($i, $a, $b);
    }

    return $i;
}


// ****************************************************************************
// TEIL 1:
printf("<div>");
printf("<h3>Nicht pseudonymisierte Strings:</h3><table>");
printf("<tr><th>Index</th><th>String</th></tr>");
foreach ($example_ids as &$index) {
    $str = get_matching_string($allowed_chars,
                               $wordlength,
                               $index);
    printf("<tr><td>%d</td><td>%s</td></tr>", $index, $str);
    array_push($example_str, $str);
}
printf("</table>");

printf("<h3>Und rückwärts:</h3><table>");
printf("<tr><th>String</th><th>Index</th></tr>");
foreach ($example_str as &$str) {
    printf("<tr><td>%s</td><td>%d</td></tr>", $str, get_matching_index($allowed_chars,
                                                                       $wordlength,
                                                                       $str));
}
printf("</table>");
printf("</div>");
// ****************************************************************************

// ****************************************************************************
// TEIL 2:
$max = pow(count($allowed_chars), $wordlength);
$example_str = array();

printf("<div>");
printf("<h3>Pseudonymisierte Strings:</h3><table>");
printf("<tr><th>Index</th><th>String</th></tr>");
foreach ($example_ids as &$index) {
    $str = get_matching_string($allowed_chars,
                               $wordlength,
                               pseudomize($index, $max));
    printf("<tr><td>%d</td><td>%s</td></tr>", $index, $str);
    array_push($example_str, $str);
}
printf("</table>");

printf("<h3>Und rückwärts:</h3><table>");
printf("<tr><th>String</th><th>Index</th></tr>");
foreach ($example_str as &$str) {
    $index = get_matching_index($allowed_chars,
                                $wordlength,
                                $str);
    printf("<tr><td>%s</td><td>%d</td></tr>", $str, pseudomize($index, $max));
}
printf("</table>");
printf("</div>");
// ****************************************************************************

?>

</body>
</html>
Leider ist die Zeit bei mir im Moment sehr knapp, deshalb kann ich es jetzt nicht genauer erläutern. Allerdings sollte der Code eigentlich selbsterklärend sein.


Gruß Technipion
 
Super, ganz vielen Dank euch allen.

Ich schätze, unter diesen vielen Scripten wird sicher das Richtige dabei sein. Ich teste mich die nächsten Tage einfach mal durch. Ist wirklich toll, wie nett ihr mir geholfen habt.

(y)(y)(y)
 
Zurück