如果我们需要保持元素的顺序,那么这样怎么样:
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的解决方案,没有临时使用的var。
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开始,标准字典保持插入顺序。
在3.7版更改:字典顺序保证为插入顺序。此行为是CPython 3.6版本的实现细节。
因此,这使我们能够使用dict.from_keys进行重复删除!
注意:感谢@rlat在评论中给我们这个方法!
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, [])
注意:请记住,我们获得的人类可读性越高,脚本的性能就越差。除了dict.from_keys方法,它是python 3.7+特有的。
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
回复评论
因为@莫妮卡问了一个关于“这是怎么工作的?”的好问题。献给每一个有问题的人。我将尝试更深入地解释这是如何工作的,以及这里发生了什么巫术;)
所以她首先问:
我试图理解为什么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]
问题是我们只是在唯一的变量中没有得到想要的结果,而只是在使用的变量中。这是因为在列表理解过程中.append会修改所使用的变量并返回None。
因此,为了将结果放入唯一变量中,并且如果x未被使用,则仍然使用与.append(x)相同的逻辑,我们需要将这个.append调用移动到列表推导式的右侧,并在左侧返回x。
但如果我们太天真了,就这样:
>>> 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时,结果为False,
当x未被使用时,计算结果为None。
在这两种情况下(False/None),这将被视为假值,我们将得到一个空列表作为结果。
但是当x不被使用时,为什么这个值为None呢?有人可能会问。
这是因为Python的短路操作符就是这样工作的。
表达式x和y首先求x的值;如果x为false,则其值为
返回;否则,计算y,结果值为
返回。
因此,当x未被使用时(即当它为True时),下一部分或表达式将被计算(using .append(x)),并返回它的值(None)。
但这就是我们想要的,为了从具有重复项的列表中获得唯一的元素,我们希望仅当它们第一次遇到时才将它们追加到新列表中。
因此,我们希望只在x未被使用时才对used。append(x)求值,如果有办法将None值转换为真值就可以了,对吧?
是的,这就是第二种短路操作者发挥作用的地方。
表达式x或y首先求x的值;如果x为真,它的值为
返回;否则,计算y,结果值为
返回。
我们知道.append(x)总是假的,所以如果我们只是在它旁边加上一个或,我们总是会得到下一个部分。这就是为什么我们写:
x not in used and (used.append(x) or True)
因此,我们可以求use .append(x)的值并得到True结果,只有当表达式的第一部分(x未被使用)为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原封不动