1

I would like to pull values from a PHP array at random, without pulling the same value twice before I've pulled all values from the entire array.

In other words, if I have the array...

 _____
| foo |
| bar |
| baz |
|_____|

...I want to pull a random value three times without pulling the same value twice, so I could have...

1st:  foo  foo  bar  bar  baz  baz
2nd:  bar  baz  foo  baz  foo  bar
3rd:  baz  bar  baz  foo  bar  foo

...on my three separate pulls.

Now, I realize I could do this by simply removing the selected item from the array--IF I was only pulling from it once. However, that is not the case. I need the array to stay intact because I want to repeat the random pulls all over again once all values have been pulled in a cycle.

To summarize: I want to pull all values iteratively from an array in a random order without duplicates, until all items have been pulled...and then repeat the process indefinitely.


I thought I had this accomplished by inserting each selected value from the PHP array into a MySQL table, and then checking that table for each subsequent array pull (if the pulled value was in the MySQL table, I re-ran the function until I got a never-before-selected value).

This seems to accomplish the get-all-values-without-duplicates goal, but my output became a little funky in some cases.

At any rate, here is my code intended to accomplish the desired effect. I use jQuery (ajax) to call a PHP function every 7 seconds to get a new array value, and stick that value in a MySQL table for future duplication checking. When every available value has been pulled, I truncate the table and start over again.

Javascript (jQuery):

function cycle_facts() {        
    $.ajax({ url: 'lib/functions.php',
             data:    {setfact: 'yes'},
             type:    'post',
             success: function(output) {
                $('#header').html(output);
             }
    });
}

PHP:

function set_fact() {
    global $mysqli;

    $facts = get_facts(); //gets associative array with desired values to be randomly pulled

    /* see if all available values have been pulled, and truncate SQL table if so */
    $rows = $mysqli->query('select * from used_facts;');
    $rows = $rows->num_rows;
    if ($rows == sizeof($facts)) {
        $mysqli->query('truncate table used_facts;');
    }

    $fact = array_rand($facts);

    /* see if selected value has already been pulled in this cycle, and rerun function if so */
    $try = $mysqli->query('select * from used_facts where fact = \'' . $mysqli->real_escape_string($fact) . '\';');
    if ($try->num_rows >= 1) {
        set_fact();
    }
    else {
        $mysqli->query('insert into used_facts (fact) values (\'' . $mysqli->real_escape_string($fact) . '\');'); //insert newly selected value into SQL table for future duplication checking
    }

    echo $fact . '|' . $facts[$fact]; //return selected value to jQuery for display
}

So I can probably use this approach with some tweaking, but the problem itself intrigued me enough to wonder if there was a simpler, more direct approach that fine folks on stackoverflow have used.

Thanks!

hannebaumsaway
  • 2,644
  • 7
  • 27
  • 37

1 Answers1

9

This will give you each element of the array in a random order without reorganizing the original array

foreach (shuffle($arr) as $elem) {
  echo $elem;
}

If you'd like to improve the true randomness of the shuffle, please see the comments for more details. I particularly like Fishes Yates Shuffle but you can do some research an implement whatever you like :)


PHP's shuffle will reorder the indicies of your array – ie, it mutates the input array.

If you would like to preserve the input array and make a shuffled copy of it, I've taken the time to implement Fisher-Yates "inside-out" algorithm for you.

function pureshuffle (array $xs): array {
  $acc = [];
  for ($i = 0; $i < count($xs); $i++) {
    $j = rand(0, $i);
    if ($j != $i) $acc[$i] = $acc[$j]; 
    $acc[$j] = $xs[$i];
  }
  return $acc;
}

$data = [1,2,3,4,5,6,7];

print_r(pureshuffle($data));
// [2,1,4,6,5,7,3]

print_r($data);
// [1,2,3,4,5,6,7]

Alternatively, you could sort the MySQL result using

ORDER BY RAND()

The performance of this is going to depend on a lot of other factors, but if the query is simple enough and the result is small enough, it shouldn't be an issue.

If you're looking to take this approach, do some quick googling. You'll find lots of information and tricks to optimizing random sorts in SQL.

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • `shuffle` is not as random as you think. See [str_shuffle and randomness](http://stackoverflow.com/questions/14079703/str-shuffle-and-randomness/14079997#14079997) – Baba Jun 19 '13 at 20:01
  • I'm just giving the tip of the iceberg here; exploring entropy and randomness is a very big topic, but this should get the OP started. I added a bit in the answer to point to your comment. – Mulan Jun 19 '13 at 20:03
  • @naomik Please see my added code snippets. I don't think your answer addresses my actual issue. I don't want to randomize the array itself. I want to randomly pull a single value from the array until all values have been selected, then repeat indefinitely. – hannebaumsaway Jun 19 '13 at 20:18
  • @praguian, and I'm telling you how to achieve your goal. I'm just not using the implementation you're using. At least 7 other people agree with my solution :) – Mulan Jun 19 '13 at 20:20
  • @naomik I do apologize! I read your original answer wrong the first time through, and thought you were telling me how to return the entire array (with values randomized). I now see what you were really telling me, and I'll give your solution a shot. However, I'm not sure how to implement this with jQuery/AJAX...each time javascript makes its AJAX call, my PHP file will be reinitialized, and the array re-shuffled. So how do I prevent duplicates (until all values have been read through)? – hannebaumsaway Jun 19 '13 at 20:25
  • I don't know if this is a change since this was answered, or something, but shuffle returns a boolean, not a shuffled version of the array. This just throws an error because you're trying to foreach with your first argument as a boolean value instead of an array. – user1299656 Oct 20 '19 at 19:16