受限制的笛卡尔积计算 - PHP

时间:2013-04-26 07:46:00

标签: php arrays combinations cartesian-product

编辑1 - 自发布以来我了解到基本问题是关于如何找到CARTESIAN PRODUCT(现在去谷歌),但不仅因为我不想要每一个烫发,我想找到使用的笛卡尔产品同样的子阵列密钥永远不会超过一次,而我的“额外”问题更多的是关于如何最大限度地减少笛卡尔产品所需的工作量(接受一个小的错误率,我不得不说) -

想象一下......我有四个厨师和四个食谱,每个厨师都有每个食谱的分数,今天我希望每个厨师做一道菜(但不应该做两次菜),决定应该基于所有四个人的最佳(最高总分)排列(所以也许一个厨师不会使他个人最好)。

我已将数据放入多维数组中

 array(
   array (1,2,3,4),
   array (35,0,0,0),
   array (36,33,1,1),
   array (20,20,5,3)
 )
  • 每个子数组中的值对与子数组的数量相同(如果有帮助的话)

  • 实际上,子阵列的数量最多会达到8(因此最大烫数= 8!,大约40,000不是8 ^ 8,因为不允许使用许多组合)

  • 如果有帮助

  • ,选择这种格式的数据是灵活的

我正在尝试创建第二个数组,根据KEYs输出子数组的最佳(即最高值)可能组合,其中每个子数组只能使用一个

- 所以这里每个子阵列[0] [1] [2] [3]每个排列使用一次 并且每个subarrayKey [0] [1] [2] [3]将使用一次permutaion,在我的实际问题中我正在使用相关的数组,但这是这个问题的额外内容.--

因此该示例将创建一个数组 newArray(35,33,5,4)//请注意[2] [0]未使用

理想我宁愿不制作ALL烫发,但是,SOMEHOW,丢弃许多明显不合适的组合。

关于如何开始的任何想法?我会接受伪代码。

有关笛卡尔积的SO的示例,请参阅PHP 2D Array output all combinations

编辑2 更多关于如何提高笛卡尔积的效率,以及为什么它必须是特定于案例的,如果你想看看是否可以偷工减料(有风险)Efficient Cartesian Product algorithm

4 个答案:

答案 0 :(得分:2)

道歉,但这将是一个比代码更多的逻辑布局......

我不太清楚阵列(1,2,3,4)是第一道菜还是第一道菜的得分,但我可能会使用这样的阵列

$array[$cook_id][$dish_number] = $score;

asort()每个数组使$ array [$ cook_id] = array($ lowest_scored_dish,...,$ highest);

考虑一个特定厨师的加权偏好,使菜肴成为最佳菜肴与其他菜肴得分之间的差异。

作为一个非常简单的例子,烹饪a,b,c和菜肴0,1,2

$array['a'] = array(0=>100, 1=>50, 2=>0); // cook a prefers 0 over 1 with weight 50, over 2 with weight 100
$array['b'] = array(0=>100, 1=>100, 2=>50); // cook b prefers 0,1 over 2 with weight 50
$array['c'] = array(0=>50, 1=>50, 2=>100); // cook c prefers 2 with weight 50

在asort()之后:     $ array ['a'] = array(0 => 100,1 => 50,2> 0);     $ array ['b'] = array(0 => 100,1 => 100,2 => 50);     $ array ['c'] = array(2 => 100,0 => 50,1 => 50);

