Я пытаюсь создать ряд ограниченных перестановок списка элементов. У каждого предмета есть категория, и мне нужно найти комбинации предметов, чтобы у каждой комбинации не было нескольких предметов из одной категории. Чтобы проиллюстрировать, вот некоторые примеры данных:
Name | Category
==========|==========
1. Orange | fruit
2. Apple | fruit
3. GI-Joe | toy
4. VCR | electronics
5. Racquet | sporting goods
Комбинации будут ограничены длиной три, мне не нужны все комбинации каждой длины. Таким образом, набор комбинаций для приведенного выше списка может быть:
(Orange, GI-Joe, VCR)
(Orange, GI-Joe, Racquet)
(Orange, VCR, Racquet)
(Apple, GI-Joe, VCR)
(Apple, GI-Joe, Racquet)
... and so on.
Я делаю это довольно часто, в разных списках. Длина списков никогда не будет превышать 40 пунктов, но понятно, что это может создать тысячи комбинаций (хотя, вероятно, в каждом списке будет около 10 уникальных категорий, что несколько ограничит его)
Я придумал какой-то псевдопифон для того, как бы я реализовал его рекурсивно. Прошло слишком много времени с тех пор, как я взял комбинаторику, но, насколько я помню, это, по сути, подмножество комбинаций набора, что-то вроде C (длина списка, желаемый размер). Вероятно, есть некоторые библиотечные модули, которые могут сделать этот очиститель (или, по крайней мере, более производительным)
Мне было интересно, возможно, был ли лучший подход, чем тот, который у меня есть (возможно, тот, который каким-то образом использует itertools.combinations
):
# For the sake of this problem, let's assume the items are hashable so they
# can be added to a set.
def combinate(items, size=3):
assert size >=2, "You jerk, don't try it."
def _combinate(index, candidate):
if len(candidate) == size:
results.add(candidate)
return
candidate_cats = set(x.category for x in candidate)
for i in range(index, len(items)):
item = items[i]
if item.category not in candidate_cats:
_combinate(i, candidate + (item, ))
results = set()
for i, item in enumerate(items[:(1-size)]):
_combinate(i, (item, ))
return results