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:
- 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ą |
6 | 1:13 983 816 |
5 | 1:54 201 |
4 | 1:1032 |
3 | 1:57 |
Ilość trafień | Szansa na wygraną |
10 | 1:8911712 |
9 | 1:163382 |
8 | 1:7384 |
7 | 1:621 |
6 | 1:88 |
5 | 1:20 |
4 | 1:7 |
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ą.
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:
Indeks | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
Wartość | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
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…
Indeks | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
Wartość | 1 | 2 | 3 | 4 | 5 | 6 | 9 | 8 | 9 |
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
- Funkcja
rand (min, $max)
służy do generowania liczby z danego przedziału przy użyciu algorytmu Mersenne Twister. - 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. - Funkcja
shuffle (&$tablica)
miesza elementy w tablicy z wykorzystaniem algorytmu Mersenne Twister. - Funkcja
count ($tablica)
zwraca liczbę elementów w tablicy. - Funkcja
array_keys ($tablica)
zwraca nową tablicę z indeksami, aarray_values ($tablica)
tablicę z wartościami np. dla tablicy[5 => 1000, 10 => 2000]
,array_keys
zwróci[5,10]
, aarray_values [1000,2000]
. - Funkcja
implode ($separator, $tablica)
zamienia tablicę na ciąg znaków (string
) np. Wywołanieimplode("----",[40,50,60])
zwróci40----50----60