一个受限的笛卡尔积计算 - PHP

5
EDIT 1 - 发布后我了解到底层问题是关于如何找到笛卡尔积(现在去谷歌),但不仅仅因为我不想要每个perm,我想找到使用相同子数组Key的笛卡尔积,每个排列中不超过一次,并且我的“额外”问题更多地涉及如何最小化笛卡尔积所需的工作量(接受小误差率,我必须说)-
想象一下...我有四位厨师和四个菜谱,每位厨师都对每个菜谱评分,今天我希望每位厨师做一道菜(但不能重复),决策应基于所有四个人的最佳排列(因此可能某位厨师不会做出自己的最佳表现)。
我已将数据放入多维数组中,如下所示。
 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,因为许多组合是不允许的)

  • 如果需要,可以灵活选择以这种格式存储数据

我正在尝试创建第二个数组,该数组将根据每个键的值输出可能的最佳(即最高值)子数组组合,每个子数组仅能使用一次

-- 因此,每个子数组[0] [1] [2] [3]将在每个排列中仅被使用一次,每个subarrayKey [0] [1] [2] [3]也将在每个排列中仅被使用一次,实际问题中我使用关联数组,但这与此问题无关。--

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

理想情况下,我希望不要生成所有排列,而是以某种方式丢弃许多明显不是最佳选择的组合。

有什么启动思路吗? 我将接受伪代码。

有关笛卡尔积的示例,请参见PHP 2D数组输出所有组合

编辑2 有关使笛卡尔积更有效的更多信息,以及可能为什么它必须是特定情况(如果您想知道是否可以缩短时间)Efficient Cartesian Product算法


1
如果N个子数组的最大值为8,则排列的最大值为4 ^ 8 = 65536。 - HamZa
据我所了解,每个厨师只能烹饪一道菜,因此实际排列组合的数量为432*1 = 4! = 24。考虑到只有4个厨师/菜谱,如果采用暴力计算也不是很困难。 - Pudge601
谢谢大家!我的想法确实是8!等于40,320。 - EnglishAdam
4个回答

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分。而厨师'b'也喜欢菜品0,但权重为0分。因此,目前看来厨师'a'应该做菜品0,但还不确定。

现在我们将菜品0视为已经被预定,然后考虑厨师'b'。除了菜品0外,厨师'b'最喜欢的是菜品1。由于没有其他厨师喜欢菜品1,所以菜品1被分配给了厨师'b'。

由于默认情况下,厨师'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);

从'cook a'开始重新尝试,发现0被优先选择,但这次权重为25。'cook b'偏爱权重为50的菜肴,而'cook c'则更喜欢权重为75的菜肴。'cook c' 赢得了第一道菜肴。

回到可用厨师列表,'cook a' 偏爱权重为50的第二道菜肴,但是'cook b' 偏好权重为0。'cook a' 得到了第二道菜肴,'cook b'得到了第三道菜肴。

这仍然无法解决所有复杂性,但是这是朝着正确方向迈出的一步。有时为第一个cook和dish组合所做的假设是错误的。

WAY不那么方便:

$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'以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])。也许其他人会回答这个“What if?”

给出以下矩阵,其中方括号中的项是“选择”:

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

如果a1 + b2 == a2 + b1,则“a”和“b”不会交换菜品。我不确定的情况是是否存在一个矩阵,使这是更好的选择:
 a1   [a2]   a3
 b1    b2   [b3]
[c1]   c2    c3

从第一个状态到第二个状态需要两个开关,第一个开关似乎是任意的,因为它不会改变总数。但是,只有通过这个任意的改变才能进行最后一个开关。
我试图找到一个3x3的例子,使得基于上述“加权偏好”模型,第一个会被选择,但实际上最佳选择是第二个。我没有找到这样的例子,但这并不意味着不存在这样的情况。我现在不想再做更多的矩阵代数,但也许有人会接替我的工作。说不定这种情况不存在,但我认为我应该指出这个问题。
如果起始选择正确,则上述代码仅循环64次(8×8),适用于8个厨师/菜品。如果选择不正确并且第一个厨师有所更改,则循环次数将增加到72次。如果第8个厨师有所更改,则会增加到128次。可能会对do-while循环多次,但我怀疑它不会接近于求和所有40k组合所需的CPU周期。

我非常喜欢这种思维方式(+1),非常感谢你的时间和努力,真心感谢。明天我会一整天都在研究这个问题,并且一定会尝试这种思路,因为问题正如你所想,也涉及到边际价值和差异,试图最大化分配。(顺便说一句,你对初始数组的想法正是我想要的,抱歉让你困惑了) - EnglishAdam
在得知我试图通过简化完整的笛卡尔积搜索来节省时间之后,我接受了这样一个事实:任何通过估算来缩短时间的方法都会产生错误。对我来说,另一个问题是这种配对需要在一个更大的游戏循环中每秒进行20次。我认为相比于40000次循环,这可能会节省很多时间(而且我意识到如果有人对这个加权思想感兴趣,Stack Overflow上甚至还有其他相关问题)。不过,我将尝试构思一些非常粗略但可行的想法。 - EnglishAdam

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

谢谢,不错,我真的很感激你的努力和清晰度,+1,虽然单独的逻辑不足以解决问题(正如你所展示的),但肯定可以帮助我们尝试减少需要做的40K个组合...再次感谢,明天我会全身心投入这个项目,看看会发生什么! - EnglishAdam
你好!我还在看这个代码,但是我没看到你如何检查厨师或食谱是否重复。有什么想法吗? - EnglishAdam
当我选择了一个厨师/食谱后,我会取消所有选定的厨师,并在剩余的厨师中取消该选定的食谱。 - Pudge601

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 );

谢谢,但是输出的数组为Array([0] => 36 [1] => 33 [2] => 5 [3] => 4),这只是每个人中最高的得分,其中cook [2] 制作了2个食谱[0]和[1]。 - EnglishAdam

0

好的,这里有一个解决方案,允许你找到一个厨师对一个食谱的最佳排列,每个厨师只工作一次,每个食谱只制作一次。

感谢用于计算数组排列的代码来自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/>";  




?>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接