列表理解,检查项目是不是唯一

Posted

技术标签:

【中文标题】列表理解,检查项目是不是唯一【英文标题】:List comprehension, check if item is unique列表理解,检查项目是否唯一 【发布时间】:2015-07-31 15:38:54 【问题描述】:

我正在尝试编写一个列表理解语句,如果它当前不包含在列表中,它将只添加一个项目。有没有办法检查当前正在构建的列表中的当前项目?下面是一个简单的例子:

输入


    "Stefan" : ["running", "engineering", "dancing"],
    "Bob" : ["dancing", "art", "theatre"],
    "Julia" : ["running", "music", "art"]

输出

["running", "engineering", "dancing", "art", "theatre", "music"]

不使用列表理解的代码

output = []
for name, hobbies in input.items():
    for hobby in hobbies:
        if hobby not in output:
            output.append(hobby)

我的尝试

[hobby for name, hobbies in input.items() for hobby in hobbies if hobby not in ???]

【问题讨论】:

考虑改用一个集合,因为它会自动为您消除重复。 我可以将列表推导的输出放在 set() 构造函数中,但我想知道是否有办法只使用列表推导来做到这一点 你可以在 python3.3+ 中滥用yield from 来避免双重循环:set([(yield from x) for x in d.values()])。请注意,执行s=set((yield from x) for x in d.values()) 会产生几乎 所需的输出...它包含一个额外的None,您必须将其删除,因此s.discard(None)。 (我相信对于 gen exps 和 list-comprehensions 之间的这种行为差异已经存在疑问)。 【参考方案1】:

您可以使用set 并设置理解:

hobby for name, hobbies in input.items() for hobby in hobbies

作为m.wasowski mentioned,我们这里不用name,所以我们可以用item.values()代替:

hobby for hobbies in input.values() for hobby in hobbies

如果你真的需要一个列表作为结果,你可以这样做(但请注意,通常你可以毫无问题地使用集合):

list(hobby for hobbies in input.values() for hobby in hobbies)

【讨论】:

我想甚至没有任何理由将其转换回列表。无论如何,该列表很可能是一组概念。 @ThijsvanDien 我只是补充说,因为 OP 将他的输出放在一个列表中,他可能有一些特殊原因需要一个列表。在大多数情况下,你当然是对的。 如果你不关心名字,你可以使用input.values() @m.wasowski 谢谢,你是对的。我会把它添加到答案中。 有人可以通过中间理解来了解“爱好”和“爱好”是如何定义的吗?我对这里的执行顺序并不完全清楚。谢谢。【参考方案2】:

正如this answer 建议的那样:您可以使用唯一性过滤器:

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

并调用:

>>> f7(hobby for name, hobbies in input.items() for hobby in hobbies)
['running', 'engineering', 'dancing', 'art', 'theatre', 'music']

我会单独实现唯一性过滤器,因为设计规则说“不同的事情应该由不同的类/方法/组件/任何东西来处理”。此外,如有必要,您可以简单地重复使用此方法。

另一个优点是 - 正如在linked answer 中所写的那样 - 项目的顺序得以保留。对于某些应用程序,这可能是必要的。

【讨论】:

去掉方括号会更好 @shx2: 修改,更好? 维持秩序的要点。 IMO 如果谓词写成if x not in seen and seen.add(x) is None,则更具可读性 很公平,先生,无论如何我都赞成您的回答,因为我认为由于维持秩序,它比接受的要好。 @ThijsvanDien 有副作用的列表理解有什么问题?这与具有副作用的 for 循环几乎相同。【参考方案3】:

如果你真的想要一个 listcomp 并且只需要一个 list-comp,你可以这样做

>>> s = []
>>> [s.append(j)  for i in d.values() for j in i if j not in s]
[None, None, None, None, None, None]
>>> s
['dancing', 'art', 'theatre', 'running', 'engineering', 'music']

这里,s 是副作用的结果,d 是您的原始字典。这里的独特优势是与这里的大多数其他答案不同,您可以保留顺序

注意:这是一种不好的方式,因为它利用了 list-comp 并且结果是副作用。不要将其作为练习,此答案只是为了向您展示您可以单独使用列表组合来实现它

【讨论】:

