快速生成百万级的概率数据的N种实践方法
前言
本文所讲的 概率数据
如下:
- 一道选择题,有 100 种选项,选择
A
65 %,选择B
32% ...选择X
0.1% ,选择Y
0.2%... - 一道填空题,填
孤岛
32%,填鼓捣
23% ... - 一段视频的弹幕数据,发
辛苦
23% ,发真棒
3% ,发厉害
43% ...
实现方式
从数据生成过程入手
大体思路:
- 创建一个容量足够体现各个弹幕概率的数组(100个元素可以实现1%的精度,1个元素可以实现0.01%的进度)。
- 变量各个弹幕,生成对应概率的数量的元素去填充这个数组。
- 将数组打乱。
- 每次从这个不变的数组中随机截取一段元素作为弹幕列表。
优点:
- 被乱序的数组 + 随机截取一段数组 的概率 与整体弹幕概率一致。
- 通过数组的容量体现概率的精度,随机运算次数小于或远小于总的结果的个数。
- 随机截取数组可以快速获取单次的弹幕列表。
具体实现:
class RandBullet {
private static $bulletList = [];
private static $bulletTotal = 0;
/**
* 生成随机弹幕库
* array $wordList 弹幕概率列表 [["content" => "word", "rate" => 0.02]]
* int $arrayNum 数组元素个数
*/
public static function generateBulletList($wordList, $arrayNum = 10000)
{
//生成 1w 个弹幕池数据
$arr = [];
foreach ($wordList as $v){
$content = str_replace(['\\','\''], ['\\\\',"\\'"], $v['content']); //MySQL 关键字过滤
$num = ceil($v['rate'] * $arrayNum);
$arr = array_merge($arr, array_fill(0, $num, $content));
}
// 乱序
shuffle($arr);
self::$bulletList = $arr;
self::$bulletTotal = count($arr);
}
/**
* 获取弹幕列表(随机切)
* int $num 弹幕个数
*/
public static function getRandBulletList($num)
{
$start = mt_rand(0, self::$bulletTotal - $num);
return array_slice(self::$bulletList, $start, $num);
}
/**
* 获取弹幕列表(顺序读)
* int $num 弹幕个数
*/
protected static $bulletIndex = 0;
public static function getRandBulletList($num)
{
$remNum = self::$bulletTotal - self::$bulletIndex;
$sliceParams = [];
if ($remNum >= $num) {
$sliceParams[] = [self::$bulletIndex, $num];
self::$bulletIndex += $num;
} else {
$rewIndex = $num - $remNum;
$sliceParams[] = [self::$bulletIndex, $remNum];
$sliceParams[] = [0, $rewIndex];
self::$bulletIndex = $rewIndex;
}
$list = [];
foreach ($sliceParams as $param) {
$list = array_merge($list, array_slice(self::$bulletList, $param[0], $param[1]));
}
return $list;
}
}
从数据结果集入手
优点:不用关系数据生成算法。
缺点:大数据量(百万级)时处理速度慢,耗时长。
- 不用复杂的生成算法过程
依靠 mysql rand()
/**
* 生成随机弹幕库
* array $wordList 弹幕概率列表 [["content" => "word", "rate" => 0.02]]
*/
public static function generateBulletList($wordList)
{
//查询总个数
$total = mysqlQueryTotal("select count(1) as total from table where content = ''")
foreach ($wordList as $v){
$content = str_replace(['\\','\''], ['\\\\',"\\'"], $v['content']); //MySQL 关键字过滤
$num = ceil($v['rate'] * $total);
if ($num) {
mysqlExec("update table set content = '{$content}',is_done = 1 where is_done = 0 order by rand() limit {$num}")
}
}
}
依靠 id值 + switch 实现
update table set content = case
when id % 100 < 50 then "value_1" -- 50%
when id % 100 < 80 then "value_2" -- 30%
when id % 100 < 90 then "value_3" -- 10%
when id % 100 < 95 then "value_4" -- 5%
when id % 100 < 97 then "value_5" -- 2%
when id % 100 < 98 then "value_6" -- 1%
when id % 100 < 99 then "value_7" -- 1%
else "value_8"
end
when content = '';
如何快速处理 自增id 与 创建的时间问题
方式一:insert into select
适用于少量数据(万级以上)
insert into table (field1,field2,crate_time) select field1,field2,crate_time from tmp_table order by create_time asc;
crate_time 字段要加上索引
方式二:load data infile
适用于大批量(百万级以上)
#!/bin/bash
sourcedb='mysql -hxxxxxx.mysql.rds.aliyuncs.com -uxxxxxxxxx -pxxxxxx -P3306 -Dxxxxxx --default-character-set utf8mb4'
sql="select field_1,field_2,create_time from tmp_table order by create_time;"
$sourcedb -N -e "$sql" > tmp.txt
sql="load data local infile 'tmp.txt' into table last_table(field_1,field_2,create_time); "
$sourcedb -N -e "$sql"
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。