一种基于历史记录的随机方法


问题

假设你有一个歌单,这个时候你想随机播放,有几种情况:

  • 纯随机(伪随机也算是纯随机),没什么好说的,随机从歌单中 抽取一首歌,抽到什么看运气
  • 听的越少的越容易被抽到,用来捡起好久没听过的老歌,当然 大概率会随机到自己不喜欢听的歌
  • 听的越多的越容易被抽到,这个看起来更符合大家想要的,但 也有个问题 --- 假如一直这样下去,就会使人不断陷入 “信息茧房”,你越来越容易抽到最常刷到的歌,而非常难以抽到 不常听的。

前两种没什么好说的,这里思考一种简单的方式来解决第三种方式 遇到的问题

思考

假设第三种采用纯权重的方式,会发生什么呢?写个代码模拟一下。

import random

for i in range(10):
    play_times = [0, 10, 100, 1000]
    for _ in range(1000000):
        weight = play_times.copy()
        result = random.randint(0, sum(weight))
        count = 0
        for i, w in enumerate(weight):
            count += w
            if count > result:
                play_times[i] += 1
                break
    print(play_times)
[0, 8983, 89069, 903052]
[0, 8943, 81734, 910425]
[0, 7153, 89084, 904865]
[0, 4452, 86638, 910013]
[0, 7255, 95967, 897882]
[0, 16491, 93841, 890771]
[0, 8379, 98241, 894484]
[0, 5325, 81868, 913912]
[0, 10329, 79549, 911226]
[0, 10251, 99724, 891131]

可以看到,结果有以下几个特点:

  • 之前没有听过的歌始终不会被随机到
  • 之前被听过次数较多的(1000)结果较为稳定, 始终占比90%左右
  • 之前被听过次数较少的(10, 100)结果不稳定, 非常依赖前期的随机结果,一旦前期随机的较少而 其他结果开始膨胀,那么被听到的几率就很小了, 极端结果下占比较初始值少了一半,并且在 这种情况下后续不可能再追上。

如何让之前没有听过的歌有机会被随机到,同时 又能大体保证用户能更多的听到自己喜欢的呢?

解决方案

我们考虑不直接按历史记录随机,而是额外添加一个基本权重, 使得每次随机有一个保底值。

在上述方案中,我们初始的权重为[0, 10, 100, 1000], 假如我们给每一项添加一个10的权重,则权重变为 [10, 20, 110, 1010],这样至少可以保证之前没有随机 到的有机会被随机到了。执行结果如下:

[5023, 16501, 99562, 880016]
[13997, 13870, 107123, 866113]
[17568, 11615, 85723, 886199]
[8984, 12350, 106411, 873360]
[11594, 23554, 89698, 876258]
[5737, 15433, 101636, 878298]
[6490, 19511, 109977, 865128]
[10412, 19872, 82912, 887905]
[8019, 17631, 90793, 884658]
[7313, 16961, 94724, 882103]

但这样还不够,可见初始的随机波动仍然对权重有极大的影响, 并逐渐被放大。因此我们考虑动态的计算权重添加量,即:

dynamic_weight = int((max(play_times) - min(play_times)) * k)

这里的k可以根据需求设定,如果需要尽量保持原有的 权重则取小一些,如果需要尽量平均则取大一些。

k=0.01

[52906, 53723, 116710, 777765]
[40115, 51127, 129496, 780363]
[45553, 52948, 132145, 770457]
[44583, 54615, 121849, 780059]
[44592, 53750, 126763, 775995]
[40380, 56122, 136035, 768570]
[55551, 59107, 120198, 766251]
[41986, 56456, 125493, 777172]
[50375, 60915, 115697, 774118]
[51369, 56787, 103346, 789601]

k=0.1

[163529, 173157, 200192, 464225]
[166939, 168398, 203092, 462675]
[170092, 164942, 197692, 468383]
[173744, 168951, 203813, 454594]
[173036, 170285, 193619, 464167]
[164788, 167594, 203445, 465279]
[172007, 168909, 198399, 461788]
[172513, 166419, 197737, 464433]
[164749, 173456, 199186, 463713]
[167466, 173331, 199084, 461221]

k=1

[237497, 234838, 240152, 288621]
[232571, 234475, 242402, 291660]
[232829, 237790, 241102, 289387]
[239947, 232021, 240269, 288868]
[233805, 236803, 238316, 292183]
[236139, 233593, 239468, 291909]
[229719, 237511, 244440, 289432]
[236464, 231165, 242745, 290732]
[237024, 234734, 240415, 288935]
[237067, 234127, 238666, 291248]