呃..这种解决方案不能气馁。 @m.wasowski 确实,您的解决方案是更好的。这只是一个替代方案。任何未来的用户肯定会在我之前看到您的答案。所以他们也会学习另一种方法。我同意这不是一个好方法,我从发布它作为答案的那一刻起就一直在说。至于单字母变量名。我知道他们不符合 PEP。但这在这里没有害处,因为它是列表中的第 6 个答案。但是,如果您仍然觉得这里不应该存在此答案,请务必提及。我将删除它,并给 10k 以上的未来观众留言。再次感谢! @m.wasowski:我认为 SO 的模型是从问题中提供 一对多 关系来回答每个问题的(不利)优势。只要有足够的警告,即使是最坏情况的指数时间解决方案也是可以接受的。 投票系统用于 (a) 过滤不正确的答案和 (b) 对答案进行排名以供实际使用。 哦,我的... 抱歉表达了我的意见 ;-) 无论如何,我不会对你们大打出手,真的! @Bhargav Rao - 当然保留你的答案 - 并继续做好回答这里的人的工作。对不起,如果我的 cmets 太苛刻了,我不是故意的。 @m.wasowski 你真的不需要在这里道歉!唯一的一点是,如果这个答案是第一个或第二个或 FGITW,那么它肯定不属于这里。回答的时机也很重要。一个不好的答案表明它为什么不好也有助于未来的用户在解决问题时不要使用这种策略。我希望有了这个说明,所有的立场都清楚了!祝你今天过得愉快。 :)【参考方案4】:

集合和字典在这里是你的朋友:

from collections import OrderedDict
from itertools import chain # 'flattens' collection of iterables

data = 
    "Stefan" : ["running", "engineering", "dancing"],
    "Bob" : ["dancing", "art", "theatre"],
    "Julia" : ["running", "music", "art"]


# using set is the easiest way, but sets are unordered:
print hobby for hobby in chain.from_iterable(data.values())
# output:
# set(['art', 'theatre', 'dancing', 'engineering', 'running', 'music'])


# or use OrderedDict if you care about ordering:
print OrderedDict(
        (hobby, None) for hobby in chain.from_iterable(data.values())
    ).keys()
# output:
# ['dancing', 'art', 'theatre', 'running', 'engineering', 'music']

【讨论】:

OrderedDict 方法是我在这里选择的方法。 我认为您的回答应该澄清您对“排序”的含义:由于data 是普通字典,因此对data.values() 的调用顺序是任意的。因此,OrderedDict 中的keys() 的顺序也必须被认为是部分任意的。列表是随机排序的(在您的情况下,列表的顺序是:Bob、Stefan、Julia),但它们的元素顺序被保留。由于重叠,这可能会导致一些完全不同的订单。【参考方案5】:

还有另一种写法,它更能描述你实际在做什么,并且不需要嵌套(双重for)理解:

output = set.union(*[set(hobbies) for hobbies in input_.values()])

当您将输入表示为更具概念性时,这会变得更好,即为每个人的爱好使用set(因为那里也不应该重复):

input_ = 
    "Stefan" : "running", "engineering", "dancing",
    "Bob" : "dancing", "art", "theatre", 
    "Julia" : "running", "music", "art"


output = set.union(*input_.values())

【讨论】:

对我来说 - 迄今为止最好的解决方案【参考方案6】:

列表推导不太适合这个问题。我认为集合理解会更好,但是由于已经在另一个答案中显示了这一点,因此我将展示一种使用紧凑的单线解决此问题的方法:

list(set(sum(hobbies_dict.values(), [])))

另一个有趣的解决方案是使用按位或运算符作为集合的联合运算符:

from operator import or_
from functools import reduce # Allowed, but unnecessary in Python 2.x
list(reduce(or_, map(set, hobbies_dict.values())))

或者(无意的双关语,我发誓),而不是使用按位或运算符,只需使用set.union 并将您的值的解压缩集映射传递给它。无需导入or_reduce!这个想法的灵感来自Thijs van Dien's answer。

list(set.union(*map(set, hobbies_dict.values())))

【讨论】:

【参考方案7】:

使用一组:

dict = 
    "Stefan" : ["running", "engineering", "dancing"],
    "Bob" : ["dancing", "art", "theatre"],
    "Julia" : ["running", "music", "art"]


myset = set()
for _, value in dict.items():
    for item in value:
        myset.add(item)

print(myset)

【讨论】:

【参考方案8】:

这个怎么样:

set(dict['Bob']+dict['Stefan']+dict['Julia'])
>>> set(['art', 'theatre', 'dancing', 'engineering', 'running', 'music'])

或者更好:

dict = 
    "Stefan" : ["running", "engineering", "dancing"],
    "Bob" : ["dancing", "art", "theatre"],
    "Julia" : ["running", "music", "art"]


list_ = []
for y in dict.keys():
    list_ = list_ + dict[y]
list_ = set(list_)
>>> list_
set(['art', 'theatre', 'dancing', 'engineering', 'running', 'music'])

您可以将list 函数应用于list_,例如list(list_),以返回列表而不是集合。

【讨论】:

以上是关于列表理解,检查项目是不是唯一的主要内容,如果未能解决你的问题,请参考以下文章

布尔列表检查列表中的每个项目是不是为假

检查dict中是不是存在给定项目的列表[重复]

有没有办法生成项目列表的所有唯一排列

检查列表中的项目以查看它们是不是彼此相邻

检查列表框中的项目是不是已存在于许多其他列表框中?

.NET 是不是有办法检查列表 a 是不是包含列表 b 中的所有项目?