Я пытаюсь свести ассоциативный массив к парам уникальных значений, при этом ключи - это буквы, которые нужно связать, а значения - их count() s.

Каждая пара не может содержать одну и ту же букву дважды, например AA или BB.

Допускается более одного появления одной и той же пары.
например AC, DC, AC, DC все допустимы в результирующем массиве

Каждый раз, когда выбирается буква, связанный с ней номер уменьшается на единицу, пока ничего не остается, или

Любые нечетные / оставшиеся буквы следует пометить и не игнорировать, например Array([paired]=>Array(...) [unpaired]=>Array(...))

Пример ввода:

Array(
    [A] => 8
    [B] => 16
    [C] => 15
    [D] => 4
    [E] => 1
)

Возможный выход:

[
    'paired' => [
        'BD', 'AE', 'BC', 'AC', 'BC', ...
    ],
    'unpaired' => [
        'C' => 4
    ]
]

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

Я пробовал различные комбинации array_reduce(), array_map(), array_walk(), array_unique() и т. Д., Даже груши Math_Combinatorics, но все безрезультатно.

0
verbumSapienti 5 Ноя 2020 в 09:51

3 ответа

Лучший ответ

Вот функция, которая сгенерирует желаемые результаты. Он выполняет итерацию по массиву letters, отфильтровывая буквы, которые были полностью использованы, до тех пор, пока не останется только одна буква, которая не используется. Затем список пар и неиспользованная буква возвращаются в виде массива:

function random_pairs($letters) {
    $pairs = array();
    $letters = array_filter($letters);
    while (count($letters) > 1) {
        $keys = array_keys($letters);
        shuffle($keys);
        list($letter1, $letter2) = array_slice($keys, 0, 2);
        $pairs[] = "$letter1$letter2";
        $letters[$letter1]--;
        $letters[$letter2]--;
        $letters = array_filter($letters);
    }
    return array('pairs' => $pairs, 'unpaired' => $letters);
}

Пример использования:

$letters = array('A' => 8, 'B' => 16, 'C' => 15, 'D' => 4, 'E' => 1);
print_r(random_pairs($letters));

Пример вывода:

Array
(
    [pairs] => Array
        (
            [0] => CB
            [1] => CE
            [2] => CD
            [3] => BD
            [4] => CB
            [5] => DC
            [6] => AB
            [7] => CA
            [8] => DA
            [9] => BC
            [10] => BA
            [11] => BA
            [12] => AB
            [13] => BA
            [14] => BC
            [15] => AB
            [16] => BC
            [17] => CB
            [18] => CB
            [19] => CB
            [20] => CB
        )
    [unpaired] => Array
        (
            [C] => 2
        )
)

Демо на 3v4l.org

1
Nick 6 Ноя 2020 в 00:56

Вот рекурсивная техника. Он использует array_rand() для одновременного получения двух уникальных ключей. Если вам нужно, чтобы парные ключи были расположены случайным образом, вызовите для них shuffle(). Я имею в виду, что shuffle() может быть опущено в зависимости от ваших ожиданий.

Преимущество использования array_rand() состоит в том, что array_keys() не нужно вызывать, поскольку он возвращает случайные ключи.

Я специально разработал этот фрагмент, чтобы безоговорочно объявлять подмассив unpaired - даже если он пуст.

Код: (Демо)

function randomPairs(array $letters, array $result = []) {
    if (count($letters) < 2) {
        $result['unpaired'] = $letters;
        return $result;
    }
    $keys = array_rand($letters, 2);
    shuffle($keys);
    $result['paired'][] = $keys[0] . $keys[1];
    --$letters[$keys[0]];
    --$letters[$keys[1]];
    return randomPairs(array_filter($letters), $result);
}

Без рекурсии: (Демо)

function randomPairs(array $letters): array {
    $result['paired'] = [];
    while (count($letters) > 1) {
       $keys = array_rand($letters, 2);
       shuffle($keys);
       $result['paired'][] = $keys[0] . $keys[1];
       --$letters[$keys[0]];
       --$letters[$keys[1]];
       $letters = array_filter($letters);
    }
    $result['unpaired'] = $letters;
    return $result;
}
1
mickmackusa 5 Ноя 2020 в 11:14

Предлагаю вам вот это:

Я только что видел ответ Ника, но это занимает 15 минут. Я пытаюсь xD. Я хочу опубликовать свой ответ: p

На основе функции "pickRandomValues" я сделал ее динамической, и вы также можете выбрать количество букв, которое хотите.

function pickRandom(&$array = [], $count = 1) {
    $valuesFound = [];
    if (count($array) >= $count) {
        for ($i = 0; $i < $count; $i++) {
            $index = rand(0, count($array) - 1);
            $tmp = array_splice($array, $index, 1);
            $valuesFound[] = $tmp[0];
        }
    }
    return $valuesFound;
}

И ваш код тела

$tmpArr = [];
foreach ($a as $letter => $count) {
  $t = [];
  $tmpArr = array_merge($tmpArr, array_pad($t, $count, $letter));
}

$pairs = [];
while (count($tmpArr) >= $numberOfLetters) {
    $pick = pickRandom($tmpArr, $numberOfLetters);
    $pairs[] = implode($pick);
}

$finalArray = [
    "paired" => $pairs,
    "unpaired" => array_count_values($tmpArr)
];

Итак, это дает что-то вроде этого:

http://sandbox.onlinephpfunctions.com/code/3b143396b7267bd6029ff613746d6c047557a282

-1
Esteban MANSART 5 Ноя 2020 в 08:21