从烹饪'a'开始,他喜欢菜0,超过他的下一个最好的菜50分(体重)。 Cook'b'也喜欢dih 0,但是在下一道菜中重量为0。因此,它很可能(虽然尚不确定烹饪'a'应该制作菜0。

考虑保留菜0并继续烹饪'b'。不包括菜0,厨师'b'更喜欢菜1.没有其他厨师喜欢菜1,所以烹饪'b'被分配菜1。

库克'c'默认获得碟2。

这是一个非常方便的例子,每个厨师都可以烹饪一些个人最大的东西,但我希望它能说明一些可以解决的逻辑。

让我们不那么方便:

$array['a'] = array(0=>75, 1=>50, 2=>0);
$array['b'] = array(0=>100, 1=>50, 2=>50);
$array['c'] = array(0=>100, 1=>25, 2=>25);

再次尝试烹饪'a'并看到0是首选,但这次是重量25.烹饪'b'更喜欢重量为50而烹饪'c'更喜欢重量为75.烹饪'c'赢得菜0。

回到可用厨师名单,'a'喜欢1,体重50,但'b'更喜欢它的重量为0.'a'得到菜1,'b'得到菜2。

这仍然没有解决所有复杂问题,但它是朝着正确方向迈出的一步。有时,对第一个烹饪/菜肴组合的假设是错误的。

方便不太方便:

$array['a'] = array(0=>200, 1=>148, 2=>148, 3=>0);
$array['b'] = array(0=>200, 1=>149, 2=>0, 3=>0);
$array['c'] = array(0=>200, 1=>150, 2=>147, 3=>147);
$array['d'] = array(0=>69, 1=>18, 2=>16, 3=>15);

'a'得到0,因为那是最大值,没有其他人喜欢0的权重更高 'b'赢得1,体重149 'd'胜2,因为'c'没有可用选项的偏好 'c'得到3

得分:200 + 149 + 147 + 16 = 512

虽然这是一个很好的猜测,但没有检查每个排列,但这可能是错误的。从这里开始,问:“如果一个厨师和其他任何一个厨师交易,那么总量会增加吗?”

答案是肯定的,a(0)+ d(2)= 200 + 16 = 216,但a(2)+ d(0)= 148 + 69 = 217。

我会留给你用加权方法编写“最佳猜测”的代码,但在那之后,这是一个很好的开始:

// a totally uneducated guess...
$picks = array(0=>'a', 1=>'b', 2=>'c', 3=>'d');

do {
    $best_change = false;
    $best_change_weight = 0;
    foreach ($picks as $dish1 => $cook1) {
        foreach ($picks as $dish2 => $cook2) {
            if (($array[$cook1][$dish1] + $array[$cook2][$dish2]) <
                ($array[$cook1][$dish2] + $array[$cook2][$dish1]))
            {
                $old_score = $array[$cook1][$dish1] + $array[$cook2][$dish2];
                $new_score = $array[$cook1][$dish2] + $array[$cook2][$dish1];
                if (($new_score - $old_score) > $best_change_weight) {
                    $best_change_weight = $new_score - $old_score;
                    $best_change = $dish2;
                }
            }
        }
        if ($best_change !== false) {
            $cook2 = $picks[$best_change];
            $picks[$dish1] = $cook2;
            $picks[$dish2] = $cook1;
            break;
        }
    }
} while ($best_change !== false);

我找不到一个反例来证明这不起作用,但我怀疑这种情况     ($ array [$ cook1] [$ dish1] + $ array [$ cook2] [$ dish2])     ==     ($ array [$ cook1] [$ dish2] + $ array [$ cook2] [$ dish1])

也许其他人会跟进这个“怎么回事?”的答案。

给定这个矩阵,括号中的项目是“选择”

[a1]   a2   a3
 b1   [b2]  b3
 c1    c2  [c3]

如果a1 + b2 == a2 + b1,那么'a'和'b'将不会切换菜肴。我不是100%肯定的情况是,如果存在矩阵,这是一个更好的选择:

 a1   [a2]   a3
 b1    b2   [b3]
[c1]   c2    c3

从第一个状态到第二个状态需要两个开关,第一个似乎是任意的,因为它不会改变总数。但是,只有通过这种任意改变才能进行最后一次切换。

我试图找到一个例子3x3这样基于我上面写的“加权偏好”模型,第一个将被选中,但也是这样,真正的最佳选择由第二个给出。我无法找到一个例子,但这并不意味着它不存在。我现在不想做更多矩阵代数,但也许有人会在我离开的地方继续学习。哎呀,也许这个案子不存在,但我想我应该指出这个问题。

如果它确实有效且你从正确的选择开始,上面的代码将只循环64次(8x8)8个厨师/菜。如果选择不正确并且第一个厨师有变化,那么它将达到72.如果第8个厨师有变化,则最多为128.这可能会多次循环,但我怀疑它将接近所有40k组合所需的CPU周期。

答案 1 :(得分:1)

我可能有一个起点,这个算法试图根据最大分数与分数总和的比例选择厨师(因此试图选择真正擅长一种食谱而不是其他食谱的厨师)做食谱的食谱)

$cooks = array(
    array(1,2,3,4),
    array(35,0,0,0),
    array(36,33,1,1),
    array(20,20,5,3)
);
$results = array();

while (count($cooks)) {
    $curResult = array(
        'cookId' => -1,
        'recipe' => -1,
        'score'  => -1,
        'ratio'  => -1
    );
    foreach ($cooks as $cookId => $scores) {
        $max = max($scores);
        $ratio = $max / array_sum($scores);
        if ($ratio > $curResult['ratio']) {
            $curResult['cookId'] = $cookId;
            $curResult['ratio']  = $ratio;
            foreach ($scores as $recipe => $score) {
                if ($score == $max) {
                    $curResult['recipe'] = $recipe;
                    $curResult['score']  = $score;
                }
            }
        }
    }

    $results[$curResult['recipe']] = $curResult['score'];
    unset($cooks[$curResult['cookId']]);
    foreach ($cooks as &$cook) {
        unset($cook[$curResult['recipe']]);
    }
}

