【问题标题】:Efficient algorithm or method for shuffling a list with many duplicates?使用许多重复项对列表进行改组的有效算法或方法?
【发布时间】:2023-04-03 21:00:01
【问题描述】:

假设我有 5 个弹珠,3 个是红色的,2 个是蓝色的。 相同颜色的弹珠编号从1n,其中n 是该颜色的弹珠数量。

我需要的是洗牌列表 ['red', 'red', 'red','blue', 'blue'] ,其中单个弹珠的排列无关紧要。

例如 ['red1', 'red2', 'red3','blue', 'blue']['red2', 'red1', 'red3','blue', 'blue'] 只是一种解决方案。

因为我最初的问题有更多的一种颜色的弹珠,与其他的相比,列表的正常洗牌会导致某些订单的偏差很大。

我的一个想法是:

  1. 从一个新的空列表开始,
  2. 生成 2 个随机整数(第一个整数用于选择颜色,第二个整数用于确定选择该颜色的弹珠数量)
  3. 将挑选的弹珠添加到新列表中
  4. 重复直到所有弹珠都用完

我想知道是否有更好的方法来解决这个问题,也许可以保证更公正的结果。

编辑: 一个小规模的场景将是 4 种不同的颜色,每种颜色有 2 到 5 个弹珠。 现实情况是 20 差异。颜色,每种颜色有 5 到 50 个弹珠。

【问题讨论】:

  • 因为这是一个性能问题,有多少可能的值,列表有多大?
  • 颜色不应该改变位置吗?您只想按相同的颜色随机播放?
  • “列表的正常洗牌会导致某些顺序的高偏差。” 你这是什么意思?无论有多少重复项,像 Fisher-Yates (which random.shuffle implements) 这样的标准洗牌算法都会给出无偏见的洗牌。
  • @kaya3 : 如果我想洗牌 ['red', 'red', 'blue] Fisher-Yates 会像 ['red1','red2','blue'] 那样处理它,如果我正确理解了算法。这增加了看起来完全相同的解决方案,但分布将朝着这些结果变化
  • 它以与所有解决方案相同的比例添加“看起来相同”的解决方案。对于您的示例,Fisher-Yates 将以相等的概率生成 (red,red,blue)、(red,blue,red) 和 (blue,red,red)。怎么能少一点偏见呢?

标签: python algorithm list shuffle


【解决方案1】:

如果您想要一个无偏见的随机播放,那么您将您的算法与 Knuth 的(Fisher-Yate 的略微改进版本)进行比较shuffle

如果您打算使用两阶段选择进行优化并希望它保持公平,您需要计算运行一、二、三相同颜色的概率,并比相应的更有效地执行此计算交换数量,然后将源中的副本数量复制到目标列表中。

我不会为性能而烦恼,因为对于 1000 个弹珠,您不太可能通过对交换进行数学运算来提高性能 - 您将不得不在每个阶段进行另一个列表迭代来计算概率,然后是额外的工作,这将使它成为 O(N²) 而不是 O(N)。

【讨论】:

    【解决方案2】:

    这将给出唯一组合的均匀分布:

    from random import randrange
    from collections import Counter
    def marblePick(marbles):
        remaining = list(Counter(marbles).elements())
        return [remaining.pop(randrange(r)) for r in range(len(remaining),0,-1)]
    

    性能似乎是线性的,我通过抽样测试了分布如下:

    marbles = {"R":3,"B":2}
    
    N = 1000000
    dist = Counter()
    for _ in range(N):
        combo = marblePick(marbles)
        dist[tuple(combo)]+=1
    
    for s,c in dist.items():
        print(s,c/N)
    
    import statistics as stats
    prop = [c/N for c in dist.values()]
    mean = stats.mean(prop)
    stdev = stats.stdev(prop)
    print("mean:",mean,"stdDev:",f"{100*stdev/mean:.1f}%",N,"samples",len(dist),"combos")
    

    这个样本(以及我尝试过的其他一些样本)的结果是决定性的:

    ('B', 'R', 'B', 'R', 'R') 0.100245
    ('R', 'R', 'R', 'B', 'B') 0.099789
    ('R', 'R', 'B', 'B', 'R') 0.099952
    ('B', 'R', 'R', 'B', 'R') 0.100579
    ('R', 'R', 'B', 'R', 'B') 0.099732
    ('R', 'B', 'R', 'R', 'B') 0.100033
    ('B', 'B', 'R', 'R', 'R') 0.099861
    ('B', 'R', 'R', 'R', 'B') 0.099941
    ('R', 'B', 'R', 'B', 'R') 0.100103
    ('R', 'B', 'B', 'R', 'R') 0.099765
    mean: 0.1 stdDev: 0.3% 1000000 samples 10 combos
    

    【讨论】:

      猜你喜欢
      • 2023-03-04
      • 2014-06-13
      • 2014-08-01
      • 2020-04-20
      • 1970-01-01
      • 1970-01-01
      • 2019-09-01
      • 1970-01-01
      • 2017-06-18
      相关资源
      最近更新 更多