Выбирайте подмножество из списка случайным образом и поддерживайте одинаковое количество пиков в Python

Учитывая список строк вроде этого (на самом деле у меня есть гораздо более длинный список, но я буду сокращать его здесь):

items=['fish','headphones','wineglass','bowtie','cheese','hammer','socks']

Я хотел бы выбрать подмножество, скажем, 3, из этого списка случайным образом, чтобы элементы могли быть выбраны только один раз. Это достаточно просто, используя следующее:

import itertools
import random
def random_combination(iterable, r):
    "Random selection from itertools.combinations(iterable, r)"
    pool = tuple(iterable)
    n = len(pool)
    indices = sorted(random.sample(xrange(n), r))
    return tuple(pool[i] for i in indices)

items=['fish','headphones','wineglass','bowtie','cheese','hammer','socks']
randomPick=random_combination(items,3)

Далее, чтобы быть болью, я не хочу делать это только один раз, но несколько раз скажу 10 раз. Конечным продуктом будет 10 списков элементов, выбранных случайным образом только один раз, с ограничением на то, чтобы по этим 10 элементам списков одинаковое количество раз отображалось в списках. Я бы хотел, чтобы "носки" не поднимались 10 раз, а "молоток", например, только один раз.

Это тот шаг, на котором я застрял, я просто не знаю достаточно программирования или недостаточно о доступных функциях в python для выполнения такой вещи.

Кто-нибудь может помочь?

3 ответа

Решение

Хорошо, в конце концов, я прибег к следующему. Это более ограниченная реализация, в которой я устанавливаю, сколько раз я хочу, чтобы элемент повторялся, например, из 10 списков, которые я хочу, чтобы каждый элемент выбирался 5 раз:

List = ['airplane',
            'fish',
            'watch',
            'balloon',
            'headphones',
            'wineglass',
            'bowtie',
            'guitar',
            'desk',
            'bottle',
            'glove'] #there is more in my final list but keeping it short here
numIters = 5
numItems = len(List)
finalList=[]
for curList in range(numIters):
    random.shuffle(List)
    finalList.append(List[0 : numItems/2]) #append first list
    finalList.append(List[numItems/2 : -1]) #append second list

return finalList

Следующий код может помочь. Случайный элемент всплывает до (копия) iterable пуст, затем начинается заново из всего списка. Недостатком является то, что каждый предмет выбирается один раз, прежде чем один предмет может быть выбран во второй раз. Однако, как вы можете видеть из результатов, распределение предметов заканчивается примерно равным.

import random

def equal_distribution_combinations(iterable, n, csize):
    """
    Yield 'n' lists of size 'csize' containing distinct random elements
    from 'iterable.' Elements of 'iterable' are approximately evenly
    distributed across all yielded combinations.
    """
    i_copy = list(iterable)

    if csize > len(i_copy):
        raise ValueError(
            "csize cannot exceed len(iterable), as elements could not distinct."
        )

    for i in range(n):
        comb = []
        for j in range(csize):
            if not i_copy:
                i_copy = list(iterable)

            randi = random.randint(0, len(i_copy) - 1)

            # If i_coppy was reinstantiated it would be possible to have
            # duplicate elements in comb without this check.
            while i_copy[randi] in comb:
                randi = random.randint(0, len(i_copy) - 1)
            comb.append(i_copy.pop(randi))

        yield comb

редактировать

Извинения за Python 3. Единственное изменение в функции для Python 2 должно быть range -> xrange,

Изменить 2 (отвечая на вопрос комментария)

equal_distribution_combinations должно привести к равномерному распределению для любого n, csizeи длина iterable, пока csize не превышает len(iterable) (поскольку элементы комбинации не могут быть различимы).

Вот тест с использованием конкретных цифр в вашем комментарии:

items = range(30)
item_counts = {k: 0 for k in items}

for comb in equal_distribution_combinations(items, 10, 10):
    print(comb)
    for e in comb:
        item_counts[e] += 1

print('')
for k, v in item_counts.items():
    print('Item: {0}  Count: {1}'.format(k, v))

Выход:

[19, 28, 3, 20, 2, 9, 0, 25, 27, 12]
[29, 5, 22, 10, 1, 8, 17, 21, 14, 4]
[16, 13, 26, 6, 23, 11, 15, 18, 7, 24]
[26, 14, 18, 20, 16, 0, 1, 11, 10, 2]
[27, 21, 28, 24, 25, 12, 13, 19, 22, 6]
[23, 3, 8, 4, 15, 5, 29, 9, 7, 17]
[11, 1, 8, 28, 3, 13, 7, 26, 16, 23]
[9, 29, 14, 15, 17, 21, 18, 24, 12, 10]
[19, 20, 0, 2, 25, 5, 22, 4, 27, 6]
[12, 13, 24, 28, 6, 7, 26, 17, 25, 23]

Item: 0  Count: 3
Item: 1  Count: 3
Item: 2  Count: 3
Item: 3  Count: 3
Item: 4  Count: 3
Item: 5  Count: 3
Item: 6  Count: 4
Item: 7  Count: 4
Item: 8  Count: 3
Item: 9  Count: 3
Item: 10  Count: 3
Item: 11  Count: 3
Item: 12  Count: 4
Item: 13  Count: 4
Item: 14  Count: 3
Item: 15  Count: 3
Item: 16  Count: 3
Item: 17  Count: 4
Item: 18  Count: 3
Item: 19  Count: 3
Item: 20  Count: 3
Item: 21  Count: 3
Item: 22  Count: 3
Item: 23  Count: 4
Item: 24  Count: 4
Item: 25  Count: 4
Item: 26  Count: 4
Item: 27  Count: 3
Item: 28  Count: 4
Item: 29  Count: 3

Как видно, предметы распределяются равномерно.

Я бы сделал что-то вроде этого:

items = set(items)
res = []
for _ in xrange(10):
    r = random.sample(items, 3)
    res.append(r)
    items -= set(r)

все, что нужно сделать - это взять 3 элемента, сохранить их, а затем вычесть их из исходного списка, чтобы их нельзя было выбрать снова.

Другие вопросы по тегам