生成组合并根据三个参数选择最佳

时间:2017-09-01 19:27:33

标签: php

我尝试生成对,然后想根据设置参数选择最佳对。生成对并不困难,但是选择最好的对是很棘手的。

我认为如果我继续举例,那就最好了,我们现在有4个元素,并将它们命名为element1element2,{{ 1}},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]. - 这是一组可以与当前元素配对的其他元素。在目前的例子中,我确实有4个元素,但是其中一些元素不能配对在一起,因为它们之前可能已经配对在一起(并且它不允许将某人配对两次)。此约束适用于函数Possible pairs
  • generatePairsWithElements - 这是一个唯一的整数值,对于两个元素不能相同。选择配对元素时,将选择较低Place的元素。此约束适用于函数Place
  • 组合必须是唯一的 - 例如,如果chooseBest可以与element1element2配对,而element3只能与element4配对,那么{{即使先前迭代element2element1位置较低,也只能与element3配对。所以组合将是element1。此约束适用于函数element2

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

2 个答案:

答案 0 :(得分: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. 跟踪每个元素找到的匹配项数。通过这样做,任何只有一个可能匹配的元素将在$nbMatches的{​​{1}}中具有关联值。

    1
  3. 然后我添加了另一个循环来提升这些元素,使它们最终位于列表顶部,然后在其余元素之前进行处理。

    $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 1element 2 只匹配element 3,则无法保护您。但是,我们处理的样本量非常小,而且您拥有的元素越多,这些边缘情况就越不可能发生。 在我看来除非您只使用4个元素,否则不需要此修复。使用6个元素产生15种可能的组合,10种元素产生45种可能的组合。这些案件不太可能发生。

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

    您可以尝试更新的版本here

答案 1 :(得分:0)

所以我觉得我弄明白了,多亏了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并选择最佳,给每个排列组合一个&#39;得分&#39;:

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

计算最佳分数并返回排列对。