Fonction aléatoire dans une double boucle while

WRInaute passionné
Salut à tous,

J'ai la fonction php ci-dessous qui sélectionne aléatoirement dans mon répertoire "images" des n° d'images :
PHP:
function getNumImage(){
    $nb_images = count(glob("images/*.*"));
    $num_image = rand(1, $nb_images);
  return $num_image;
}
J'utilise ensuite cette fonction php dans une double boucle while qui va chercher les infos dans une table mysql et affiche ensuite 20 ($nb) résultats par page.
PHP:
$nb = $MysqliNumRows($result);
$i = 0;
while($i<$nb)
{
    while($voir = $MysqliFetchArray($result))
    {
    $num_image = getNumImage();
    echo "$num_image<br>";
    }
$i++;
}
Mais cette fonction getNumImage() me retourne parfois des doublons de n° d'images, comment faire pour que cela me retourne 20 ($nb) n° d'images unique (distinct) ?

Merci pour vos réponses et votre aide.
 
WRInaute passionné
Un grand merci spout, ton code marche nickel ;) c'est clair et efficace : respect :cool:

Sincèrement je ne crois pas que j'aurais trouvé ce code tout seul :(
 
WRInaute impliqué
J'aime bien le principe de demander à chatGPT qui finalement donne une réponse pas si débile, mais en même temps pas si intelligente. Du moins, elle se base sur le code existant sans remettre les choses à plat alors qu'on pourrait faire mieux : éviter l'imbrication des boucles, éviter un count() systématique.
 
WRInaute passionné
C'est totalement hallucinant ce ChatGPT, je n'y avais pas pensé mais effectivement lorsque tu lui poses la question :
Code:
comment éviter d'avoir des doublons de numéros d'images avec la fonction :
function getNumImage(){
    $nb_images = count(glob("images/*.*"));
    $num_image = rand(1, $nb_images);
  return $num_image;
}
Il te répond :
Code:
La fonction getNumImage() utilise la fonction rand() pour générer un nombre aléatoire entre 1 et le nombre total d'images présentes dans le dossier images. Cela signifie qu'il est possible d'obtenir le même numéro d'image plusieurs fois, créant ainsi des doublons.

Pour éviter cela, vous pouvez stocker les numéros d'images déjà utilisés dans une variable ou un tableau, puis vérifier si le numéro généré aléatoirement a déjà été utilisé avant de le retourner. Voici un exemple de code qui pourrait fonctionner :
Avec ce bout de code qu'il sort d'où ? Et qui fonctionne parfaitement bien :
PHP:
function getNumImage(){
    $image_numbers = array();
    $images = glob("images/*.*");
    foreach ($images as $image) {
        $image_numbers[] = str_replace('images/', '', $image);
    }
    $num_images = count($image_numbers);
    
    do {
        $num_image = rand(1, $num_images);
    } while (in_array($num_image, $image_numbers));
    
    return $num_image;
}
Et il t'explique en plus que :
Code:
Ce code utilise la fonction in_array() pour vérifier si le numéro généré aléatoirement a déjà été utilisé, et la boucle do while pour continuer à générer des numéros aléatoires jusqu'à ce qu'un numéro unique soit trouvé. La fonction str_replace() est utilisée pour supprimer le chemin du nom de fichier de l'image, afin de ne stocker que le numéro de l'image dans le tableau image_numbers.
Mon cerveau était out et ChatGPT te trouve la solution en 2 secondes : halluuuuuuuuucinant ! C'est la mort des programmeurs informaticiens...
 
WRInaute impliqué
C'est la mort des programmeurs informaticiens.
Coder, c'est la partie la plus simple. Ce qui est plus difficile, c'est de concevoir une architecture : de quelle façon les "bouts de code" s'imbriquent les uns avec les autres et donnent des fonctionnalités. Pour trouver des bouts de code, ChatGPT est plutôt bon, StackOverflow interrogé par Google donnait déjà de bons résultats. L'IA simplifie la partie requête : il est plus souple sur la manière dont on formule la question et adapte plus ou moins seul la réponse en fonction de la question.

Sur l'exemple précis, c'est peut être moi qui n'ai pas les neurones alignés, mais je ne vois pas comment il peut se souvenir des numéros déjà utilisés puisque la variable $image_numbers est réinitialisée à chaque appel de fonction (entre autres problèmes).
 
WRInaute passionné
Sur l'exemple précis, c'est peut être moi qui n'ai pas les neurones alignés, mais je ne vois pas comment il peut se souvenir des numéros déjà utilisés puisque la variable $image_numbers est réinitialisée à chaque appel de fonction (entre autres problèmes).
Pourtant le code qui a été fourni par ChatGPT fonctionne, il n'y a pas de doublon de n° d'images, j'ai testé.
 
WRInaute impliqué
Pour en avoir le cœur net :
PHP:
function getNumImage(){
    $image_numbers = array();
    $num_images = 5;
    
    do {
        $num_image = rand(1, $num_images);
    } while (in_array($num_image, $image_numbers));
    
    return $num_image;
}

