Croupier
随机、优先与权重
动机
除了汇编语言这样的另类,常规的编程语言几乎都提供了按平均概率生成整数或者浮点数的标准库。这也是应用开发中非常基本的功能。不过,有时候我们需要一些关于随机性的更复杂的功能。
这种复杂性主要来自两个方面:非平均的随机分布和随机结果的使用方式。
非平均概率
标准库的随机算法,通常都是以一个平均概率的,生成(0,1)之间浮点数的函数,或者以一个生成[0, MAX_INT)区间的整数值的方法为基础,构造相关的算法(Java标准库的Random类型,nextInt 和 nextDouble是分别实现的,互不依赖),这就使得相关的随机数生成总是在已知区间内的平均概率。有时候我们会希望以非平均概率生成随机数。例如我手上有一个推荐系统,在我们从数据集中抽取推荐内容发送到客户端时,并不希望每个内容都有平均的被选择机会,肯定是希望权重更高,更靠前的内容,有更大的几率被选中。
一种常见的方法是计算出每个内容的权重——weight、rank、score或者其它什么名字,整数或者浮点数——然后根据这个权重去选择内容。但其实严格的按权重计算概率是一个相对比较奢侈的做法,很多时候我们未必需要一个严格按照权重分布的随机数,而是仅仅要求一个有序列表中的元素,排在更前面的元素总是有更大的几率被选中——这正是推荐系统常见的需求。
随机数的使用
很奇怪,C++ 的 STL 都提供了 random_shuffle 这样的针对集合的随机算法,但是Java没有,不但Java的标准库没有,scala的标准库,甚至 Apache Commons,都没有从一个容器中随机选择若干元素的方法,Apache Commons 的 RandomUtils 中包含 nextBoolean、nextBytes这样的随机内容构造,但是就是没有从一个有限集合中做随机选择的支持。但是对我的日常工作来说,随机数一个非常重要的用途,就是利用随机行为,生成一个数据列表的子集。有时候我需要一个从固定集合中反复采样,有时候我希望做一个随机分割,还有时候,我还要处理只读和可变的容器。
针对这两个方面的缺失,我回顾了这些年来遇到的各种相关问题,在 Jaskell Core 和 Jaskell Java 8 中各自加入了一组随机工具,用于对 Java 的 List 和 Scala 的 Seq、ListBuffer 做随机选择操作。
实现
随机算法和选择算法分离
[Read More]