快速生成百万级的概率数据的N种实践方法

前言

本文所讲的 概率数据 如下:

  • 一道选择题,有 100 种选项,选择 A 65 %,选择 B 32% ...选择 X 0.1% ,选择 Y 0.2%...
  • 一道填空题,填 孤岛 32%,填 鼓捣 23% ...
  • 一段视频的弹幕数据,发 辛苦 23% ,发 真棒 3% ,发 厉害 43% ...

实现方式

从数据生成过程入手

大体思路:

  1. 创建一个容量足够体现各个弹幕概率的数组(100个元素可以实现1%的精度,1个元素可以实现0.01%的进度)。
  2. 变量各个弹幕,生成对应概率的数量的元素去填充这个数组。
  3. 将数组打乱。
  4. 每次从这个不变的数组中随机截取一段元素作为弹幕列表。

优点:

  1. 被乱序的数组 + 随机截取一段数组 的概率 与整体弹幕概率一致。
  2. 通过数组的容量体现概率的精度,随机运算次数小于或远小于总的结果的个数。
  3. 随机截取数组可以快速获取单次的弹幕列表。

具体实现:

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

从数据结果集入手

优点:不用关系数据生成算法。

缺点:大数据量(百万级)时处理速度慢,耗时长。

  1. 不用复杂的生成算法过程

依靠 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"

liyiyang
76 声望2 粉丝

引用和评论

0 条评论