Finding all non-conflicting combinations of values from multiple lists of values

前端 未结 9 1025
没有蜡笔的小新
没有蜡笔的小新 2021-01-01 05:34

I have the following array which contains arrays of values:

$array = array(
    array(\'1\', \'2\'),
    array(\'a\', \'b\', \'c\'),
    array(\'x\', \'y\'),         


        
相关标签:
9条回答
  • 2021-01-01 05:56

    Look at it from a different angle: in order to compose a result row, you need to pick a value for each column. Each value should be picked from a different source row. The problem is know as "pick N out of M", or more mathematically, a Combination.

    This means that a result row corresponds to an array of source-row indices.

    You can build all possible pickings by starting to build an index-array like this (pseudo-code)

    function combinations( $source ) {
      if( count( $source ) == 0 ) return $source;
      $result=array();
      // build one row
      foreach( $source as $index=>$value ) {
        $newsource = array_splice( $source, $index, 1 );
    
        $reduced_combinations=combinations( $newsource  );
        foreach( $reduced_combinations as $reduced_combi ) {
          $newrow=array_unshift( $reduced_combi, $value );
          $result[]=$newrow;
        }
    
      }
      return $result;
    }
    
    function retrieve_indices( $indices, $arrays ) {
       $result=array();
       foreach( $indices as $column=>$index ) {
         $result[]=$arrays[$index][$column];
       }
       return $result;
    }
    
    $source_arrays = array(
      array( "1", "2", "3" ),
      array( "a", "b", "c" ),
      array( "x", "y", "z" )
    );
    
    $index_combinations = combinations( range(0,2) );
    $result=array();
    foreach( $index_combinations as $combination ) {
      $result[]=retrieve_indices( $combination, $source_arrays );
    }
    
    0 讨论(0)
  • 2021-01-01 06:05

    This is one of those cases where self-generated code and brute force will beat most algorithms on simplicity and performance. In previous answers I've seen lots of recursion, array manipulation and computations, when in actuality what you'd want to do is:

    foreach ($array[0] as $k0 => $v0)
    {
        foreach ($array[1] as $k1 => $v1)
        {
            if ($k1 == $k0)
            {
                continue;
            }
            foreach ($array[2] as $k2 => $v2)
            {
                if ($k2 == $k1 || $k2 == $k0)
                {
                    continue;
                }
                $result[] = $v0.$v1.$v2;
            }
        }
    }
    

    Of course, you cannot write this unless you know how many arrays are in $array. That's where generated code comes handy:

    $array = array(
        array('1', '2'),
        array('a', 'b', 'c'),
        array('x', 'y')
    );
    $result = array();
    
    $php = '';
    foreach ($array as $i => $arr)
    {
        $php .= 'foreach ($array[' . $i . '] as $k' . $i . ' => $v' . $i . '){';
    
        if ($i > 0)
        {
            $sep  = 'if (';
            $j    = $i - 1;
            do
            {
                $php .= $sep . '$k' . $i . ' == $k' . $j;
                $sep  = ' || ';
            }
            while (--$j >= 0);
    
            $php .= ') { continue; } ';
        }
    }
    
    $php .= '$result[] = $v' . implode('.$v', array_keys($array)) . ';' . str_repeat('}', count($array));
    
    eval($php);
    print_r($result);
    

    Note that this routine assumes that $array is a zero-based numerically indexed array, as in your example. It will generate the code quoted above, adjusted for an arbitrary number of arrays.


    Update

    Here's an alternative algorithm. It's still self-generated, but less bruteforce. We still have nested loops, except that each loop works on a copy of the array where keys that are currently used by outer loops have been removed from this loop's array. For example, if the values should be (a,b,c) but the outer loops are using the indices 0 and 2, we remove "a" (index 0) and "c" (index 2) and all we have left is "b". It means that loops work only on possible combinations and we don't need a if condition anymore.

    Furthermore, and this part can be applied to the previous algorithm as well, we process the arrays of values in order from the smallest to the largest so that we guarantee that used indices exist in current array. The downside is it doesn't generate the combinations in the same order. It generates the same combinations, just not in the same order. The code looks like this:

    $a0 = $array[0];
    foreach ($a0 as $k0 => $v0)
    {
        $a2 = $array[2];
        unset($a2[$k0]);
        foreach ($a2 as $k2 => $v2)
        {
            $a1 = $array[1];
            unset($a1[$k0], $a1[$k2]);
            foreach ($a1 as $k1 => $v1)
            {
                $result[] = "$v0$v1$v2";
            }
        }
    }
    

    The above routine sets up a copy of the values at the beginning of every loop, then removes values that are used by outer loops. You can improve this process by setting up a copy of the values only once at the beginning, remove the keys as they are used (at the beginning of each loop) and put them back as they are freed (at the end of each loop). The routine then looks like this:

    list($a0,$a1,$a2) = $array;
    foreach ($a0 as $k0 => $v0)
    {
        unset($a1[$k0], $a2[$k0]);
        foreach ($a2 as $k2 => $v2)
        {
            unset($a1[$k2]);
            foreach ($a1 as $k1 => $v1)
            {
                $result[] = "$v0$v1$v2";
            }
            $a1[$k2] = $array[1][$k2];
        }
        $a1[$k0] = $array[1][$k0];
        $a2[$k0] = $array[2][$k0];
    }
    

    The actual code that generates the source above is:

    $keys = array_map('count', $array);
    arsort($keys);
    
    $inner_keys = array();
    foreach ($keys as $k => $cnt)
    {
        $keys[$k] = $inner_keys;
        $inner_keys[] = $k;
    }
    
    $result = array();
    
    $php = 'list($a' . implode(',$a', array_keys($array)) . ')=$array;';
    foreach (array_reverse($keys, true) as $i => $inner_keys)
    {
        $php .= 'foreach ($a' . $i . ' as $k' . $i . ' => $v' . $i . '){';
    
        if ($inner_keys)
        {
            $php .= 'unset($a' . implode('[$k' . $i . '],$a', $inner_keys) . '[$k' . $i . ']);';
        }
    }
    
    $php .= '$result[] = "$v' . implode('$v', array_keys($array)) . '";';
    
    foreach ($keys as $i => $inner_keys)
    {
        foreach ($inner_keys as $j)
        {
            $php .= '$a' . $j . '[$k' . $i . ']=$array[' . $j . '][$k' . $i . "];\n";
        }
        $php .= '}';
    }
    eval($php);
    
    0 讨论(0)
  • 2021-01-01 06:09

    try this :

    function algorithmToCalculateCombinations($n, $elems) {
            if ($n > 0) {
                $tmp_set = array();
                $res = algorithmToCalculateCombinations($n - 1, $elems);
                foreach ($res as $ce) {
                    foreach ($elems as $e) {
                        array_push($tmp_set, $ce . $e);
                    }
                }
                return $tmp_set;
            } else {
                return array('');
            }
        }
    
    $Elemen = array(range(0,9),range('a','z'));
    $Length = 3;
    $combinations = algorithmToCalculateCombinations($Length, $Elemen);
    
    0 讨论(0)
  • 2021-01-01 06:12

    Your problem is similar to that of finding a determinant of a matrix. The best way imho would be to fill smaller arrays with some symbol, say '0', so they all have equal number of values, in your example

    $array = array(
        array('1', '2', '0'),
        array('a', 'b', 'c'),
        array('x', 'y', '0'),
    );
    

    then loop through each of the first array values and for each increment the index of array by 1 and check the next array and the next column (on the first loop it will be '1' and index will be 0 incremented - 1, then get $array1 - 'b' and so on) if you reach '0', break, if you reach right border, reset first index to 0. Then do the same with decrementing and you'll have all the combinations. It is probably unclear, check the image I linked to

    0 讨论(0)
  • 2021-01-01 06:16

    I think this works. It is using recursion to go over the structure like a tree. For each branch it keeps track of which columns are already taken. It is probably slow because it is a brute force approach.

    <?php 
    
    $array = array(
        array('1', '2'),
        array('a', 'b', 'c'),
        array('x', 'y'),
    );
    
    
    function f($array, & $result, $colsToIgnore = array(), $currentPath = array()) {
        $row = array_shift($array);
        $length = count($row);
        $isMoreRows = !! count($array);
    
        for ($col = 0; $col < $length; $col++) {
            //check whether column has already been used
            if (in_array($col, $colsToIgnore)) {
                continue;   
            }
    
            $item = $row[$col];
    
            $tmpPath = $currentPath;
            $tmpPath[] = $item;
    
            if ($isMoreRows) {
                $tmpIgnoreCols = $colsToIgnore;
                $tmpIgnoreCols[] = $col;
                f($array, $result, $tmpIgnoreCols, $tmpPath);
            } else {
                $result[] = implode('', $tmpPath);
            }
    
        }
    }
    
    
    $result = array();
    f($array, $result);
    print_r($result);
    
    0 讨论(0)
  • 2021-01-01 06:16

    probably not the most elegant way, but does the trick (javascript)

    var result = [];
    
    for(i=0;i<arr1.length;i++)
    {
      for(j=0;j<arr2.length;j++)
      {
        if(j==i)
          continue;
        else
        {
          for(k=0;k<arr3.length;k++)
          {
            if(k==i||k==j)
              continue;
            else
            {
              result.push(arr1[i]+arr2[j]+arr3[k]);
            }
          }
        }
      }
    }
    
    0 讨论(0)
提交回复
热议问题