Losowanie liczb w PHP

W tym artykule napiszemy program, którego celem będzie pokazanie funkcji losujących w php na przykładzie losowania liczb w Lotto. Oczywiście tego typu loterie są przeznaczone dla osób pełnoletnich, a poniższa treść jest jedynie w celach edukacyjnych. Zastrzegam również, że nie można przewidzieć wyników losowania.

Co trzeba znać przed rozpoczęciem artykułu?

Podstawy PHP:

seohost
  • zmienne
  • tablice
  • pętle (for, while, foreach).

Czego się nauczysz?

  • Generowania liczb pseudolosowych
  • Pseudolosowe mieszanie elementów tablicy
  • Wyciąganie kluczy i wartości z tablicy
  • Zamiana tablicy na ciąg znaków

Szanse na wygraną

Zanim przejdziemy do algorytmu, warto wspomnieć jakie są szanse na trafienie dla poszczególnych gier liczbowych.

Ilość trafieńSzansa na wygraną
61:13 983 816
51:54 201
41:1032
31:57
Duży Lotek
Ilość trafieńSzansa na wygraną
101:8911712
91:163382
81:7384
71:621
61:88
51:20
41:7
Multi Lotek przy skreśleniu 10 liczb

Warto dodać, że Multi Lotek oferuje również wariant z plusem. Trafienie dodatkowo plusa zmniejsza szansę dwukrotnie, czyli trafienie 10-tki i plusa jest mniej prawdopodobne od trafienia 6-tki w Dużym Lotku. Dodatkowo, Multi Lotek pozwala również na skreślenie mniejszej ilości liczb. I tak przy wybraniu jednej liczby szansa wynosi 1:4. a trafienie dwóch liczb z dwóch to 1:17.

Jak więc widać szanse na wygranie większych kwot są ekstremalnie wręcz małe i nie ma znanego nauce sposobu na przewidzenie wyników kolejnego losowania.

Generator licz (pseudo)losowych

Uzyskanie liczb prawdziwie losowych jest w zasadzie niemożliwe do osiągnięcia na tradycyjnym komputerze. Liczby są generowane w oparciu o cechy architektury sprzętu na której uruchamiany jest program i z tego powodu niektóre z nich mogą pojawiać się częściej niż inne. Generowanie jest zależne od momentu uruchomienia programu, obciążenia systemu itd. Z punktu widzenia naszego programu nie ma to jednak dużego znaczenia. Zaznaczam to tylko w celach formalnych.

Funkcja do generowania liczb w php to rand

rand (int $min, int max): int

lub mt_rand

mt_rand(int $min, $int $max): int

Od wersji PHP 7.1 obie te funkcje działają identycznie, ale jeżeli z jakiś powodów korzystasz ze starszej wersji to dodam tylko, że mt_rand jest szybszy i opiera się na algorytmie Mersenne Twister. W poprzednich wersjach PHP był on jednak niewłaściwie zaimplementowany.

Wspomnę jeszcze o funkcji random_int, która generuje jeszcze bardziej losowe wyniki i może być używana w kryptografii. Jest znacznie wolniejsza.

random_int(int $min, $int $max): int

Wylosowanie jednej z 49 liczb będzie wyglądać na przykład tak

$liczba = rand (1, 49);

Losowanie wielu różnych liczb w pętli

Funkcja generująca liczby zwraca jedną liczbę z danego przedziału, ale jeżeli losujemy wiele liczb, mogą się one oczywiście powtórzyć. Najprawdopodobniej tak będzie. Najprostszym rozwiązaniem może być sprawdzanie czy już wylosowaliśmy określoną liczbę. Jeżeli tak to losujemy ponownie aż do skutku. W bardzo skrajnych sytuacjach algorytm jednak może działać w nieskończoność (teoretycznie). Spójrzmy na przykład:

$wylosowane = [];
do {

  $liczba = rand (1,49); // losujemy
  if ( !isset ($wylosowane[$liczba]) ) // czy nie istnieje już liczba w tablicy?
    $wylosowane[$liczba] = null; // dodajemy liczbę

} while ( count ($wylosowane) < 6 ); // wykonuj dopóki jest mniej niż 6 liczb

echo 'Wylosowano: '.implode (', ' , array_keys($wylosowane) ). "\n";

W powyższym przykładzie użyliśmy tablicy asocjacyjnej w której zapisujemy wylosowane liczby. Sprawdzamy czy wylosowana liczba istnieje. Jeżeli nie istnieje to dodajemy ją do tablicy asocjacyjnej jako klucz. Pętlę wykonujemy tak długo, aż w tablicy znajdzie się 6 liczb. Na koniec je wypisujemy. Funkcja array_keys zwraca tablicę z kluczami tablicy, a implode zamienia tablicę na tekst w której każdy element jest rozdzielony przecinkiem i spacją.

Kod
Wynik działania powyższego skryptu w darmowym testerze php online (przycisk do uruchamiania kodu na górze)

Losowanie wielu różnych liczb z wykorzystaniem tablicy

