如何 return 考虑互斥项的随机列表?

How to return a shuffled list considering mutually exclusive items?

假设我有一个选项列表,我想随机选择一个数字。

在我的例子中,假设选项在列表 ['a', 'b', 'c', 'd', 'e'] 中,我希望我的脚本包含 return 3 个元素。

不过也有两个选项不能同时出现的情况。也就是说,如果选项 'a' 是随机选择的,则选项 'b' 不能被选择。反之亦然。

所以有效的输出是:['a', 'c', 'd']['c', 'd', 'b'],而像 ['a', 'b', 'c'] 这样的东西不会,因为它们同时包含 'a' 和 'b'.

为了满足这些要求,我正在获取 3 个选项和另一个选项以补偿可能的丢弃。然后,我保留一个具有互斥条件的 set() 并继续从中删除并检查是否已选择两个元素:

import random


mutually_exclusive = set({'a', 'b'})
options = ['a', 'b', 'c', 'd', 'e']
num_options_to_return = 3

shuffled_options = random.sample(options, num_options_to_return + 1)

elements_returned = 0
for item in shuffled_options:
    if elements_returned >= num_options_to_return:
        break

    if item in mutually_exclusive:
        mutually_exclusive.remove(item)
        if not mutually_exclusive:
            # if both elements have appeared, then the set is empty so we cannot return the current value
            continue

    print(item)
    elements_returned += 1

但是,我可能编码过多,Python 可能有更好的方法来处理这些要求。通过 random's documentation 我无法找到开箱即用的方法。有没有比我现在的解决方案更好的解决方案?

一种方法是使用 itertools.combinations to create all of the possible results, filter out the invalid ones and make a random.choice 来自于:

>>> from itertools import combinations
>>> from random import choice
>>> def is_valid(t):
...     return 'a' not in t or 'b' not in t
... 
>>> choice([
...     t 
...     for t in combinations('abcde', 3) 
...     if is_valid(t)
... ])
... 
('c', 'd', 'e')

可能有点幼稚,但您可以生成样本直到满足您的条件:

import random

options = ['a', 'b', 'c', 'd', 'e']
num_options_to_return = 3
mutually_exclusive = set({'a', 'b'})

while True:
    shuffled_options = random.sample(options, num_options_to_return)
    if all (item not in mutually_exclusive for item in shuffled_options):
        break

print(shuffled_options)

我会用集合来实现它:

import random

mutually_exclusive = {'a', 'b'}
options = ['a', 'b', 'c', 'd', 'e']
num_options_to_return = 3

while True:
    s = random.sample(options, num_options_to_return)
    print('Sample is', s)
    if not mutually_exclusive.issubset(s):
        break
    print('Discard!')

print('Final sample:', s)

打印(例如):

Sample is ['a', 'b', 'd']
Discard!
Sample is ['b', 'a', 'd']
Discard!
Sample is ['e', 'a', 'c']
Final sample: ['e', 'a', 'c']

您可以重组您的选择。

import random

options = [('a', 'b'), 'c', 'd', 'e']
n_options = 3

selected_option = random.sample(options, n_options)

result = [item if not isinstance(item, tuple) else random.choice(item)
          for item in selected_option]

print(result)

我创建了以下函数,我认为它也值得分享 ;-)

def random_picker(options, n, mutually_exclusives=None):
    if mutually_exclusives is None:
        return random.sample(options, n)
    elif any(len(pair) != 2 for pair in mutually_exclusives):
        raise ValueError('Lenght of pairs of mutually_exclusives iterable, must be 2')

    res = []
    while len(res) < n:
        item_index = random.randint(0, len(options) - 1)
        item = options[item_index]
        if any(item == exc and pair[-(i - 1)] in res for pair in mutually_exclusives
               for i, exc in enumerate(pair)):
            continue
        res.append(options.pop(item_index))
    return res

其中:

  • options 是可供选择的可用选项列表。
  • n 是您要从 options
  • 中挑选的项目数
  • mutually_exclusives 是一个包含互斥项
  • 的元组对的可迭代对象

您可以按如下方式使用:

>>> random_picker(['a', 'b', 'c', 'd', 'e'], 3)
['c', 'e', 'a']
>>> random_picker(['a', 'b', 'c', 'd', 'e'], 3, [('a', 'b')])
['d', 'b', 'e']
>>> random_picker(['a', 'b', 'c', 'd', 'e'], 3, [('a', 'b'), ('a', 'c')])
['e', 'd', 'a']
import random

l = [['a','b'], ['c'], ['d'], ['e']]
x = [random.choice(i) for i in random.sample(l,3)]

这里l是一个二维列表,第一层反映and关系,第二层反映or关系。