PHP recursion call to generate an array

Viewed 75

I'm trying to generate an array of all possible data based on 2 arrays:

$arr1 = ['a', 'b', 'c'];
$arr2 = [true, false];

The result should be something like:

[
    [
        "a" => true,
        "b" => true,
        "c" => true
    ],
    [
        "a" => true,
        "b" => false,
        "c" => true
    ],
    [
        "a" => true,
        "b" => false,
        "c" => false
    ],
    [
        "a" => true,
        "b" => true,
        "c" => false
    ],
    [
        "a" => false,
        "b" => true,
        "c" => true
    ]
    ...
]

This is what I've done so far:

function generateAllCases($arr1, $arr2)
{
    $resultArr = [];

    foreach ($arr1 as $i => $elm)
    {
        array_shift($arr1);

        foreach ($arr2 as $vis)
        {
            $resultArr[] =
            [
                $elm => $vis
            ];

            $resultArr[] =  $this->generateAllCases($arr1, $arr2);
        }
    }
    return $resultArr;
}

generateAllCases(['a', 'b', 'c'], [true, false]);

And I'm getting correct results but the array is not formatted as I proposed, I tried different ways to do it, but had no luck to get correct results. I can't get my head around it.

EDIT: if is there a better way to do the loop please let me know.

Any help would be appreciated.

2 Answers

It seems you're looking for all permutations with repetition of all elements of $arr2 with length of the number of elements in $arr1.

If so, the following should work:

<?php
declare(strict_types=1);

error_reporting(-1);
ini_set('display_errors', 'On');

function pwr(array $elements, int $k, int $idx = 0, array &$result = []): \Generator
{
    foreach ($elements as $element) {
        $result[$idx] = $element;

        if ($k - $idx > 1) {
            yield from pwr($elements, $k, $idx + 1, $result);
        }
        else {
            yield $result;
        }
    }
}

function gen(array $keys, array $values): \Generator
{
    foreach (pwr($values, \count($keys)) as $set) {
        yield array_combine($keys, $set);
    }
}

// this is just to test the result in a more *visual* way
foreach (gen(range('a', 'j'), [true, false]) as $case) {
    foreach ($case as $k => $v) {
        echo $v ? $k : '_';
    }

    echo "\n";
}

To avoid memory issues you can use yield, but if an array is absolutely required use iterator_to_array.

Obviously this grows extremely quickly (\count($arr2) ** \count($arr1)), so be careful when using iterator_to_array.

demo: https://3v4l.org/l5PRo

I finally managed to do it. it's not the best solution but it works. Also if we have more than 10 items the process starts to slow down because it gets multiplied every time we add new item:

public function generateAllCases($arr1, $arr2, $resultArr, &$index, $firstCall = false)
{
    $shifted = false;

    foreach ($arr1 as $elm)
    {
        foreach ($arr2 as $i => $vis)
        {
            if(!$shifted)
            {
                array_shift($arr1);
                $shifted = true;
            }

            if(!isset($resultArr[$index]) || !isset($resultArr[$index][$elm]))
            {
                $resultArr[$index][$elm] = $vis;
            }
            else 
            {
                $prevItem = $resultArr[$index];
                $index++;
                $resultArr[$index] = $prevItem;
                $resultArr[$index][$elm] = $vis;
            }

            $resultArr = $this->generateAllCases($arr1, $arr2, $resultArr, $index);
        }
        break;
    }

    if($firstCall)
    {
        $allResults = [];
        foreach ($resultArr as $k => $v) 
        {
            $allResults[implode('.', $v)] = $v;
        }

        $allResults = array_values($allResults);
        return $allResults;
    }

    return $resultArr;
}

called like this:

$index = 0;
$cases = $rpd->generateAllCases(['a', 'b', 'c'], [true, false], [], $index, true);
Related