【问题标题】:Generate combinations and choose best based on three parameters生成组合并根据三个参数选择最佳组合
【发布时间】:2018-02-10 21:47:31
【问题描述】:

我正在尝试生成配对,然后想根据设置的参数选择最佳配对。生成对并不难,但棘手的是从中选出最好的。

我认为最好继续举例,假设我们目前有 4 个元素并将它们命名为 element1,element2,element3,element4。每个元素都有在生成对时很重要的属性:

while ($element = mysqli_fetch_assoc($queryToGetElements)){
    $elementId = $element['eid'];
    $elementOpponents = getElementOpponents($elementId); //Returns opponents Id's as an array. These are the possible elements which can be paired.
    $elementPairs = generatePairsWithElement($elementId,$elementOpponents); //Pair generating function, this uses the possible pairs which is defined below. Pretty much checks who hasn't paired before and puts them together in the pair.
    $bestPair = chooseBest($elementPairs,$allOtherPairs) // This should select the best pair out of the elementPairs considering uniqueness with other pairs.

}

//So let's define our four elements:
$element1Place = 1;
$element2Place = 2;
$element3Place = 3;
$element4Place = 4;
//execute while loop with $element1:
$element1Opponents = [$element2,$element3,$element4];
$elementPairs = [[$element1,$element3],[$element1,$element3],[$element1,$element4]];
$element2Opponents = [$element3]
$elementPairs = [[$element2,$element3],[$element2,$element1]];
$elemenet3Opponents = [$element2]
$elementPairs = [[$element2,$element3],[$element1,$element3]];
$element4Opponents = [$element1]
$elementPairs = [[$element1,$element4];
//Pairs returned should be: [$element1,$element4] & [$element2,$element3].
  • Possible pairs - 这是可以与当前元素配对的其他元素的数组。在当前示例中,我确实有 4 个元素,但其中一些元素不能配对在一起,因为它们之前可能已经配对在一起(并且不允许将某人配对两次)。此约束应用于函数generatePairsWithElements
  • Place - 这是一个唯一的整数值,两个元素不能相同。选择配对元素时,将选择Place 较低的元素。此约束应用于函数 chooseBest
  • 组合必须是唯一的 - 例如,如果element1 可以与element2element3 配对,并且element4 只能与element2 配对,那么element1 只能与element3 配对,即使element1 迭代较早,element2 位置较低。所以组合将是[element1,element3] and [element2,element4]。此约束应用于函数chooseBest

如果没有第三个方面,生成独特的组合,这项任务不会那么困难。迭代可能的对手并选择最好的对手非常容易,但是生成独特的组合对我的项目至关重要。

【问题讨论】:

  • 将所有的pairs放入一个数组中,然后按参数对数组进行排序。那么数组中的第一个元素将是最好的对。
  • 您对如何排序保证唯一性有什么建议吗?
  • 这是创建所有配对的一部分。你说那部分并不难。
  • 我认为您误解了或者我措辞不当。可能的pair在开始时不会过滤掉唯一性约束,它只是映射尚未配对的人,并且约束在chooseBest函数中设置。
  • 生成对时,将它们放入数组中。在向阵列添加新对之前,请检查它是否已经存在。

标签: php


【解决方案1】:

所以我想以免责声明作为这个答案的序言:我不是数学家;我对线性代数、矩阵代数和统计的理解是足够的,但绝不是广泛的。可能有一些方法可以用更少的代码行或更有效地实现相同的结果。不过,我相信这个答案的冗长程度会让更多的人理解它,并一步一步地遵循逻辑。既然已经不碍事了,让我们开始吧。

计算每对的重量

当前方法的问题在于,当您遍历查询结果时,会发生寻找最佳匹配的逻辑。这意味着当您进行最后一次迭代时,您可能会留下无法匹配在一起的元素。为了解决这个问题,我们需要稍微拆分一下流程。第一步是获取给定数组元素的所有可能对。

$elements = [
    1,2,3,4,5,6    
];

$possiblePairs = [];
for($i = 1; $i <= $elementCount; $i++) {
    for($j = $i + 1; $j <= $elementCount; $j++) {
        $possiblePairs[] = [
            'values' => [$elements[$i - 1], $elements[$j - 1]],
            'score' => rand(1, 100)
        ];
    }
}

如您所见,对于每个可能的配对,我还附加了一个score 元素,在这种情况下是一个随机整数。这个分数表示这对匹配的强度。这个score 的值实际上并不重要:它可以是特定范围内的值(例如:0-100)或由您自己的某些函数定义的每个值。这里重要的是,具有最高匹配潜力的配对应该具有更高的分数,而几乎没有潜力的配对应该具有较低的分数。如果某些对绝对不能在一起,只需将score 设置为零。

下一步是使用比score 值对数组进行排序,以便匹配度更高的对位于顶部,而较弱(或不可能)的对位于底部。我使用 PHP 7.0 的 spaceship operator 来完成这项工作,但您可以找到其他方法来实现这种排序 here

usort($possiblePairs, function($a, $b) {
    return $b['score'] <=> $a['score'];
});

现在我们终于准备好构建我们的最终输出了。这一步实际上相当简单:遍历可能的对,检查值是否尚未使用,然后将它们推送到输出数组。

$used = []; // I used a temporary array to store the processed items for simplicity
foreach($possiblePairs as $key => $pair) {
    if($pair['score'] !== 0 && // additional safety if two elements cannot go together
        !in_array($pair['values'][0], $used) && 
        !in_array($pair['values'][1], $used)) 
    {
        $output[] = $pair['values']; // push the values to the return of the function
        array_push($used, $pair['values'][0], $pair['values'][1]); // add the element to $used so they get ignored on the next iteration
    }
}

var_dump($output);

// output
array(3) {
    [0]=> array(2) {
        [0]=> int(2)
        [1]=> int(4)
    }
    [1]=>  array(2) {
        [0]=> int(1)
        [1]=> int(3)
    }
    [2]=> array(2) {
        [0]=> int(5)
        [1]=> int(6)
    }
}

就是这样!首先选择最强的对,然后优先级下降。玩转加权算法,看看什么适合您的特定需求。最后一句话:如果我是在业务应用程序的上下文中编写的,我可能会添加一些错误报告,如果碰巧有不匹配的对(毕竟,这在统计上是可能的),以便更容易发现问题的原因并决定是否需要调整权重。


你可以试试here的例子


编辑添加:

为了防止当另一个元素只能与第一个元素配对时,一个元素被存储在一个对中(这导致永远不会创建对),您可以尝试这个小补丁。我首先在 getScore() 函数中重新创建您的要求。

function getScore($elem1, $elem2) {
    $score;

    if($elem1 > $elem2) {
        $tmp = $elem1;
        $elem1 = $elem2;
        $elem2 = $tmp;
    }

    if($elem1 === 1 && $elem2 === 2) {
        $score = 100;
    }
    elseif(($elem1 === 3 && $elem2 !== 2) || ($elem1 !== 2 && $elem2 === 3)) {
        $score = 0;
    }
    else {
        $score = rand(0, 100);
    }

    return $score;
}

然后,我修改了$possiblePairs 数组的创建来做两件额外的事情。

  1. 如果分数为0,则不要追加到数组中
  2. 跟踪为每个元素找到了多少匹配项。通过这样做,任何只有一个可能匹配项的元素都将在 $nbMatches1 中具有关联值。

    $nbMatches = [
        1 => 0,
        2 => 0,
        3 => 0,
        4 => 0
    ]
    
    $possiblePairs = [];
    for($i = 1; $i <= $elementCount; $i++) {
        for($j = $i + 1; $j <= $elementCount; $j++) {
            $score = getScore($elements[$i - 1], $elements[$j - 1]);
            if($score > 0) {
                $possiblePairs[] = [
                    'values' => [$elements[$i - 1], $elements[$j - 1]],
                    'score' => $score
                ];
                $nbMatches[$elements[$i - 1]]++;
                $nbMatches[$elements[$j - 1]]++;
            }
        }
    }
    

然后我添加了另一个循环,该循环将增加这些元素,以便它们最终位于要处理的列表顶部,然后再处理其他元素。

foreach($nbMatches as $elem => $intMatches) {
    if($intMatches === 1) {
        foreach($possiblePairs as $key => $pair) {
            if(in_array($elem, $pair['values'])) {
                $possiblePairs[$key]['score'] = 101;
            }
        }
    }
}

usort($possiblePairs, function($a, $b) {
    return $b['score'] <=> $a['score'];
});

那么输出是:

array(2) {
    [0]=> array(2) {
        [0]=> int(2)
        [1]=> int(3)
    }
    [1]=> array(2) {
        [0]=> int(1)
        [1]=> int(4)
    }
}

我只想强调:这只是一个临时补丁。如果element 1 只能匹配element 2element 3 只能匹配element 2,它不会保护您。但是,我们正在处理非常小的样本量,并且您拥有的元素越多,这些极端情况发生的可能性就越小。 在我看来除非您只使用 4 个元素,否则没有必要进行此修复。使用 6 个元素会产生 15 种可能的组合,10 个元素会产生 45 种可能的组合。这些情况已经不太可能发生了。

另外,如果您发现那些边缘情况仍然发生,最好回过头来调整匹配算法以使其更灵活,或者考虑更多参数。 p>


你可以试试更新版here

【讨论】:

  • 嘿威廉,我还没有完全理解你的代码,但实际上不可能所有对都不匹配(至少在我的应用程序中)。我无法理解随机分值背后的原因,因为它可以将最好的对变成最差的对。
  • @Banana,我在这里使用随机值只是为了获得 some 数字,因为我不知道 your 的确切要求是什么。在代码中的那一点,您可以通过调用一个函数来替换它,该函数将返回一个代表该对匹配强度的数字,如果无法匹配,则返回 0
  • 嘿威廉,我已经完成了你的代码,它看起来很合法,虽然我确实想添加失败场景,有什么想法可以在可能的情况下添加它对被迭代但没有找到对?只需添加 else 语句并检查我是否在 possiblePairs 的最后一个元素中?
  • @Banana 有很多方法可以做到这一点,也有很多变量需要考虑,我不可能在这里一一列举。您可以抛出异常并继续,您可以抛出异常并停止执行,您可以简单地写入日志文件,或者您可以什么都不做并在工作流程中进一步处理它。在这一点上真的取决于你。 This question 有一些有趣的信息,我会从那里开始
  • 谢谢你,William,还有一个问题 - 首先选择最佳可能会导致我最初的问题中提到的问题。以数字 1,2,3,4 为例,数字 1 可以与 2 和 4 配对(从而形成配对 (1,2),(1,4))。现在,当数字 3 可以配对时,就会出现问题仅与数字 2 配对,因为 (1,2) 比 (1,4) 更好,因此首先选择 (1,2) (如果 (3,4) 则 (1,2) 将是合理的选择是可能的)。
【解决方案2】:

所以我想我弄明白了,感谢 William 和来自另一个线程的 ishegg。

因此,作为先决条件,我有一个数组$unmatchedPlayers,它是一个关联数组,其中元素名称是键,元素位置 (Place) 是值。

首先使用该信息,我正在生成唯一的排列对

function generatePermutations($array) {
    $permutations = [];
    $pairs = [];
    $i = 0;
    foreach ($array as $key => $value) {
        foreach ($array as $key2 => $value2) {
            if ($key === $key2) continue;
            $permutations[] = [$key, $key2];
        }
        array_shift($array);
    }
    foreach ($permutations as $key => $value) {
        foreach ($permutations as $key2=>$value2) {
            if (!in_array($value2[0], $value) && !in_array($value2[1], $value)) {
                $pairs[] = [$value, $value2];
            }
        }
        array_shift($permutations);
    }
    return $pairs;
}

这将返回一个名为 $pairs 的数组,其中包含不同可能对的数组,其方式为:$pairs= [[['One','Two'],['Three','Four']],[['One','Three'],['Two','Four']],[['One','Four'],['Two','Three']]];

现在我将遍历数组 $pairs 并选择最好的,给每个排列组合一个“分数”:

function chooseBest($permutations,$unmatchedPlayers){
    $currentBest = 0;
    $best = [];
    foreach ($permutations as &$oneCombo){ //Iterate over all permutations [[1,2],[3,4]],[..]
        $score = 0;
        foreach ($oneCombo as &$pair){
            $firstElement = $pair[0];
            $secondElement = $pair[1];
            //Check if these two has played against each other? If has then stop!
            if(hasPlayedTP($firstElement,$secondElement)){
                $score = 0;
                break;
            }
            $score += $unmatchedPlayers[$firstElement];
            $score += $unmatchedPlayers[$secondElement];
        }
        if($score > $currentBest){
            $currentBest = $score;
            $best = $oneCombo;
        }
    }
    return $best;
}

计算出最好的分数并返回排列对。

【讨论】:

    猜你喜欢
    • 2013-12-16
    • 2014-04-09
    • 2016-01-31
    • 2017-04-21
    • 1970-01-01
    • 1970-01-01
    • 2017-05-04
    • 2015-06-04
    • 1970-01-01
    相关资源
    最近更新 更多