Idea jest taka, że tworzymy tablicę zawierającą wszystkie możliwe liczby (49). Losujemy element w tablicy, a nie samą liczbę (początkowo indeks 0 będzie miał liczbę 1, indeks 1 będzie miał liczbę 2 itd. Spójrzmy na przykład z 9 liczbami. Losujemy liczbę od 0 do 8. Przykładowo wylosowaliśmy 6:

Indeks012345678
Wartość123456789

Pod indeksem 6 jest liczba 7 – zapisujemy ją i następnie przenosimy liczbę znajdującą się na ostatnim indeksie, czyli 9-tkę. Ponownie losujemy indeks, ale tym razem z zakresu od 0 do 7…

Indeks012345678
Wartość123456989

W kolejnym losowaniu wypadł indeks 7 (ostatni). Zapisujemy liczbę pod indeksem, ale nie ma czego przenosić (lub przenosimy w to samo miejsce), więc kontynuujemy losowanie, aż nie wylosujemy 6 liczb.

Mam nadzieję, że koncepcja jest zrozumiała. Napiszmy teraz stosowny kod:

$wylosowane = [];
$tablica = range(1,49); // stwórz i wypełnij tablicę liczbami od 1 do 49

for ($i=0; $i<6; $i++) { // wylosuj 6 liczb

  $liczba = rand (0, 48 - $i); // losuj indeks
  $wylosowane[] = $tablica [$liczba];  // zapisz wylosowaną liczbę znajdującą się w indeksie
  $tablica [$liczba] = $tablica [48 - $i]; // ostatni element zapisz w miejsce wylosowanej liczby

}

echo 'Wylosowano: '.implode (', ' , array_values ( $wylosowane ) ). "\n";

Funkcja range tworzy tablicę i wypełnia ją liczbami od 1 do 49. Następnie wykonujemy 6 losowań każdorazowo zmniejszając zakres losowanych indeksów o 1 i przepisując ostatnią liczbę w miejscu wylosowanego indeksu. Wylosowane liczby oczywiście zapisujemy. Funkcja array_values pobiera wszystkie wartości tablicy z wylosowanymi liczbami, które na końcu scalamy do tekstu przy użyciu implode.

Powyższe rozwiązanie ma jedną dużą wadę. Jeżeli generator liczb losowych działa prawidłowo to ostatnie liczby powinny występować rzadziej. Pamiętajmy, że aby wylosować 49 za drugim razem, musimy ponownie wylosować tą samą liczbę, którą już losowaliśmy. Aby zachować pełną losowość przy tej metodzie możemy ponowić samo losowanie ok. 7 razy (49 / 6) po wylosowaniu liczby. Można się pokusić o kod przed właściwym losowaniem:

for ($j=0; $j<7 - $i; $j++) rand (0; $j<48 - $i);

Wydaje się to głupie i niepotrzebne, ale chodzi o to, że przykładowo indeks 6 (w przykładzie na początku tego rozdziału) powinien się pojawić nie wcześniej niż po 7 losowaniach. Mamy zestaw 49 liczb – w świecie idealnym żadna liczba nie powinna się powtórzyć, czyli każda liczba pojawi się ponownie mniej więcej co 8 losowań (49 / 6 = 8,166…)

A nie da się prościej?

No dobra – po tych fikołkach czas przejść do właściwego i ultra prostego algorytmu. Spójrzmy.

$liczby = range (1, 49);
shuffle ( $liczby );
$wylosowane = array_slice ($liczby, 0, 6);

echo 'Wylosowano: '.implode (', ' , array_values ($wylosowane) ). "\n";

Funkcja shuffle wykonuje operację mieszania na tablicy $liczby. Następnie za pomocą array_slice wycinamy 6 pierwszych liczb. To wszystko. Stwórzmy więc gotową funkcję, która będzie działać dla dowolnego zakresu liczb.

function losuj ($zakres, $ile_liczb) {

  $liczby = range (1, $zakres);
  shuffle ( $liczby );
  return array_slice ( $liczby, 0, $ile_liczb );

}

echo 'Wylosowano: '.implode (', ' , losuj(49, 6) ). "\n";

Podsumowanie

  1. Funkcja rand (min, $max) służy do generowania liczby z danego przedziału przy użyciu algorytmu Mersenne Twister.
  2. Funkcja random_int (min, $max) robi to samo, ale z większą losowością (może być używana w kryptografii). Jej wadą jest prędkość działania.
  3. Funkcja shuffle (&$tablica) miesza elementy w tablicy z wykorzystaniem algorytmu Mersenne Twister.
  4. Funkcja count ($tablica) zwraca liczbę elementów w tablicy.
  5. Funkcja array_keys ($tablica) zwraca nową tablicę z indeksami, a array_values ($tablica) tablicę z wartościami np. dla tablicy [5 => 1000, 10 => 2000], array_keys zwróci [5,10], a array_values [1000,2000].
  6. Funkcja implode ($separator, $tablica) zamienia tablicę na ciąg znaków (string) np. Wywołanie implode("----",[40,50,60]) zwróci 40----50----60