Python - 检查排列是否存在/组合是否唯一

3
我有以下代码,它可以创建水果、蔬菜和饮料的组合,并保持在特定价格范围内:
fruits = [('apple', 1), ('banana', 2), ('orange', 3)]
veggies = [('tomato', 1), ('cucumber', 2), ('onion', 3)]
drinks = [('water', 1), ('juice', 2), ('soda', 3)]

for fruit1 in fruits:
    for fruit2 in fruits:
        for veggie1 in veggies:
            for veggie2 in veggies:
                for drink1 in drinks:
                    for drink2 in drinks:
                        if 12 <= fruit1[1]+fruit2[1]+veggie1[1]+veggie2[1]+drink1[1]+drink2[1] <= 14: 
                            if fruit1 != fruit2: 
                                if veggie1 != veggie2: 
                                    if drink1 != drink2:
                                        combos.append((fruit1[0], fruit2[0], veggie1[0], veggie2[0], drink1[0], drink2[0], fruit1[1]+fruit2[1]+veggie1[1]+veggie2[1]+drink1[1]+drink2[1])) 

它会检查以确保不会选择相同的水果/蔬菜/饮料两次,但我需要确保组合是独一无二的。

例如,如果最后得到了

('apple', 'banana', 'tomato', 'cucumber', 'water', 'soda', 10) 

我不想要

('banana', 'apple', 'tomato', 'cucumber', 'water', 'soda', 10)

等等等。这比我想象的更麻烦,所以任何帮助都将不胜感激。


所有类别中至少应使用两个? - thefourtheye
如果 12 <= fruit1[1]+fruit2[1]+veggie1[1]+veggie2[1]+drink1[1]+drink2[1] <= 14: ... 语法无效! - isedev
3
@isedev,这是一种有效的语法。a <= b <= c 相当于 a <= b and b <= c。(连锁比较运算符) - falsetru
哇,这可真是新鲜事啊,至少我今天学到了一些新东西。 - isedev
确实是“神圣的嵌套”。我认为最好使用专门为此设计的模块,即:itertools,以避免所有这些嵌套。 - Russia Must Remove Putin
我将使用itertools,谢谢大家。 - mcnollster
3个回答

3
您可以像这样处理数据。
prices = {k:v for items in [fruits, veggies, drinks] for k, v in items}
fru,veg,dri=[i[0] for i in fruits],[i[0] for i in veggies],[i[0] for i in drinks]

from itertools import combinations, product, chain
for items in product(*(combinations(i, r = 2) for i in (fru, veg, dri))):
    total = sum(prices[i] for item in items for i in item)
    if 12 <= total <= 14:
        print tuple(chain.from_iterable(items)) + (total,)

输出

('apple', 'banana', 'tomato', 'onion', 'juice', 'soda', 12)
('apple', 'banana', 'cucumber', 'onion', 'water', 'soda', 12)
('apple', 'banana', 'cucumber', 'onion', 'juice', 'soda', 13)
('apple', 'orange', 'tomato', 'cucumber', 'juice', 'soda', 12)
('apple', 'orange', 'tomato', 'onion', 'water', 'soda', 12)
('apple', 'orange', 'tomato', 'onion', 'juice', 'soda', 13)
('apple', 'orange', 'cucumber', 'onion', 'water', 'juice', 12)
('apple', 'orange', 'cucumber', 'onion', 'water', 'soda', 13)
('apple', 'orange', 'cucumber', 'onion', 'juice', 'soda', 14)
('banana', 'orange', 'tomato', 'cucumber', 'water', 'soda', 12)
('banana', 'orange', 'tomato', 'cucumber', 'juice', 'soda', 13)
('banana', 'orange', 'tomato', 'onion', 'water', 'juice', 12)
('banana', 'orange', 'tomato', 'onion', 'water', 'soda', 13)
('banana', 'orange', 'tomato', 'onion', 'juice', 'soda', 14)
('banana', 'orange', 'cucumber', 'onion', 'water', 'juice', 13)
('banana', 'orange', 'cucumber', 'onion', 'water', 'soda', 14)

如果您只想选择drinks中的一个元素,则可以将组合部分更改为以下内容。
d = {0: 2, 1: 2, 2: 1}
for items in product(*(combinations(j, r=d.get(i)) for i, j in enumerate((fru,veg,dri)))):

通过这个变化,输出结果变为:

('apple', 'orange', 'cucumber', 'onion', 'soda', 12)
('banana', 'orange', 'tomato', 'onion', 'soda', 12)
('banana', 'orange', 'cucumber', 'onion', 'juice', 12)
('banana', 'orange', 'cucumber', 'onion', 'soda', 13)