for ($i=0; $i<5; $i++){
    print getNumImage() . PHP_EOL;
}

Un des retours obtenus :
Code:
4
3
3
4
2

Surtout que, $image_numbers n'est jamais renseigné, alors du coup je vois vraiment pas comment ça pourrait marcher.

Bien sûr avec un nombre d'images $num_images plus élevé, les risques de doublons sont plus faibles, mais un script bien conçu ne devrait pas me retourner deux fois le même nombre, même avec un nombre de demandes identiques aux propositions.

Avec :
PHP:
$image_numbers = array();
function getNumImage(){
    global $image_numbers;
    $num_images = 5;
    
    do {
        $num_image = rand(1, $num_images);
    } while (in_array($num_image, $image_numbers));
    
    $image_numbers[] = $num_image;
    
    return $num_image;
}

for ($i=0; $i<5; $i++){
    print getNumImage() . PHP_EOL;
}

J'ai bien le résultat attendu. Mais ça reste une approche peu optimale, déjà le besoin de recourir à une globale n'est pas idéal, on pourrait tout à fait s'en passer et optimiser le code en faisant une fonction qui retourne d'un coup les x images demandées (le nombre d'images pouvant être fourni en paramètres), ce qui éviterait de lister toutes les images (et même de les compter) à chaque fois.

Mais surtout, le code que je propose peut engendrer une boucle infinie si le nombre d'images demandées est supérieur au nombre disponible.
 
WRInaute passionné
emualliug le code qui ne retourne pas de doublons d'images et que j'utilise est celui-ci :
PHP:
$numeros_utilises = array();

function getNumImage(){
    global $numeros_utilises;
    $nb_images = 50; // Nombre totale d'images dans mon répertoire ../images/
    do{
        $num_image = rand(1, $nb_images);   
    }
    while (in_array($num_image, $numeros_utilises));
    $numeros_utilises[] = $num_image;   
  return $num_image;
}

for($i=0; $i<20; $i++){ // J'affiche 20 images par page
    print "".getNumImage()."<br>";
}
 
WRInaute impliqué
Oui, donc c'est bien ce que je disais, le code proposé par ChatGPT est foireux il y manque ce que tu as ajouté :
* l'appel à une variable globale
* l'alimentation de cette variable au fur et à mesure des numéros déjà sortis

Elle a le même défaut que ce je pointais, si tu demandes plus de 50 images, tu t'enfermes dans une boucle infinie.

Et en outre, à moins que getNumImage() soit utilisé par ailleurs (et auquel cas il faudra bien penser à réinitialiser $numeros_utilises, d'où l'intérêt de la place peut être avant le for plutôt qu'avant la fonction), le recours à une fonction ne se justifie pas vraiment. Et si elle devait être réutilisée, plutôt que de faire comme ainsi, j'aurai plutôt tendance à créer une fonction générique tirage(int $tirages, int $nombreMax) qui renverrai un array() avec $tirages nombres différents compris entre 1 et $nombreMax.

Rien de transcendant, mais tout ça pour dire que ChatGPT aliment le programmeur de bouts de codes qui sont parfois faux, et sans repenser l'architecture globale.
 
WRInaute accro
Oui mais l'idée de ChatGPT donne une piste. Il suffit de mettre $numeros_utilises en paramètre par référence de la fonction getNumImages, and voilà plus de variable globale.
 
WRInaute occasionnel
Bonsoir,
c'est tentant mais je pense que ça a encore ses limites , qu'il doit savoir résoudre un petit problème mais qu'il produira un code plein de bugs si on lui demande de créer une grosse application. Quel est votre avis ?
 
WRInaute impliqué
Si vous voulez conserver un tableau au sein d'une fonction, il est préférable d'utiliser static, qui permet de déclarer des variables qui conserveront leur état eu fil des appels.
Code:
function getNumImage(){
    // au premier appel uniquement, le tableau est initialisé
    static $numeros_utilises = [];
    $nb_images = 50; // Nombre totale d'images dans mon répertoire ../images/
    do{
        $num_image = rand(1, $nb_images);
    }
    while (in_array($num_image, $numeros_utilises));
    $numeros_utilises[] = $num_image;
}

SI LE NOMBRE D'IMAGES N'EST PAS TRÈS SUPÉRIEUR À CE DONT TU AS BESOIN, tu peux mélanger.
Mais il est bien sûr peu recommandé de mélanger 10000 noms d'images pour n'en récupérer que 20.

Si tu as un tableau contenant tous les noms des images, tu peux le mélanger et récupérer les 20 derniers du mélange au fil de ta boucle.
Code:
// tableau contenant les noms des images : rempli des lettres A à Z, pour l'exemple
$listeImages = range('A', 'Z');

// on mélange
shuffle($listeImages);

// affiche la dernière entrée et la supprime du tableau.
for ($i = 0; $i < 20; $i++)
    echo array_pop($listeImages) . "<br>";

Ou encore, si tu veux vraiment des numéros :
Code:
$nb_images = 50;

// liste de 1 à 50
$liste = range(1, $nb_images);

// mélangée
shuffle($liste);

// boucle sur les 20 premiers
foreach (array_slice($liste, 0, 20) as $num_image)
    echo $num_image . "<br>";
 
Dernière édition:
WRInaute impliqué
Ah zut, j'ai oublié un commentaire : pour le dernier exemple, j'utilise array_slice pour conserver le tableau $liste intact, au cas où on en aurait besoin plus tard.

Comme mon but était de partager un peu de connaissances, voilà des trucs qui pourront peut-être vous intéresser.

in_array est une fonction qui parcourt le tableau à la recherche d'une valeur : chaque élément est comparé avec celui recherché.
On va dire qu'au lieu de vouloir des indexes dans un ensemble de 1 à 50, on tire des cartes.

Dans la fonction getNumImage, voilà ce qui se passe :
- on tire une carte
- on vérifie qu'on ne l'a pas déjà tirée
- si elle n'a pas déjà été tirée, on note quelle carte c'est
Imaginons maintenant qu'on ait 50.000 cartes et qu'on n'en veuille pas juste une dizaine, mais qu'on veut les avoir toutes mélangées.
L'algorithme est alors très mauvais : lors des premier passages, ça va, mais plus on avance et plus on a d'opérations inutiles car plus on a de chances de tomber sur un n° déjà sorti et de devoir refaire un essai. Pour chaque chiffre trié, il faut comparer avec toutes la liste des éléments triés en reprenant tous les numéros sortis l'un après l'autre (c'est ce que fait in_array, même si vu coté PHP, ça n'est qu'une instruction).
Et c'est lent.

Maintenant, imaginez qu'au lieu de noter les n° au fil des tirages, et de devoir parcourir la liste à chaque fois pour vérifier si le n° n'est pas déjà sorti, vous ayez un cahier avec la liste de tous les n° pouvant sortir dans l'ordre, et qu'au fur et à mesure, vous barriez les n°. C'est beaucoup plus simple de savoir si le n° est déjà sorti : il suffit de regarder s'il a été barré.
Pour ça, on va utiliser le fonctionnement des tableaux de PHP : si le n° sort, on définit l'index correspondant, et on vérifiera avec isset, qui permet un accès direct.

Et enfin, shuffle est fait pour les mélanges : il effectue des permutations et c'est assez proche de ce qu'on ferait avec un jeu de cartes. Plus exactement, il passe sur chaque élément, et l'échange avec un autre au hasard. Il n'y a pas besoin de garder trace de ce qui a été fait précédemment puisque chaque élément sera mélangé, et la fonction fait partie du langage et est donc plus rapide.

Voilà ce que donnent les perfs pour un tableau de 50.000 entrées dont on veut récupérer tous les éléments mélangés :
- algo 1 avec in_array : 19,4031 secondes
- algo 2 avec isset : 0,0610 seconde
- algo 3, un simple shuffle : 0,0012 seconde

Code:
// algo 1
function getNumImage(){
    static $numeros_utilises = [];
   $nb_images = 50000; // Nombre totale d'images dans mon répertoire ../images/
    do{
      $num_image = rand(1, $nb_images);
    }
   while (in_array($num_image, $numeros_utilises));
   $numeros_utilises[] = $num_image;
   return $num_image;
}
for ($i = 0; $i < 50000; $i++)
   $t = getNumImage();


// algo 2
function getNumImage2(){
   static $numeros_utilises = [];
   $nb_images = 50000;
   do{
      $num_image = rand(1, $nb_images);
    }
   while (isset($numeros_utilises[$num_image]));
   $numeros_utilises[$num_image] = 1;
   return $num_image;
}
for ($i = 0; $i < 50000; $i++)
   $t = getNumImage2();


// "algo" 3
$ary = range(1, 50000);
shuffle($ary);
 
Dernière édition:
WRInaute accro
Perso je serais plutôt parti sur une solution toute simple :

PHP:
<?php 
function getRandomFiles($path = 'images/*.*', $num = 1)
{
    return array_rand(array_flip(glob($path)), $num);
}

print_r(getRandomFiles('images/*.*', $nb));
 
WRInaute impliqué
Je suis bluffé par la différence entre in_array() et isset().
Oui, pour un gros tableau sur lequel on fait beaucoup de tests isset. Pour 20 chiffres sur un tableau de 50, ça ne va pas changer grand chose.
J'en ai parlé quand même parce que c'est bon à connaitre. J'ai plusieurs pages d'admin avec ce genre de choses (sur des tableaux encore plus grands), et passer de plusieurs dizaines de secondes (ou plusieurs minutes) à attendre à du quasi instantané, c'est bien agréable.
 
Discussions similaires
Haut