如果我们需要保持元素的顺序,这样怎么样:
used = set()
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = [x for x in mylist if x not in used and (used.add(x) or True)]
还有一种使用reduce 且不使用临时used 变量的解决方案。
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = reduce(lambda l, x: l.append(x) or l if x not in l else l, mylist, [])
更新 - 2020 年 12 月 - 也许是最好的方法!
从 python 3.7 开始,标准 dict 保留插入顺序。
在 3.7 版中更改:保证字典顺序为插入顺序。这种行为是 CPython 3.6 的实现细节。
所以这使我们能够使用dict.from_keys 进行重复数据删除!
注意:感谢 @rlat 在 cmets 中为我们提供了这种方法!
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = list(dict.fromkeys(mylist))
在速度方面 - 对我来说它足够快且可读性足以成为我最喜欢的新方法!
更新 - 2019 年 3 月
还有第三种解决方案,这是一个简洁的解决方案,但有点慢,因为 .index 是 O(n)。
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = [x for i, x in enumerate(mylist) if i == mylist.index(x)]
更新 - 2016 年 10 月
另一个使用reduce 的解决方案,但这次没有.append,这使得它更易于阅读和理解。
mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
unique = reduce(lambda l, x: l+[x] if x not in l else l, mylist, [])
#which can also be writed as:
unique = reduce(lambda l, x: l if x in l else l+[x], mylist, [])
注意:请记住,我们获得的可读性越高,脚本的性能就越差。除了特定于 python 3.7+ 的 dict.from_keys 方法。
import timeit
setup = "mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']"
#10x to Michael for pointing out that we can get faster with set()
timeit.timeit('[x for x in mylist if x not in used and (used.add(x) or True)]', setup='used = set();'+setup)
0.2029558869980974
timeit.timeit('[x for x in mylist if x not in used and (used.append(x) or True)]', setup='used = [];'+setup)
0.28999493700030143
# 10x to rlat for suggesting this approach!
timeit.timeit('list(dict.fromkeys(mylist))', setup=setup)
0.31227896199925453
timeit.timeit('reduce(lambda l, x: l.append(x) or l if x not in l else l, mylist, [])', setup='from functools import reduce;'+setup)
0.7149233570016804
timeit.timeit('reduce(lambda l, x: l+[x] if x not in l else l, mylist, [])', setup='from functools import reduce;'+setup)
0.7379565160008497
timeit.timeit('reduce(lambda l, x: l if x in l else l+[x], mylist, [])', setup='from functools import reduce;'+setup)
0.7400134069976048
timeit.timeit('[x for i, x in enumerate(mylist) if i == mylist.index(x)]', setup=setup)
0.9154880290006986
回答评论
因为 @monica 提出了一个关于“这是如何工作的?”的好问题。对于每个有问题的人。我将尝试更深入地解释它是如何工作的以及这里发生了什么巫术;)
于是她先问:
我试图理解为什么unique = [used.append(x) for x in mylist if x not in used] 不起作用。
它确实有效
>>> used = []
>>> mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']
>>> unique = [used.append(x) for x in mylist if x not in used]
>>> print used
[u'nowplaying', u'PBS', u'job', u'debate', u'thenandnow']
>>> print unique
[None, None, None, None, None]
问题是我们在unique 变量中没有得到想要的结果,而只是在used 变量中。这是因为在列表解析期间.append 修改了used 变量并返回None。
因此,为了将结果放入unique 变量中,并且仍然使用与.append(x) if x not in used 相同的逻辑,我们需要将这个.append 调用移动到列表理解的右侧并返回@987654347 @在左侧。
但是如果我们太天真了,那就去吧:
>>> unique = [x for x in mylist if x not in used and used.append(x)]
>>> print unique
[]
我们不会得到任何回报。
同样,这是因为.append 方法返回None,这使得我们的逻辑表达式如下所示:
x not in used and None
这基本上总是:
- 当
x 在used 中时计算为False,
- 当
x 不在used 中时评估为None。
在这两种情况下 (False/None),这将被视为 falsy 值,因此我们将得到一个空列表。
但是当x 不在used 中时,为什么它的计算结果为None?有人可能会问。
这是因为这就是 Python 的 short-circuit 运算符 works 的方式。
表达式x and y首先计算x;如果 x 为假,则其值为
回来;否则,对 y 求值,结果值为
返回。
所以当x 没有被使用时(即当它的True)下一部分或表达式将被评估(used.append(x))和它的值 (None) 将被返回。
但这就是我们想要从具有重复项的列表中获取唯一元素的目的,我们希望仅当我们第一次遇到它们时才将它们 .append 放入一个新列表中。
所以我们真的只想在x 不在used 中时评估used.append(x),也许如果有办法将这个None 值变成truthy 值,我们会没事的,对吧?
嗯,是的,这就是第二类short-circuit 运算符的作用所在。
表达式x or y首先计算x;如果 x 为真,则其值为
回来;否则,对 y 求值,结果值为
返回。
我们知道.append(x) 永远是falsy,所以如果我们只在他旁边添加一个or,我们总是会得到下一部分。这就是我们写作的原因:
x not in used and (used.append(x) or True)
所以我们可以评估 used.append(x) 并得到True 结果,仅当表达式的第一部分(x not in used) 是True。
在reduce 方法的第二种方法中可以看到类似的方式。
(l.append(x) or l) if x not in l else l
#similar as the above, but maybe more readable
#we return l unchanged when x is in l
#we append x to l and return l when x is not in l
l if x in l else (l.append(x) or l)
我们在哪里:
- 将
x 附加到l 并在x 不在l 中时返回l。感谢or 语句,.append 被评估,l 被返回。
- 当
x 在l 中时返回l untouched