在重复迭代期间更有效的洗牌

Posted

技术标签:

【中文标题】在重复迭代期间更有效的洗牌【英文标题】:More efficient shuffling during repeated iterations 【发布时间】:2017-07-31 03:32:03 【问题描述】:

我需要对列表进行任意次数的迭代,yield以随机顺序排列列表中的每个元素(每次遍历完整列表时使用不同的顺序)。我需要在第二次产生该元素之前产生每个元素一次,在第三次产生该元素之前产生每个元素两次,等等。

目前,我的代码如下所示:

def random_yield(data):
  random.shuffle(data)
  data_index = 0
  while True:
    yield data[data_index]
    data_index += 1

    if data_index == len(data):
      random.shuffle(data)
      data_index = 0

有没有办法更有效地做到这一点,所以我不会在每个 len(data) yields 之后支付 random.shuffle() 的性能损失?

【问题讨论】:

“我需要在第二次产生该元素之前产生每个元素一次......”什么?如果你第一次做某事,第二次你做了两次,只是为了记录。 我在列表中循环了任意次数。因此,如果列表是 [1,2,3],那么我可以产生 1, 2, 3, 2, 3, 1, 3, 2 但不能产生 1, 1, 1, 3, 2... 不幸的是,这禁止了简单的解决方案,即随机选择一个索引并产生 data[random_idx] 这是一个旁白,但您的生成器实现可以大大简化(无需跟踪data_index 随机洗牌恰好涉及“获取一个随机数并交换两个元素”的n 迭代,并且您每隔n 收益率执行一次,因此平均每个收益率发生一次。您的“简单解决方案”涉及每次产量获得一个随机数。为什么你觉得其中一种效率很高,而另一种却非常慢? this help? 【参考方案1】:

您可以在每次迭代中执行 Fisher-Yates 洗牌,从而在每次迭代中平均分配成本。这并没有更高效——事实上,它可能效率更低,因为库函数可能比 Python 代码更快——但它避免了长时间的停顿。

该代码与每次仅抓取一个随机元素没有太大区别。唯一的区别是你从向量的一个子集中抓取随机元素:

from random import randrange
def random_yield(data):
  index = 0
  limit = len(data)
  while True:
    if index + 1 >= limit:
      yield data[index]
      index = 0
    else:
      # Get a random element which we haven't yet used this cycle
      # (This is a single iteration of the F-Y shuffle algorithm)
      j = randrange(index, limit)
      rv = data[j]
      yield rv
      # Swap the element we just selected so its not in the next subrange
      data[j] = data[index]
      data[index] = rv
      index += 1

【讨论】:

同样的想法,但没有最后一个元素的特殊情况:gist.github.com/paulhankin/30ffd466055c18faa957b316b3d3bce9。我认为它更容易理解。 让我换一种说法。我发现没有模更容易理解,因为对我来说很明显它正在做的是从尚未选择的元素集中选择一个随机元素。对我来说,面对模运算符,我不得不推导出我刚才所说的;它不再是明显的(即可见,这是证据的根源)。但这只是我。我可能非常不具代表性,但我仍然根据自己的直觉进行编码。幸运的是,这是一个广阔的世界,我们都在合唱团中占有一席之地。

以上是关于在重复迭代期间更有效的洗牌的主要内容,如果未能解决你的问题,请参考以下文章

随机洗牌列表[重复]

如何在洗牌的连续整数数组中找到重复元素?

Pandas - 迭代中的重复行

广播数据帧没有删除洗牌阶段(几行数据)[重复]

如何在 C# 中使用 Random 类对数组进行洗牌 [重复]

如果发生错误,如何在Matlab中重复循环迭代