【问题标题】:How to randomize a list to meet conditions如何随机化列表以满足条件
【发布时间】:2020-03-02 04:33:32
【问题描述】:

我想生成一个列表的 n 个随机版本,这样每次随机化的顺序都与之前的不同,并且每个元素的位置必须与之前的列表不同。我已经生成了列表的所有可能排列的列表,但我被困在如何选择符合我的条件的子列表上。我在想也许列表理解可以工作,但不知道如何完成它。

# constraints: n <= 12

lst = ['John', 'William', 'Michael', 'Victor', 'Tom', 'Charley', 'Patrick', 'David']

permutations = list(itertools.permutations(lst))

randomized_lists = [i for i in permutations if <conditions>]

任何想法如何做到这一点?另外,有没有更好(更有效)的方法来解决这个问题?

【问题讨论】:

  • 你的条件是什么?
  • @mario_sunny 他们在问题中说。
  • @mario_sunny 每个元素也必须有不同的位置
  • the sub-lists that match my conditions. - 哪些条件?也许我不明白这个问题。
  • @mario_sunny。 OP 希望以这样一种方式对列表进行洗牌,以使所有元素都不会在其原始位置结束。

标签: python list random


【解决方案1】:

只有你的第二个条件是必要的:如果所有元素都不在原来的位置,那么顺序就会不同。您可以根据自己的规范生成一个列表,而不是查看所有可能的排列并随机选择一个(这比洗牌效率更低,直到满足您的条件)。

您的列表有 8 个元素,这意味着每个位置可以有 7 个选项之一(除了当前存在的元素)。这相当于将 1 放在一个由 0 组成的矩阵中,这样任何一个都不会占据与另一个相同的行或列(正常洗牌),但有元素不能位于主对角线上的限制,这会使事情变得复杂。

您可以实现包含限制的Fisher-Yates-like shuffle。这将使您的列表随机化,而无需筛选大量排列。

使用索引可能更容易避免放置冲突,而不是在每个元素被交换时查找它的位置。

n = len(lst)
ind = list(range(n))
for i in range(n - 1):
    # Case 1: Swap out necessary, conflictonly with self
    if ind[i] == i:
        swap_ind = random.randrange(i + 1, n)
    else:
        try:
            # Case 2: swap might cause conflict
            ind_of_i = ind.index(i, i + 1)
            swap_ind = random.randrange(i, n - 1)
            if swap_ind >= ind_of_i:
                swap_ind += 1
        except ValueError:
            # Case 3: no conflict, full range available
            swap_ind = random.randrange(i, n)
    ind[i], ind[swap_ind] = ind[swap_ind], ind[i]

你现在有一个洗牌的索引列表,没有一个在它开始的地方结束。你可以安排输入列表

result = [lst[i] for i in ind]

请记住,检查ind[swap_ind] 的实施在时间上效率非常低,并且有效地否定了排序索引而不是实际值的好处。您可以将其替换为第二个列表,该列表维护索引到位置的反向查找表,并将其与ind 交换。结果将是O(1) 查找和两倍的内存利用率。

我不确定结果在所有可用排列的空间上有多均匀,但我怀疑 (a) 它可能足以满足您的需求,并且 (b) 可以轻松修改为如有必要/可能,更统一。

【讨论】:

  • 我重新措辞了这个问题并在这里得到了解决方案:stackoverflow.com/questions/58773283/…
  • @Clifton。 reword 在功能上与此问题相同。另一个问题的答案建议随机改组并清除不合格的元素。陷入这种方法的概率低到可以忽略不计,但仍然没有效率。您应该在此处选择其中一个答案。
【解决方案2】:

这可以通过修改Fisher–Yates shuffle 算法来避免与自身交换一项。也就是说,对于 k 处的每个项目(其中 k 从 0 开始),而不是在 [0, k] 或 [k, n - 1] (包括 k)中选择一个随机项目,在[0, k)或(k, n - 1](不包括k)中选择一个随机项目,并将k处的项目与随机项目交换。


下面的方法实现了这个想法:

import random

def shuffle_diff_pos(list):
  """ Returns a shuffled list in which 
      each item moves to a different position. """
  list=[x for x in list]
  if len(list)>=2:
    i=len(list)-1
    while i>0:
      k=random.randint(0, i-1)
      tmp=list[i];list[i]=list[k];list[k]=tmp
      i-=1
  return list

lst = ['John', 'William', 'Michael', 'Victor', 'Tom', 'Charley', 'Patrick', 'David']
randomized_lists = [shuffle_diff_pos(lst) for _ in range(12)]

【讨论】:

  • 我一直在研究具有相同内容和实现的答案。我认为结果不会是 100% 统一的,无法给出令人满意的解释或修复。 +1
  • @Peter 这似乎是一个有趣的答案。你能放一个示例代码吗?
  • 我不确定您的代码是否涵盖了交换元素时的三种可能性。我已经发布了一个基本上详细说明您最初想法的答案。
【解决方案3】:

您可以使用此列表推导:

result = [i for i in permutations if any(a != b for a, b in zip(i, lst))]

我们正在any(a == b for a, b in zip(i, lst) 测试lst 中相同索引上是否有任何匹配项,并在此行中进行排列。

【讨论】:

  • any(a == b in zip(item, lst))?
  • 您可以将result 设为生成器,这样您就不必一次构建整个东西。
  • 请随时编辑我的答案。我不太明白,因为 a 和 b 是什么意思。
  • a 和 b 是脑残。我的意思是any(a == b for a, b in zip(item, lst))。我不想碰你的代码。
  • 您根本不需要for 循环。在这种情况下,您可以使用列表推导。
猜你喜欢
  • 2022-12-04
  • 1970-01-01
  • 2017-07-22
  • 2017-04-29
  • 2017-06-23
  • 2014-08-04
  • 1970-01-01
  • 1970-01-01
  • 2020-03-05
相关资源
最近更新 更多