对于提供的数据集,它确实找到了最佳答案(35,33,5,4)。但是,它仍然不完美,例如,使用数组:

$cooks = array(
    array(1,2,3,4),
    array(35,0,33,0),
    array(36,33,1,1),
    array(20,20,5,3)
);

理想的答案是(20,33,33,4),但是这个算法会返回(35,33,5,4)。

但是既然问题是要求从哪里开始的想法,我想这至少可以作为开始的东西:P

答案 2 :(得分:0)

试试这个

$mainArr = array(
   array (1,2,3,4) ,
   array (35,0,0,0) ,
   array (36,33,1,1) ,
   array (20,20,5,3) 
 );
$i = 0;
foreach( $mainArr as $subArray )
{
    foreach( $subArray as $key => $value)
    {
        $newArr[$key][$i]=$value;
        $i++;   
    }
}
$finalArr = array();
foreach( $newArr as $newSubArray )
{
    $finalArr[] = max($newSubArray);    
}
print_r( $finalArr );

答案 3 :(得分:0)

好的这里是一个解决方案,可以让你找到一个厨师到一个食谱的最佳排列,没有厨师工作两次,没有配方两次。

感谢计算数组perm的代码转到o'reilly ... http://docstore.mik.ua/orelly/webprog/pcook/ch04_26.htm

注意事项:

  • 厨师人数和食谱数量相同。

  • 超过5乘5矩阵,这将非常快。 (见第2部分即将发布)

逻辑: 数组的排列分配一个位置以及仅包含(即组合的功能),所以为什么不将这样的数组的每个键分配给一个配方,排列保证不重复烹饪,键保证没有食谱重复。

如果我的想法或代码中有改进或错误,请告诉我,但现在就是这样!

<?php

function pc_next_permutation($p, $size) {
//this is from http://docstore.mik.ua/orelly/webprog/pcook/ch04_26.htm
    // slide down the array looking for where we're smaller than the next guy
    for ($i = $size - 1; $p[$i] >= $p[$i+1]; --$i) { }

    // if this doesn't occur, we've finished our permutations
    // the array is reversed: (1, 2, 3, 4) => (4, 3, 2, 1)
    if ($i == -1) { return false; }

    // slide down the array looking for a bigger number than what we found before
    for ($j = $size; $p[$j] <= $p[$i]; --$j) { }

    // swap them
    $tmp = $p[$i]; $p[$i] = $p[$j]; $p[$j] = $tmp;

    // now reverse the elements in between by swapping the ends
    for (++$i, $j = $size; $i < $j; ++$i, --$j) {
         $tmp = $p[$i]; $p[$i] = $p[$j]; $p[$j] = $tmp;
    }

    return $p;
}
$cooks[441] = array(340=>5,342=>43,343=>50,344=>9,345=>0);
$cooks[442] = array(340=>5,342=>-33,343=>-30,344=>29,345=>0);
$cooks[443] = array(340=>5,342=>3,343=>0,344=>9,345=>10,);                     
$cooks[444] = array(340=>25,342=>23,343=>20,344=>19,345=>20,); 
$cooks[445] = array(340=>27,342=>27,343=>26,344=>39,345=>50,); 

//a consideration: this solution requires that the number of cooks equal the number of recipes
foreach ($cooks as $cooksCode => $cooksProfile){
        $arrayOfCooks[]=$cooksCode;
        $arrayOfRecipes = (array_keys($cooksProfile));
}
echo "<br/> here is the array of the different cooks<br/>";
print_r($arrayOfCooks);
echo "<br/> here is the array of the different recipes<br/>";
print_r($arrayOfRecipes);

$set = $arrayOfCooks;
$size = count($set) - 1;
$perm = range(0, $size);
$j = 0;

do { 
     foreach ($perm as $i) { $perms[$j][] = $set[$i]; }
} while ($perm = pc_next_permutation($perm, $size) and ++$j);
echo "<br/> here are all the permutations of the cooks<br/>";
print_r($perms);

$bestCombo = 0;
foreach($perms as $perm){
    $thisScore =0;
        foreach($perm as $key =>$cook){
        $recipe= $arrayOfRecipes[$key];
        $cookScore =$cooks[$cook][$recipe];
        $thisScore = $thisScore+$cookScore;
        }
    if ($thisScore>$bestCombo){
        $bestCombo=$thisScore;
        $bestArray= $perm;
    }
}



echo "<br/> here is the very best array<br/>";
print_r ($bestArray);
echo "<br/> best recipe assignment value is:".$bestCombo."<br/><br/>";  




?>