Python:有效地在容器中找到副本

时间:2010-11-16 04:14:36

标签: python algorithm python-3.x duplicates

我有一个容器cont。如果我想知道它是否有重复,我只需检查len(cont) == len(set(cont))

假设我想找到一个重复的元素(如果它存在)(只是任意的重复元素)。是否有任何整洁有效的方式来写这个?

[Python 3]

9 个答案:

答案 0 :(得分:7)

您可以开始将它们添加到集合中,只要您尝试添加已在集合中的元素,就会发现重复。

答案 1 :(得分:4)

不明显的是找到一个任意元素是一个副本或一个或多个其他元素的集合...你想要删除它吗?将其属性与其twins / triplets / ... / N-tuplets的属性合并?在任何情况下,这是一个O(N)操作,如果重复,直到不再检测到重复操作是O(N ** 2)操作。

但是,您可以在算法仓库中获得大量交易:对集合进行排序 - O(N * log(N)) - 然后使用itertools.groupby将重复数据堆叠起来并巡航一串,忽略大小为1的束并用大小的束做任何你想做的事情> 1 - 所有这些只是O(N)。

答案 2 :(得分:4)

好的,我的第一个答案已经有了很多不足,所以我想我会尝试一些不同的方法来报告这些差异。这是我的代码。

import sys
import itertools

def getFirstDup(c, toTest):

    # Original idea using list slicing => 5.014 s
    if toTest == '1':
        for i in xrange(0, len(c)):
            if c[i] in c[:i]:
                return c[i]

    # Using two sets => 4.305 s
    elif toTest == '2':
        s = set()
        for i in c:
            s2 = s.copy()
            s.add(i)
            if len(s) == len(s2):
                return i

    # Using dictionary LUT => 0.763 s
    elif toTest == '3':
        d = {}
        for i in c:
            if i in d:
                return i
            else:
                d[i] = 1

    # Using set operations => 0.772 s
    elif toTest == '4':
        s = set()
        for i in c:
            if i in s:
                return i
            else:
                s.add(i)

    # Sorting then walking => 5.130 s
    elif toTest == '5':
        c = sorted(c)
        for i in xrange(1, len(c)):
            if c[i] == c[i - 1]:
                return c[i]

    # Sorting then groupby-ing => 5.086 s
    else:
        c = sorted(c)
        for k, g in itertools.groupby(c):
            if len(list(g)) > 1:
                return k

    return None


c = list(xrange(0, 10000000))
c[5000] = 0

for i in xrange(0, 10):
    print getFirstDup(c, sys.argv[1])

基本上,我以六种不同的方式尝试此操作,如源文件中所列。我使用Linux time命令并收集实时运行时,运行如此命令

time python ./test.py 1

1是我想要尝试的算法。每个算法在10,000,000个整数中查找第一个副本,并运行十次。列表中有一个重复,虽然我确实尝试了反向排序列表而没有注意到算法之间的比例差异,但是“主要是排序”。

我的原始建议在5.014秒时表现不佳。我对icyrock.com解决方案的理解也在4.305 s时表现不佳。接下来我尝试使用字典来创建一个LUT,它在0.763秒时提供了最佳的运行时间。我尝试在集合上使用in运算符,得到0.772秒,几乎与字典LUT一样好。我尝试对列表进行排序和行走,这给了一个可怜的5.130秒的时间。最后,我尝试了John Machin对itertools的建议,这给了5.086秒的糟糕时间。

总之,字典LUT 似乎是要走的路,设置操作(可能在其实现中使用LUT)紧随其后。


更新:我尝试了razpeitia的建议,除了你需要确切知道你正在寻找什么重复键之外,实际算法到目前为止做得最差(66.366秒)。


更新2:我确定有人会说这个测试是有偏见的,因为重复的位置靠近列表的一端。 在downvoting之前尝试使用其他位置运行代码并报告结果!

答案 3 :(得分:3)

from collections import Counter

cont = [1, 2, 3]
c = Counter(cont)
x = someItem

if c[x] == 0:
    print("Not in cont")
elif c[x] == 1:
    print("Unique")
else:
    print("Duplicate")

答案 4 :(得分:0)

你必须扫描重复项的所有元素,因为它们可能只是你检查的最后一个元素,所以你不能比最坏情况的O(N)时间更有效,就像线性搜索一样。但是,找到重复的简单线性搜索将耗尽O(N)内存,因为您需要跟踪到目前为止所看到的内容。

如果对数组进行了排序,您可以在O(N)时间内找到重复项,而不使用任何额外的内存,因为重复的对将彼此相邻。

答案 5 :(得分:0)

如果您的容器是一个列表,您只需将您要查找的值传递给其count()方法并检查结果:

>>> l = [1,1,2,3]
>>> l.count(1)
2
>>> 

字典不能有重复的密钥,也不能有一套。除此之外,我需要知道它是什么类型的容器。我想真正的重点是在编写自定义解决方案之前始终确保您没有错过明显的问题解决方案。我有时会成为这个的牺牲品:)

答案 6 :(得分:0)

根据http://wiki.python.org/moin/TimeComplexity,大多数列表操作都非常低效(刚刚确认x in myList在python3中看起来似乎是O(N)

原始海报给出的方法有效,因为它是O(N)时间和空间(这是你可以做到的“最佳”,而不对列表做出额外的假设,因为列表操作像x in myList} O(N))。

有一种可能的主要优化,即迭代地构建集合。这将在某些类型的列表上快速返回,例如[0,1,1,2,3,4,5,...]。但是,您隐含地假设了一些列表的分布(例如,您是针对这种情况进行优化,还是最后针对重复项进行优化,或两者兼而有之?)。这种优化的好处是它不会影响渐近速度。这是我如何优雅地编码:

def hasDuplicate(iter):
    visited = set()
    for item in iter:
        if item in visited:
            return True
        visited.add(item)
    return False

您也可以返回第一个副本,但不能返回None;因为可迭代可能包含None

,所以你必须提出异常

旁注:有一些方法可以提高空间效率,以达到时间效率的轻微打击(例如布隆过滤器)。

答案 7 :(得分:0)

另一个建议,类似于jonesy的回答。至少在python3中(未在python 2.7中测试过),当c [-5000] = 0时,这比原始答案的解3和4更快。否则它只比解决方案1和2稍快一点......

elif toTest == '7':
    for i in c:
        if c.count(i)>1:
            return i

答案 8 :(得分:-1)

试试这个:

def getFirstDup(cont):
    for i in xrange(0, len(cont)):
        if cont[i] in cont[:i]:
            return cont[i]
    return None