Finding cartesian product with PHP associative arrays

前端 未结 10 1857
醉梦人生
醉梦人生 2020-11-22 04:17

Say that I have an array like the following:

Array
(
    [arm] => Array
        (
            [0] => A
            [1] => B
            [2] => C
         


        
相关标签:
10条回答
  • 2020-11-22 04:39

    Another solution:

    function getAllVariations($input) {
        $result = array();
        $cnt = array_product(array_map('count', $input));
        $step = 1;
        foreach ($input as $key=>$array) {
            for ($i=0; $i<$cnt; $i++) {
                foreach ($array as $value) {
                    for ($k=0; $k<$step; $k++) {
                        $result[$i+$k][$key] = $value;
                    }
                    $i += $step;
                }
                $i--;
            }
            $step = $step * count($array);
        }
        return $result;
    }
    

    Usage:

    $input = array(
        'arm' => array('A', 'B', 'C'),
        'gender' => array('Female', 'Male'),
        'location' => array('Vancouver', 'Calgary'),
        'name' => array('Rio', 'Mark')
    );
    
    echo "<pre>";
    var_dump(getAllVariations($input));
    
    0 讨论(0)
  • 2020-11-22 04:40

    If memory consumption is important or you don't need all the combinations in the end you could use an iterator to generate one combination at a time. If you need all the combinations you can use iterator_to_array.

    function cartezianIterator($inputArray)
    {
        $maximumPosition = array_map('count', $inputArray);
        $position = array_pad([], count($inputArray), 0);
    
        while (false !== ($item = buildItemAtPosition($inputArray, $position))) {
    
            yield $item;
    
            $position = incrementPosition($position, $maximumPosition);
        }
    }
    
    function buildItemAtPosition($inputArray, $positions)
    {
        if ($positions[0] >= count($inputArray[0])) {
            return false;
        }
    
        $item = [];
        foreach ($inputArray as $rowIndex => $row) {
            $position = $positions[$rowIndex];
    
            $item[] = $row[$position];
        }
    
        return $item;
    }
    
    function incrementPosition($position, $maximumPosition)
    {
        $digitToIncrement = count($position) - 1;
    
        do {
            $position[$digitToIncrement]++;
    
            if ($position[$digitToIncrement] < $maximumPosition[$digitToIncrement] || 0 === $digitToIncrement) {
                //no overflow
                break;
            }
    
            //overflow, reset to zero and increment parent digit
            $position[$digitToIncrement] = 0;
    
            $digitToIncrement--;
        } while ($digitToIncrement >= 0);
    
        return $position;
    }
    

    Then, to get one solution at a time you could use a foreach or next, like this:

    $iterator = cartezianIterator($inputArray);
    
    //of course, you need to do something with the result...
    $combination = next($iterator);
    $combination = next($iterator);
    $combination = next($iterator);
    $combination = next($iterator);
    $combination = next($iterator);
    $combination = next($iterator);
    

    This solution is very very fast if you need only a few combinations. Also, the memory consumption is very low (it uses a flat array to store some integers).

    Note: recursive functions are not used.

    0 讨论(0)
  • 2020-11-22 04:41

    Here is optimized version of @Jon's cartesian function:

    function cartesian($input) {
        $result = array(array());
    
        foreach ($input as $key => $values) {
            $append = array();
    
            foreach($result as $product) {
                foreach($values as $item) {
                    $product[$key] = $item;
                    $append[] = $product;
                }
            }
    
            $result = $append;
        }
    
        return $result;
    }
    

    Read more about the math behind this algorithm: http://en.wikipedia.org/wiki/Cartesian_product

    See more examples of this algorithm in different languages: https://rosettacode.org/wiki/Cartesian_product_of_two_or_more_lists

    0 讨论(0)
  • 2020-11-22 04:52

    In PHP 7 @Serg's answer can be shortened to:

    function cartesian(array $input)
    {
        $result = [[]];
        foreach ($input as $key => $values) {
            $append = [];
            foreach ($values as $value) {
                foreach ($result as $data) {
                    $append[] = $data + [$key => $value];
                }
            }
            $result = $append;
        }
    
        return $result;
    }
    
    0 讨论(0)
  • Why not use a database to do this?

    It's easy in MySQL..

    table arm
       id integer primary key
       label char
    
    table gender
       id integer primary key
       gender enum('male','female')
    
    table location
       id integer primary key
       city varchar(255)
    

    Then do a query

    $query = mysql_query(" 
      SELECT a.label, g.gender, l.city
      FROM arm a
      CROSS JOIN gender g
      CROSS JOIN location l
      ORDER BY a.id
    ") or die("Could not execute query");
    
    while($row = mysql_fetch_array($query) )
    {
       ....
    }
    

    And read that out:

    0 讨论(0)
  • 2020-11-22 04:56

    I quickly adjusted your code a bit , my attempt is crude i think but see if this works as you want:

    $result = array();
    $nm = '';
    foreach ($map as $name => $a) {
        if (empty($result)) {
            $result = $a;
            $nm = $name;
            continue;
        }
    
        $res = array();
        foreach ($result as $r) {
            foreach ($a as $v) {
                $myr = $r;
                $myv = $v;
                if(!is_array($r)) $myr = array($nm => $r);
                if(!is_array($v)) $myv = array($name => $v);
    
                $res[] = array_merge($myr, $myv);
            }
        }
        $result = $res;
    }
    echo "<pre>";
    print_r($result);
    
    0 讨论(0)
提交回复
热议问题