如果我们需要保持元素的顺序,这样如何:
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.fromkeys()
进行去重!
注意:感谢@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, [])
unique = reduce(lambda l, x: l if x in l else l+[x], mylist, [])
注意:请记住,我们越易读,脚本的性能就越差。除了适用于Python 3.7+的
dict.fromkeys()
方法外。
import timeit
setup = "mylist = [u'nowplaying', u'PBS', u'PBS', u'nowplaying', u'job', u'debate', u'thenandnow']"
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
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]
问题在于我们只能在
used
变量中得到期望的结果,而无法在
unique
变量中得到。这是因为在列表推导中,
.append
修改了
used
变量并返回
None
。
为了将结果放入
unique
变量中,并仍然使用
.append(x) if x not in used
的逻辑,我们需要将这个
.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
在 used
中时,评估结果为 False
,
- 当
x
不在 used
中时,评估结果为 None
。
而在这两种情况下(
False
/
None
),这将被视为假值,并且我们将得到一个空列表作为结果。
但是为什么当
x
不在
used
中时,它会评估为
None
?有人可能会问。
嗯,这是因为这是 Python 的
短路运算符
的工作方式。
表达式 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
。
unique_items = list(dict.fromkeys(list_with_duplicates))
(CPython 3.6+) - user3064538