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

时间:2018-12-21 16:10:26

标签: python python-3.x generator

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

13 个答案:

答案 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_everseentoolz.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)

如果您的对象是hashableint是可哈希的),则可以使用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)

setsorted+ 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 个解决方案)获得了大部分选票。

  1. solution by Patrick Artner - 表示为 PA。
  2. first solution by jpp - 表示为 jpp1
  3. second solution by jpp - 表示为 jpp2

这是因为这些声称在 O(N) 中运行而其他人在 O(N^2) 中运行,或者不保证返回列表的顺序。

实验设置

本实验考虑了 3 个变量。

  1. N 个元素。函数搜索的前 N ​​个元素的数量。
  2. 列表长度。列表越长,算法就越需要寻找最后一个元素。
  3. 重复限制。在下一个元素出现在列表中之前,一个元素可以重复多少次。这在 1 和重复限制之间均匀分布。

数据生成的假设如下。这些的严格程度取决于所使用的算法,但更多的是对数据生成方式的说明,而不是对算法本身的限制。

  1. 元素在其重复序列首次出现在列表中后就不再出现。
  2. 元素为数字且递增。
  3. 元素的类型为 int。

因此在 [1,1,1,2,2,3,4 ....] 的列表中,1,2,3 永远不会再出现。 4 之后的下一个元素将是 5,但在我们看到 5 之前可能有一个随机数 4,直到重复限制。

为每个变量组合创建了一个新数据集,并重新生成了 20 次。 python timeit 函数用于在每个数据集上分析算法 50 次。此处报告了 20x50=1000 次运行(对于每个组合)的平均时间。由于算法是生成器,因此将其输出转换为列表以获取执行时间。

结果

正如预期的那样,搜索的元素越多,所需的时间就越长。该图表明,执行时间确实是作者声称的 O(N)(直线证明了这一点)。

Fig 1. Varying the first N elements searched for.

图 1. 改变搜索的前 N ​​个元素。

所有三种解决方案都不会消耗超出所需的额外计算时间。下图显示了当列表大小受限而不是 N 个元素时会发生什么。长度为 10k 的列表,元素最多重复 100 次(因此平均重复 50 次)平均会用完唯一元素 200 (10000/50)。如果这些图表中的任何一个显示计算时间增加超过 200,就会引起关注。

Fig 2. The effect of first N elements chosen > number of unique elements.

图 2. 选择的前 N ​​个元素的效果 > 唯一元素的数量。

下图再次显示,算法必须筛选的数据越多,处理时间就会增加(以 O(N) 的速度增加)。增加率与前 N 个元素变化时相同。这是因为单步执行列表是两者的共同执行块,也是最终决定算法速度的执行块。

Fig 3. Varying the repeat limit.

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

输出:

output

答案 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]