Перетасовать массив PHP, гарантируя, что у каждого есть свой ключ

Я создаю веб-сайт обмена подарками, куда люди вставляют свои электронные письма, а затем каждый из участников сопоставляется с другим участником (отправителем). Я использую PHP (возможно, Symfony, если это имеет значение).

Я ожидаю, что количество участников будет около 600-800, и это будет проводиться довольно часто.

Я подумал, что могу использовать shuffle() и array_combine() для массива получателей, чтобы сделать это. Однако после shuffle() отправитель все еще может находиться в том же положении, поэтому ему придется подарить себе подарок Тайного Санты.

Например:

$recipients = "SQL query that returns array"
# ['bob', 'alice', 'joe']

$senders = $recipients; 
shuffle($senders);
# ['alice', 'bob', 'joe']

$result = array_combine($recipients, $senders);
# ['bob' => 'alice', 'alice' = 'bob', 'joe' => 'joe']

Поэтому мне нужно гарантировать, что в конечном массиве ни одно из значений не равно ключу. До сих пор я думал о следующих возможных решениях, но все они кажутся дорогими и немного мусорными:

  1. Используйте array_walk() над окончательным массивом. Поместите любые значения в другой массив, а затем поменяйте их местами друг с другом. Если есть только 1, просто поменяйте его местами.
  2. Возьмите все значения из массива $recipients с четными ключами и все значения из массива $senders с нечетными ключами. Перемешайте оба этих массива.
  3. Вместо использования shuffle() реализую свою дрянную версию, которая делает что-то вроде сдвига всех значений вперед на два, а затем выполняет array_reverse().
  4. Переберите получателей и используйте array_rand(), чтобы выбрать элемент из $senders. Если это то же самое, выберите еще раз, в противном случае удалите его из массива, установите его как отправителя для этого получателя и перейдите к следующему получателю.

Возможно, я слишком много думаю об этом - есть ли более простой способ? Или есть особый способ сделать это в PHP, о котором я не знаю?


person Dan Blows    schedule 06.08.2014    source источник
comment
@Dagon, этот вопрос касается удаления повторяющихся пар ключ-значение из ассоциативного массива. В этом вопросе я не пытаюсь удалить дубликаты, я пытаюсь предотвратить создание shuffle() массива, в котором ключ равен значению.   -  person Dan Blows    schedule 07.08.2014


Ответы (2)


  1. перемешивать
  2. создать копию
  3. выполнить круговой сдвиг
  4. комбинировать

    $users = array('bob', 'alice', 'joe');
    
    shuffle($users);
    
    $santas = $users;
    $santas[] = array_shift($santas);
    
    $result = array_combine($santas, $users);
    
    var_dump($result);
    

Демонстрация: http://codepad.org/jxrzczRG

person zerkms    schedule 06.08.2014
comment
Что вы подразумеваете под циклическим сдвигом? - person Dan Blows; 07.08.2014
comment
@Blowski Представьте свой массив в виде вращающегося диска, поэтому, когда вы перемещаете свой массив на одну позицию вперед, последний элемент занимает первую позицию, первый элемент занимает вторую позицию, и так далее, и так далее. - person ILikeTacos; 07.08.2014
comment
О, только что прочитал, кажется, я понял. shuffle() массив, а затем выполните простую версию решения № 3 - person Dan Blows; 07.08.2014
comment
@zerkms думал об этом, и до сих пор нет гарантии, что в результирующем массиве ни один пользователь не будет равен соответствующему санте. Они могли находиться в разных положениях до кругового сдвига, а затем в одном и том же положении после. - person Dan Blows; 07.08.2014
comment
@Blowski: на это есть гарантия. Как только в сумме будет больше 2-х человек - после круговой смены они будут ВСЕГДА разными. Если вы все еще думаете, что я ошибаюсь, попробуйте предоставить перетасованный массив $users, подтверждающий вашу точку зрения. - person zerkms; 07.08.2014
comment
@zerkms Извините, на самом деле я не писал код, и теперь я понимаю, что вы правы. - person Dan Blows; 12.08.2014

Вы также можете использовать аналогичный код для перетасовки Фишера-Йейтса.

function santaYates($array) {
    $keys = array_keys($array); //Store the keys
    $values = array_values($array); // Cause we need a clean numeric array for this kind of randomisation
    $secure = false;
    for($i = count($values) - 1; $i > 0; $i--) {
        $r = mt_rand(0, $i-1); //subtract 1 from $i to force a new place.
        $tmp = $values[$i];
        $values[$i] = $values[$r];
        $values[$r] = $tmp;
    }
    $returnArray = array_combine($keys, $values); //Now recombine keys and values
    return $returnArray;
}

Я написал этот код для ответа на аналогичный вопрос, но эта версия также может обрабатывать ассоциативные массивы. Вы можете протестировать эту версию

person A.F.    schedule 28.05.2020