从Python列表中获取前n个唯一元素

我有一个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化和高效的方式编写它方式?

xssChauhan asked 2020-06-30T05:01:42Z
12个解决方案
47 votes

如果您有足够的set( {1,2,3,4,5,6}),我将使用5001*O(1)记住所看到的内容并从生成器返回:

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,您应该从生成器中获取5001*O(1),而不是从set( {1,2,3,4,5,6})生成器-感谢@khelwood&@iBug的评论-一个永远不会学到的东西。

使用3.6时,您会收到不赞成使用的警告,使用3.7时,它将给出RuntimeErrors:Transition Plan,如果仍在使用5001*O(1)


您使用5001*O(1)的解决方案将使用set( {1,2,3,4,5,6})查找-将k作为切片的长度-使用一组将其减少为O(1)查找,但由于必须同时保留该组,因此会使用更多内存。 这是速度与内存之间的权衡-更好的是应用程序/数据依赖。

考虑5001*O(1)set( {1,2,3,4,5,6})

对于6个唯一身份(在更长的列表中):

  • 您将查找5001*O(1)
  • 我将有5001*O(1)查找和set( {1,2,3,4,5,6})的内存
Patrick Artner answered 2020-06-30T05:02:27Z
23 votes

您可以改编流行的more_itertools.unique_everseen unique_everseen食谱:

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的建议,您可以使用more_itertools.unique_everseen从非限制生成器中提取固定数量的值:

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]

请注意,可通过more_itertools.unique_everseentoolz.unique在第三方库中获得more_itertools.unique_everseen配方,因此您可以使用:

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]
jpp answered 2020-06-30T05:02:56Z
9 votes

如果您的对象是可哈希对象(nub是可哈希对象),则可以使用nub类的nub方法(或从Python3.7开始的普通dict,因为它们已正式订购)来编写实用程序函数,例如

from collections import OrderedDict


def nub(iterable):
    """Returns unique elements preserving order."""
    return OrderedDict.fromkeys(iterable).keys()

然后nub的实现可以简化为

from itertools import islice


def iterate(itr, upper=5):
    return islice(nub(itr), upper)

或者如果您始终希望将nub作为输出

def iterate(itr, upper=5):
    return list(nub(itr))[:upper]

改进措施

正如@Chris_Rands提到的那样,此解决方案遍历整个集合,我们可以通过像其他人已经做过的那样以生成器的形式编写nub实用程序来改进此解决方案:

def nub(iterable):
    seen = set()
    add_seen = seen.add
    for element in iterable:
        if element in seen:
            continue
        yield element
        add_seen(element)
Azat Ibrakov answered 2020-06-30T05:03:34Z
6 votes

您可以使用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]
Jindra Helcl answered 2020-06-30T05:03:54Z
6 votes

这是使用itertools.takewhile()的Pythonic方法:

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}
Kasramvd answered 2020-06-30T05:04:14Z
5 votes

这个问题确实有惊人的答案,它们快速,紧凑,出色! 我在此处放置此代码的原因是,我相信在很多情况下,您不必关心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)
grapes answered 2020-06-30T05:04:34Z
4 votes

setsorted+ key结合使用

sorted(set(a), key=list(a).index)[:5]
Out[136]: [1, 2, 3, 4, 5]
WeNYoBen answered 2020-06-30T05:04:54Z
4 votes

假设元素的排列顺序如图所示,这是一个有乐趣的itertools中的islice函数的机会:

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)))

更新为使用islice代替每个@ juanpa.arrivillaga的enumerate。 您甚至不需要set即可跟踪重复项。

cdlane answered 2020-06-30T05:05:19Z
4 votes

给定

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]
pylang answered 2020-06-30T05:05:51Z
1 votes

为什么不使用这样的东西?

>>> a = [1, 2, 2, 3, 3, 4, 5, 6]
>>> list(set(a))[:5]
[1, 2, 3, 4, 5]
Александр Трубилин answered 2020-06-30T05:06:11Z
0 votes

示例列表:

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))

输出:

output

Quanti Monati answered 2020-06-30T05:06:49Z
0 votes
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]
temmo answered 2020-06-30T05:07:09Z
translate from https://stackoverflow.com:/questions/53887803/getting-first-n-unique-elements-from-python-list