我有一个python列表,其中的元素可以重复。
>>> a = [1,2,2,3,3,4,5,6]
我想从列表中获得前n
个唯一元素。
因此,在这种情况下,如果我想要前5个唯一元素,它们将是:
[1,2,3,4,5]
我想出了一个使用生成器的解决方案:
def iterate(itr, upper=5):
count = 0
for index, element in enumerate(itr):
if index==0:
count += 1
yield element
elif element not in itr[:index] and count<upper:
count += 1
yield element
使用中:
>>> i = iterate(a, 5)
>>> [e for e in i]
[1,2,3,4,5]
我怀疑这是最佳解决方案。有没有一种我可以实施的替代策略,可以用更加Python化和高效的方式编写它 方式吗?
答案 0 :(得分:45)
当您有足够的set
时,我会使用seen
来记住所见并从生成器返回:
a = [1,2,2,3,3,4,5,6]
def get_unique_N(iterable, N):
"""Yields (in order) the first N unique elements of iterable.
Might yield less if data too short."""
seen = set()
for e in iterable:
if e in seen:
continue
seen.add(e)
yield e
if len(seen) == N:
return
k = get_unique_N([1,2,2,3,3,4,5,6], 4)
print(list(k))
输出:
[1,2,3,4]
根据PEP-479,您应该从生成器return
,而不是raise StopIteration
-感谢@khelwood和@iBug的评论-一个永远不会学。
使用3.6时,您会弃用警告,使用3.7时,它会给出RuntimeErrors:Transition Plan(如果仍使用raise StopIteration
您使用elif element not in itr[:index] and count<upper:
的解决方案使用O(k)
查找-以k
为切片的长度-使用一组将其减少为O(1)
查找,但使用更多的内存,因为设置也必须保留。这是速度与内存之间的折衷-更好的是应用程序/数据依赖项。
考虑[1,2,3,4,4,4,4,5]
与[1]*1000+[2]*1000+[3]*1000+[4]*1000+[5]*1000+[6]
:
对于6个唯一身份(在较长列表中):
O(1)+O(2)+...+O(5001)
5001*O(1)
拥有set( {1,2,3,4,5,6})
查找+内存答案 1 :(得分:23)
您可以改编流行的itertools
unique_everseen
recipe:
def unique_everseen_limit(iterable, limit=5):
seen = set()
seen_add = seen.add
for element in iterable:
if element not in seen:
seen_add(element)
yield element
if len(seen) == limit:
break
a = [1,2,2,3,3,4,5,6]
res = list(unique_everseen_limit(a)) # [1, 2, 3, 4, 5]
或者,按照@Chris_Rands的建议,您可以使用itertools.islice
从非限制生成器中提取固定数量的值:
from itertools import islice
def unique_everseen(iterable):
seen = set()
seen_add = seen.add
for element in iterable:
if element not in seen:
seen_add(element)
yield element
res = list(islice(unique_everseen(a), 5)) # [1, 2, 3, 4, 5]
请注意,unique_everseen
配方可通过more_itertools.unique_everseen
或toolz.unique
在第三方库中使用,因此您可以使用:
from itertools import islice
from more_itertools import unique_everseen
from toolz import unique
res = list(islice(unique_everseen(a), 5)) # [1, 2, 3, 4, 5]
res = list(islice(unique(a), 5)) # [1, 2, 3, 4, 5]
答案 2 :(得分:9)
如果您的对象是hashable(int
是可哈希的),则可以使用fromkeys
method中的collections.OrderedDict
class(或从 Python3.7 < / em>一个普通的dict
,因为它们成为officially的有序对象),如
from collections import OrderedDict
def nub(iterable):
"""Returns unique elements preserving order."""
return OrderedDict.fromkeys(iterable).keys()
然后将iterate
的实现简化为
from itertools import islice
def iterate(itr, upper=5):
return islice(nub(itr), upper)
或者如果您始终希望将list
作为输出
def iterate(itr, upper=5):
return list(nub(itr))[:upper]
就像@Chris_Rands提到的那样,此解决方案遍历整个集合,我们可以像其他人一样通过以generator的形式编写nub
实用程序来改进此解决方案:
def nub(iterable):
seen = set()
add_seen = seen.add
for element in iterable:
if element in seen:
continue
yield element
add_seen(element)
答案 3 :(得分:6)
您可以使用OrderedDict
,或者从Python 3.7开始使用普通的dict
,因为它们是为保留插入顺序而实现的。请注意,这不适用于集合。
N = 3
a = [1, 2, 2, 3, 3, 3, 4]
d = {x: True for x in a}
list(d.keys())[:N]
答案 4 :(得分:6)
这是使用itertools.takewhile()
的Python方法:
In [95]: from itertools import takewhile
In [96]: seen = set()
In [97]: set(takewhile(lambda x: seen.add(x) or len(seen) <= 4, a))
Out[97]: {1, 2, 3, 4}
答案 5 :(得分:5)
对于这个问题,确实有非常惊人的答案,它们快速,紧凑,出色!我将这段代码放到这里的原因是,我相信在很多情况下,您不必关心1微秒的时间松散,也不希望在代码中使用其他库来一次性解决一个简单的任务。
a = [1,2,2,3,3,4,5,6]
res = []
for x in a:
if x not in res: # yes, not optimal, but doesnt need additional dict
res.append(x)
if len(res) == 5:
break
print(res)
答案 6 :(得分:4)
将set
与sorted+ key
一起使用
sorted(set(a), key=list(a).index)[:5]
Out[136]: [1, 2, 3, 4, 5]
答案 7 :(得分:4)
假设元素按所示顺序排列,这是一个通过itertools中的groupby
函数进行娱乐的机会:
from itertools import groupby, islice
def first_unique(data, upper):
return islice((key for (key, _) in groupby(data)), 0, upper)
a = [1, 2, 2, 3, 3, 4, 5, 6]
print(list(first_unique(a, 5)))
每个@ juanpa.arrivillaga已更新为使用islice
而不是enumerate
。您甚至不需要set
来跟踪重复项。
答案 8 :(得分:4)
给出
import itertools as it
a = [1, 2, 2, 3, 3, 4, 5, 6]
代码
简单的列表理解(类似于@cdlane的答案)。
[k for k, _ in it.groupby(a)][:5]
# [1, 2, 3, 4, 5]
或者,在Python 3.6及更高版本中:
list(dict.fromkeys(a))[:5]
# [1, 2, 3, 4, 5]
答案 9 :(得分:2)
哪种解决方案最快?有两个明显最喜欢的答案(和 3 个解决方案)获得了大部分选票。
这是因为这些声称在 O(N) 中运行而其他人在 O(N^2) 中运行,或者不保证返回列表的顺序。
本实验考虑了 3 个变量。
数据生成的假设如下。这些的严格程度取决于所使用的算法,但更多的是对数据生成方式的说明,而不是对算法本身的限制。
因此在 [1,1,1,2,2,3,4 ....] 的列表中,1,2,3 永远不会再出现。 4 之后的下一个元素将是 5,但在我们看到 5 之前可能有一个随机数 4,直到重复限制。
为每个变量组合创建了一个新数据集,并重新生成了 20 次。 python timeit
函数用于在每个数据集上分析算法 50 次。此处报告了 20x50=1000 次运行(对于每个组合)的平均时间。由于算法是生成器,因此将其输出转换为列表以获取执行时间。
正如预期的那样,搜索的元素越多,所需的时间就越长。该图表明,执行时间确实是作者声称的 O(N)(直线证明了这一点)。
图 1. 改变搜索的前 N 个元素。
所有三种解决方案都不会消耗超出所需的额外计算时间。下图显示了当列表大小受限而不是 N 个元素时会发生什么。长度为 10k 的列表,元素最多重复 100 次(因此平均重复 50 次)平均会用完唯一元素 200 (10000/50)。如果这些图表中的任何一个显示计算时间增加超过 200,就会引起关注。
图 2. 选择的前 N 个元素的效果 > 唯一元素的数量。
下图再次显示,算法必须筛选的数据越多,处理时间就会增加(以 O(N) 的速度增加)。增加率与前 N 个元素变化时相同。这是因为单步执行列表是两者的共同执行块,也是最终决定算法速度的执行块。
图 3. 改变重复限制。
在所有情况下,2nd solution posted by jpp 是 3 中最快的解决方案。该解决方案仅比 solution posted by Patrick Artner 略快,几乎是 his first solution 的两倍。
答案 10 :(得分:1)
为什么不使用这样的东西?
>>> a = [1, 2, 2, 3, 3, 4, 5, 6]
>>> list(set(a))[:5]
[1, 2, 3, 4, 5]
答案 11 :(得分:0)
a = [1, 2, 2, 3, 3, 4, 5, 6]
第一个参数-要使用的列表,第二个参数(可选)-唯一项的计数(默认情况下-无-表示将返回所有唯一元素)
def unique_elements(lst, number_of_elements=None):
return list(dict.fromkeys(lst))[:number_of_elements]
这里是示例它如何工作的。列表名称为“ a”,我们需要获取2个唯一元素:
print(unique_elements(a, 2))
答案 12 :(得分:0)
a = [1,2,2,3,3,4,5,6]
from collections import defaultdict
def function(lis,n):
dic = defaultdict(int)
sol=set()
for i in lis:
try:
if dic[i]:
pass
else:
sol.add(i)
dic[i]=1
if len(sol)>=n:
break
except KeyError:
pass
return list(sol)
print(function(a,3))
输出
[1, 2, 3]