skip to Main Content

I want to populate a result array containing values randomly drawn from an input array, but the result array must not have two identical consecutive values.

Additional rules:

  1. The input array of values will contain only unique values and will have at least two values to ensure that it is possible to populate the required result array.
  2. The number of random values may be more or less than the size of the input array.
  3. The result array must not require that all values from the input are used if the number of random values is greater than the input array’s size. In other words, the randomly selected values must not be biased for even distribution.

Sample input:

$array = ['one', 'two', 'three', 'four'];
$n = 10;

A non-exhaustive list of possible valid results:

  • ["three","one","three","one","two","one","four","one","three","four"]

  • ["four","three","two","one","two","four","one","three","two","one"]

  • ["two","four","three","one","two","one","four","two","three","one"]


This question was inspired by this deleted question which struggled to ask the question with clear rules and expectations.

2

Answers


  1. Chosen as BEST ANSWER

    To guarantee that the two consecutive values are not the same, keep track of the previous value (or its key) and remove it as a possible random value for the current iteration. Push the random value into the result array, then update the "previous" variable.

    array_diff_key() can be used to exclude a specific key before calling array_rand() to return the random key.

    Code: (Demo) (Reduced alternative) (The ugly version)

    $lastIndex = -1;
    $result = [];
    for ($x = 0; $x < $n; ++$x) {
        $key = array_rand(array_diff_key($array, [$lastIndex => null]));
        $result[] = $array[$key];
        $lastIndex = $key;
    }
    echo json_encode($result);
    

    Alternatively, you can use unset() to exclude the previous random value, but it is important to not modify the original array or else there may not be enough values to fill the result array. Modifying a copy of the input array will do.

    Code: (Demo)

    $lastIndex = -1;
    $result = [];
    for ($x = 0; $x < $n; ++$x) {
        $copy = $array;
        unset($copy[$lastIndex]);
        $key = array_rand($copy);
        $result[] = $copy[$key];
        $lastIndex = $key;
    }
    echo json_encode($result);
    

    A brute force script can guess, check, and overwrite a pushed consecutive duplicate value -- this will not have a finite number of loops. Consecutive duplicates will be increasingly probable with smaller input arrays.

    In the loop, unconditionally push the randomly fetched value into the result array, then only conditionally increment the counter variable if the result array has a solitary value or the last two values are different. (Demo)

    $result = [];
    for ($x = 0; $x < $n; $x += (int) (!$x || $result[$x] !== $result[$x - 1])) {
        $result[$x] = $array[array_rand($array)];
    }
    echo json_encode($result);
    

  2. Brute force approach

    It is possible to do this with a while loop to compare the current randomly chosen element of the input $array with the last element of the $result array (using the PHP end() function).

    This method will result in more calls to array_rand() than the number of required elements to be returned, however it is not necessary to create a copy of the original array.

    However, the number of extra loops will be connected to the length of $array, therefore when $array has:

    • 4 elements, we can expect about 33% extra loops
    • 5 elements, we can expect about 25% extra loops
    • 6 elements, we can expect about 20% extra loops

    This is clearly visible when we increase the start value of $n to about 1000.

    As the length of $array increases, the expected percentage of extra loops will tend to 0 though obviously never reaching 0.

    Code: Demo

        $array = ['one', 'two', 'three', 'four', 'five'];
        // $array = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
    
        $n = 1000;
        $result = [];
        $loop_counter = 0;
    
        while($n > 0) {
            $key = array_rand($array);
            if(end($result) != $array[$key]) {
                $result[] = $array[$key];
                $n--;
            }
            $loop_counter++;
        }
    
        echo PHP_EOL . json_encode($result);
        echo PHP_EOL . $loop_counter;
    

    Finite number of loops approach

    There are several ways to ensure that there are a finite number of loops as given in another answer to this question but they involve creating a copy of $array for each loop, which may or may not be an issue.

    The following method also creates copies of $array but rather than creating a new copy of $array on each loop, this approach creates a map of arrays and looks up to see if the array has already been created.

    Code: Demo

        $array = ['zero', 'one', 'two', 'three', 'four'];
        $n = 10;
        $result = [];
        $map_of_arrays = [];
    
        // We only select a random element once from the original array
        $key = array_rand($array);
        $result[] = $array[$key];
        $n--;
    
        while($n > 0) {
            if(!array_key_exists($key, $map_of_arrays)) {
                $map_of_arrays[$key] = $array;
                unset($map_of_arrays[$key][$key]);
            }
            $new_key = array_rand($map_of_arrays[$key]);
            $result[] = $map_of_arrays[$key][$new_key];
            $key = $new_key;
            $n--;
        }
    
        echo PHP_EOL . json_encode($result);
    
        // Check that the map of arrays has been created correctly
        // by comparing the index with the string representation
        echo PHP_EOL . PHP_EOL;
        var_dump($map_of_arrays);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search