这看起来像是可以工作的,但我不熟悉itertools,所以我需要进行研究。你能解释一下第一部分给我吗 -> prices = {k:v for items in [fruits, veggies, drinks] for k, v in items}如果我只想要一个饮料项目,我需要改变什么? - mcnollster
{..} 中的那部分被称为字典推导式,它将每个项目的价格存储在其名称下。您可以尝试 print prices - thefourtheye
谢谢。如果我想要两种水果和两种蔬菜,但只有一种饮料怎么办?有没有简单的方法可以做到这一点?我看到你有组合(i, r = 2),它告诉它每个(fru、veg、dri)中有两个项目。最后,为什么在+ (total,) 结尾处有逗号。感谢您的帮助。 - mcnollster

1

创建后消除冗余集合:

您可以创建一组不可变集合(frozensets),这将消除冗余的集合:

>>> tup_1 = ('apple', 'banana', 'tomato', 'cucumber', 'water', 'soda', 10)
>>> tup_2 = ('banana', 'apple', 'tomato', 'cucumber', 'water', 'soda', 10)
>>> fs_1 = frozenset(tup_1)
>>> fs_2 = frozenset(tup_2)
>>> set([fs_1, fs_2])
set([frozenset(['tomato', 'apple', 10, 'water', 'cucumber', 'soda', 'banana'])])

由于集合中的元素必须是可哈希的,因此每个项目都需要一个frozenset。请注意,使用此方法会丢失排序。

确保没有冗余项:

另外,itertools.combinations将确保您不会得到冗余的项:

import itertools
fruits = [('apple', 1), ('banana', 2), ('orange', 3)]
veggies = [('tomato', 1), ('cucumber', 2), ('onion', 3)]
drinks = [('water', 1), ('juice', 2), ('soda', 3)]
combs=(itertools.combinations(fruits, 2), 
       itertools.combinations(veggies, 2), 
       itertools.combinations(drinks, 2))
multi_combs = [list(itertools.chain.from_iterable(i)) 
                    for i in itertools.product(*combs)]

print(len(multi_combs))

打印

27

然后您可以进行过滤:
filtered_combs = [i for i in multi_combs 
                      if 12 <= sum(p for _, p in  i) <= 14]

len(filtered_combs)

16

结果:

为了美化打印项目和总数:

printable = [list(itertools.chain((j for j, _ in i), 
                                  [sum(p for _, p in  i),])) 
                                      for i in filtered_combs]

pprint.pprint(printable)

打印

[['apple', 'banana', 'tomato', 'onion', 'juice', 'soda', 12],
 ['apple', 'banana', 'cucumber', 'onion', 'water', 'soda', 12],
 ['apple', 'banana', 'cucumber', 'onion', 'juice', 'soda', 13],
 ['apple', 'orange', 'tomato', 'cucumber', 'juice', 'soda', 12],
 ['apple', 'orange', 'tomato', 'onion', 'water', 'soda', 12],
 ['apple', 'orange', 'tomato', 'onion', 'juice', 'soda', 13],
 ['apple', 'orange', 'cucumber', 'onion', 'water', 'juice', 12],
 ['apple', 'orange', 'cucumber', 'onion', 'water', 'soda', 13],
 ['apple', 'orange', 'cucumber', 'onion', 'juice', 'soda', 14],
 ['banana', 'orange', 'tomato', 'cucumber', 'water', 'soda', 12],
 ['banana', 'orange', 'tomato', 'cucumber', 'juice', 'soda', 13],
 ['banana', 'orange', 'tomato', 'onion', 'water', 'juice', 12],
 ['banana', 'orange', 'tomato', 'onion', 'water', 'soda', 13],
 ['banana', 'orange', 'tomato', 'onion', 'juice', 'soda', 14],
 ['banana', 'orange', 'cucumber', 'onion', 'water', 'juice', 13],
 ['banana', 'orange', 'cucumber', 'onion', 'water', 'soda', 14]]

1
这种方法并不是很高效,因为一旦你花费超过14个单位,就没有必要继续搜索了。但是你可以使用itertools来简化,或者至少扁平化暴力方法:
from itertools import combinations, product, chain

fruits = [('apple', 1), ('banana', 2), ('orange', 3)]
veggies = [('tomato', 1), ('cucumber', 2), ('onion', 3)]
drinks = [('water', 1), ('juice', 2), ('soda', 3)]

options = fruits, veggies, drinks
possibles = product(*(combinations(opt, 2) for opt in options))
purchases = (list(chain.from_iterable(p)) for p in possibles)
within_range = [p for p in purchases if 12 <= sum(price for _, price in p) <= 14]

产生

>>> within_range[0]
[('apple', 1), ('banana', 2), ('tomato', 1), ('onion', 3), ('juice', 2), ('soda', 3)]
>>> within_range[-1]
[('banana', 2), ('orange', 3), ('cucumber', 2), ('onion', 3), ('water', 1), ('soda', 3)]
>>> [sum(p for _,p in w) for w in within_range]
[12, 12, 13, 12, 12, 13, 12, 13, 14, 12, 13, 12, 13, 14, 13, 14]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接