我需要设计一种基于印记在网站上展示广告的算法。我在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)];
}
答案 0 :(得分:6)
返回所有行并使用PHP进行排序并确定要吐出的行非常低效。这在数据库端比在PHP中更好地处理。
此外,您当前的if语句集与>=
运算符重叠。
最后,我知道你没有这样做,只是一般性说明:你几乎不应该使用ORDER BY RAND()
与MySQL,因为性能很糟糕。这是因为当您使用函数返回值来排序结果时,MySQL无法使用索引进行订单操作。
因此,让我们生成一个随机整数加权到印记谱的下端:
$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次以上你不会得到任何结果。
为了避免这样的问题,整个过程将是这样的:
imprints
列值,用于最大随机值范围,如下所示:imprints
SELECT MAX(imprints) AS max FROM my_table;
变量中第一步检索到的最大值。$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));
这提供了一种做你想做的事情的另一种方式。这里的好处是,您可以更轻松地更改印记数量对广告权重的影响,还可以轻松添加更多参数(如年龄)以影响广告的选择。