首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >产品:我想要不随机的随机

产品:我想要不随机的随机

原创
作者头像
不做虫子
发布2025-10-21 20:20:59
发布2025-10-21 20:20:59
1290
举报
文章被收录于专栏:需求开发实录需求开发实录

背景

有一天,需要做一个推送功能,每天在固定的几个时间段向用户推荐电影。

产品提出:每次要随机给用户推送,但不能重复,不能推送当天已经推荐过的电影。

思考

该怎么实现呢?

在需求评审会上,产品一说完,开发同学们立刻炸锅了:“这工作量太大了”,“要存储的信息太多,这么多用户,要保存每个用户推送过的列表”。

确实,我听到这些讨论,下意识也觉得要保存每个用户的推送列表。一方面,用户量太大,存这些数据不现实,成本太高;另一方面,产品的需求也合理,给用户推送当天已经推荐过的电影确实不合适。

实现

存储成本太高,那有没有不需要存储的方案?

既然要求随机但序列可预测、可复现,这其实就是伪随机。

答案一下子就清晰了。只要用每个用户和日期作为固定的随机种子,生成的随机数就是确定的。这样可以用随机数把推荐列表打乱,然后按批次推送。

Fisher-Yates 洗牌算法(Knuth Shuffle)

这是一种高效且均匀的随机洗牌算法,时间复杂度为 O(n)。具体实现步骤如下:

  1. 设置随机种子:用 val 初始化随机数生成器。
  2. 复制数组:避免修改原始数组。
  3. 从后向前遍历数组
    • 对当前位置 i(从最后一个元素到第二个),生成一个随机索引 j(范围是 [0, i])。
    • 交换 arr[i]arr[j]
  4. 返回洗牌后的数组

关键特性

  • 确定性输出:相同的 val 和输入数组,总是得到相同的排序结果。
  • 随机性:不同的 val 会产生完全不同的排序(比如 val=42val=100 的输出完全独立)。
  • 均匀分布:每个元素出现在任何位置的概率都相等,保证公平随机性。

举个例子

假设你在洗扑克牌,手里有 10 张牌排成一列:

代码语言:txt
复制
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10]

洗牌步骤(从后往前)

  1. 最后一张牌(第10张)
    • 随机选一张牌(可以是1-10中的任意一张)
    • 把这张牌和第10张牌交换
    • 第10张牌就固定了(不再移动)
  2. 倒数第二张牌(第9张)
    • 在剩下的9张牌(1-9)中随机选一张
    • 把这张牌和第9张牌交换
    • 第9张牌也固定了
  3. 继续往前
    • 对第8张牌,在剩下8张中随机选…
    • 对第7张牌,在剩下7张中随机选…
    • …直到第一张牌

只要随机种子一样,生成的随机数顺序就一样,洗牌结果也会一样。

代码语言:txt
复制
注:golang中rand.Shuffle就是用的该算法,直接调用就可以了(注意使用固定种子)。

完美~

一般般吧这图片
一般般吧这图片

总结

其实记录用户状态不一定非要存下来,有时候利用一些数学特性也能很好地达成期望。另外,虽然我们开发时常常排斥伪随机(比如每次都用当前纳秒作为随机种子),但其实伪随机有时候也挺有用的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 思考
  • 实现
    • Fisher-Yates 洗牌算法(Knuth Shuffle)
    • 关键特性
    • 洗牌步骤(从后往前)
    • 只要随机种子一样,生成的随机数顺序就一样,洗牌结果也会一样。
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档