优先随机选择

时间:2012-03-05 17:57:05

标签: php algorithm

我需要设计一种基于印记在网站上展示广告的算法。我在DB中记录每个印记。在我的查询中无法使用mysql rand() limit 1,因为它将是不受控制的查询。我必须确保如果广告被选中,它不仅仅是一个印记最少的广告,而且还有一些随机性。新广告也会以某种方式获得有偏见的选择。

为了实现这一点,我设法编写了一个功能,但在某个地方我对它不满意。请仔细阅读并告诉我它是否可以做得更好。

function getRandomAd($result){
while($row = mysql_fetch_assoc($result)){
     $rows[] = $row;
}
$filteredRow = array();
foreach($rows as $row){
    if($row['imprints'] == 0 ){
        $row['piority'] = 10;
        $filteredRow[] = $row;
    }
    else if($row['imprints'] >= 0 and $row['imprints'] <= 5){
        $row['piority'] = 8;
        $filteredRow[] = $row;
    }
    else if($row['imprints'] >= 5 and $row['imprints'] <= 15){
        $row['piority'] = 6;
        $filteredRow[] = $row;
    }
    else if($row['imprints'] >= 15 and $row['imprints'] <= 30){
        $row['piority'] = 4;
        $filteredRow[] = $row;
    }
    else if($row['imprints'] >= 30 and $row['imprints'] <= 60) {
        $row['piority'] = 2;
        $filteredRow[] = $row;
    }
    else{
        $row['piority'] = 0;
        $filteredRow[] = $row;
    }

    foreach($filteredRow as $row){
        if($row['piority'] == 10 or $row['piority'] == 8) $high[] = $row;
        if($row['piority'] == 6 or $row['piority'] == 4) $medium[] = $row;
        if($row['piority'] == 2 or $row['piority'] == 0) $low[] = $row;
    }

    $countHigh = count($high);
    $countMed = count($medium);
    $countLow = count($low);

    if($countHigh > $countLow and $countHigh > $countMed)$rowReturned = $high;
       if($countMed > $countLow and $countMed > $countHigh) $rowReturned = $medium; 
    if($countLow > $countMed and $countLow > $countHigh) $rowReturned = $low;

}
return $rowReturned[array_rand($rowReturned,1)];

}

2 个答案:

答案 0 :(得分:6)

返回所有行并使用PHP进行排序并确定要吐出的行非常低效。这在数据库端比在PHP中更好地处理。

此外,您当前的if语句集与>=运算符重叠。

最后,我知道你没有这样做,只是一般性说明:你几乎不应该使用ORDER BY RAND()与MySQL,因为性能很糟糕。这是因为当您使用函数返回值来排序结果时,MySQL无法使用索引进行订单操作。

这是一个更好的解决方案......

  1. 在PHP中生成加权随机数,我们可以将其用作查询的条件
  2. 根据加权随机数
  3. 编写一个只返回一个结果的查询

    因此,让我们生成一个随机整数加权到印记谱的下端:

    $max    = 60;
    $random = floor($max - mt_rand(mt_rand(1, $max), $max));
    

    并编写一个使用加权随机数从表中选择单个结果的查询:

    $query = '
      SELECT id, advert_txt
      FROM table
      WHERE imprints >= '.$random.'
      ORDER BY imprints ASC
      LIMIT 1
    ';
    

    请注意,您通常希望在这样的查询中转义PHP变量(或者最好使用带有PDO的预处理语句),但由于我们知道变量在这种情况下是整数,所以没关系。

    此解决方案将根据各自的印记计数对返回的结果进行适当加权,同时保持MySQL使用索引选择记录的能力,如果表中有大量行,这一点至关重要。

    <强>更新

    我想补充一点,你可以根据自己的需要调整随机数生成。 使用这个特殊的解决方案,你永远不会得到一个带有&gt; = 60印记的行。解决方案仍然是合理的,因为调整如何确定查询条件是微不足道的。例如,您可能首先运行查询以从数据库中获取MAX(imprints),并在确定随机数时使用该值$max

    第二,只是为了演示上面指定的随机数生成的行为,如果使用$max = 10运行10,000次,您将得到类似于此的结果:

    0:  2966
    1:  1965
    2:  1420
    3:  1101
    4:  820
    5:  621
    6:  457
    7:  333
    8:  210
    9:  107
    

    更新2

    回应您的评论以进一步澄清......

    在我的原始更新中,我试图说,如果您只是将最大值硬编码为60,原始代码将永远不会返回{{1 }}。这意味着如果您只是自己使用该代码,它可以正常工作一段时间但最终(假设您每次显示广告时增加imprints >= 60列)所有记录都将被打印60次以上你不会得到任何结果。

    为了避免这样的问题,整个过程将是这样的:

    1. 查询表格一次,以获得最高imprints列值,用于最大随机值范围,如下所示:imprints
    2. 生成随机加权数字时,使用SELECT MAX(imprints) AS max FROM my_table;变量中第一步检索到的最大值。
    3. 通过为您在步骤2中检索的行增加$max列来更新表格。

答案 1 :(得分:1)

function getRandomAd($result) {
    $imprint_weights = array(
        array(
            'min' => 0,
            'max' => 0,
            'weight' => 10
        ),
        array(
            'min' => 1,
            'max' => 5,
            'weight' => 8
        ),
        array(
            'min' => 6,
            'max' => 15,
            'weight' => 6
        ),
        array(
            'min' => 16,
            'max' => 30,
            'weight' => 4
        ),
        array(
            'min' => 31,
            'max' => 60,
            'weight' => 2
        )
    );

    $weighted = array();
    foreach ($result as $row) {
        $imprints = $row['imprints'];
        $imprint_weight = 1;

        foreach ($imprint_weights as $match) {
            if ($imprints >= $match['min'] && $imprints <= $match['max']) {
                $imprint_weight = $match['weight'];
            }
        } 

        $weighted = array_merge($weighted, array_fill(0, $imprint_weight, $row));
    }

    return $weighted[array_rand($weighted)];
}

$data = array();
for ($x = 1; $x <= 100; $x++) {
    $data[] = array(
        'id' => $x,
        'imprints' => rand(0, 65)
    );
}

print_r(getRandomAd($data));

这提供了一种做你想做的事情的另一种方式。这里的好处是,您可以更轻松地更改印记数量对广告权重的影响,还可以轻松添加更多参数(如年龄)以影响广告的选择。