如何检查平面列表中是不是有重复项?

Posted

技术标签:

【中文标题】如何检查平面列表中是不是有重复项?【英文标题】:How do I check if there are duplicates in a flat list?如何检查平面列表中是否有重复项? 【发布时间】:2010-12-05 05:39:33 【问题描述】:

例如,给定列表['one', 'two', 'one'],算法应该返回True,而给定['one', 'two', 'three'],它应该返回False

【问题讨论】:

【参考方案1】:

如果所有值都可散列,则使用set() 删除重复项:

>>> your_list = ['one', 'two', 'one']
>>> len(your_list) != len(set(your_list))
True

【讨论】:

在阅读本文之前,我已经尝试过 your_list != list(set(your_list)) ,因为元素的顺序会改变,所以它不起作用。使用 len 是解决这个问题的好方法 通常不适用于浮点数组。见***.com/questions/60914705 根据peterbe.com/plog/fastest-way-to-uniquify-a-list-in-python-3.6检查/删除重复的最快方法 我认为将这个很好的答案应用于通用假设检验的一种自然方法是使用 len(your_list) == len(set(your_list)) 而不是 != ,因为您要么接受 (True)或拒绝(错误)它们的长度相同,因此没有重复。【参考方案2】:

仅推荐用于 列表:

any(thelist.count(x) > 1 for x in thelist)

不要不要在长列表中使用——它所花费的时间可能与列表中项目数的平方成正比!

对于带有可散列项(字符串、数字等)的较长列表:

def anydup(thelist):
  seen = set()
  for x in thelist:
    if x in seen: return True
    seen.add(x)
  return False

如果您的项目不可散列(子列表、字典等),它会变得更复杂,但如果它们至少具有可比性,仍然有可能获得 O(N logN)。但是您需要知道或测试项目的特征(可散列与否,可比较或不可比较)以获得最佳性能 - O(N) 用于可散列,O(N log N) 用于不可散列可比较,否则它下降到 O(N 平方),对此无能为力:-(。

【讨论】:

Denis Otkidach 提供了一个解决方案,您只需从列表中构建一个新集合,然后检查其长度。它的优点是它让 Python 中的 C 代码完成繁重的工作。您的解决方案在 Python 代码中循环,但具有在找到单个匹配项时短路的优势。如果列表可能没有重复项,我喜欢 Denis Otkidach 的版本,但如果列表的早期很可能有重复项,则此解决方案更好。 细节值得一提,尽管我认为 Denis 有更简洁的解决方案。 @steveha - 过早优化? 如果项目是可散列的,一套解决方案更直接,而且,我表达它的方式,更快(一旦知道答案就退出 - “短路”,史蒂夫哈说)。构建您建议的 dict(作为 collections.Counter 最快)当然要慢得多(需要一个 all 计数都为 1)。您还提到的所有值为 True 的 dict 是 set 的可笑、无用的臃肿模仿,没有任何附加值。 Big-O 并不是编程中的一切。 轶事,set.__contains__(4.42 微秒/256 次调用,2.55% 时间)比list.__contains__(173.00 微秒/256 次调用,100% 时间)快非常,所以以至于set.addlist.append 的边际开销是值得的,即使向集合中添加项目的速度是出了名的不那么快。在这里坚决使用集合而不是列表,因为我在几个小时后放弃的非常大的列表是在几秒钟内完成的集合。【参考方案3】:

我认为比较此处介绍的不同解决方案的时间安排会很有用。为此,我使用了我自己的库simple_benchmark

所以对于这种情况,Denis Otkidach 的解决方案确实是最快的。

一些方法也表现出更陡峭的曲线,这些方法是随元素数量成二次方的方法(Alex Martellis 第一个解决方案、wjandrea 和 Xavier Decorets 的两个解决方案)。另外值得一提的是,Keiku 的 pandas 解决方案有一个非常大的常数因子。但对于较大的列表,它几乎可以赶上其他解决方案。

如果重复项位于第一个位置。这有助于查看哪些解决方案正在短路:

这里有几种方法不会短路:Kaiku、Frank、Xavier_Decoret(第一个解决方案)、Turn、Alex Martelli(第一个解决方案)和 Denis Otkidach 提出的方法(在不重复的情况下最快)。

我在这里包含了我自己的库中的一个函数:iteration_utilities.all_distinct,它可以在无重复情况下与最快的解决方案竞争,并在开始重复的情况下以恒定时间执行(尽管不是最快的)。

基准测试代码:

from collections import Counter
from functools import reduce

import pandas as pd
from simple_benchmark import BenchmarkBuilder
from iteration_utilities import all_distinct

b = BenchmarkBuilder()

@b.add_function()
def Keiku(l):
    return pd.Series(l).duplicated().sum() > 0

@b.add_function()
def Frank(num_list):
    unique = []
    dupes = []
    for i in num_list:
        if i not in unique:
            unique.append(i)
        else:
            dupes.append(i)
    if len(dupes) != 0:
        return False
    else:
        return True

@b.add_function()
def wjandrea(iterable):
    seen = []
    for x in iterable:
        if x in seen:
            return True
        seen.append(x)
    return False

@b.add_function()
def user(iterable):
    clean_elements_set = set()
    clean_elements_set_add = clean_elements_set.add

    for possible_duplicate_element in iterable:

        if possible_duplicate_element in clean_elements_set:
            return True

        else:
            clean_elements_set_add( possible_duplicate_element )

    return False

@b.add_function()
def Turn(l):
    return Counter(l).most_common()[0][1] > 1

def getDupes(l):
    seen = set()
    seen_add = seen.add
    for x in l:
        if x in seen or seen_add(x):
            yield x

@b.add_function()          
def F1Rumors(l):
    try:
        if next(getDupes(l)): return True    # Found a dupe
    except StopIteration:
        pass
    return False

def decompose(a_list):
    return reduce(
        lambda u, o : (u[0].union([o]), u[1].union(u[0].intersection([o]))),
        a_list,
        (set(), set()))

@b.add_function()
def Xavier_Decoret_1(l):
    return not decompose(l)[1]

@b.add_function()
def Xavier_Decoret_2(l):
    try:
        def func(s, o):
            if o in s:
                raise Exception
            return s.union([o])
        reduce(func, l, set())
        return True
    except:
        return False

@b.add_function()
def pyrospade(xs):
    s = set()
    return any(x in s or s.add(x) for x in xs)

@b.add_function()
def Alex_Martelli_1(thelist):
    return any(thelist.count(x) > 1 for x in thelist)

@b.add_function()
def Alex_Martelli_2(thelist):
    seen = set()
    for x in thelist:
        if x in seen: return True
        seen.add(x)
    return False

@b.add_function()
def Denis_Otkidach(your_list):
    return len(your_list) != len(set(your_list))

@b.add_function()
def MSeifert04(l):
    return not all_distinct(l)

对于论点:


# No duplicate run
@b.add_arguments('list size')
def arguments():
    for exp in range(2, 14):
        size = 2**exp
        yield size, list(range(size))

# Duplicate at beginning run
@b.add_arguments('list size')
def arguments():
    for exp in range(2, 14):
        size = 2**exp
        yield size, [0, *list(range(size)]

# Running and plotting
r = b.run()
r.plot()

【讨论】:

供参考:all_distinct 函数为written in C。 很高兴看到最漂亮(又名 Pythonic)的解决方案也是最快的解决方案。你必须欣赏这个 Python 的特性。【参考方案4】:

这是旧的,但这里的答案使我得到了一个稍微不同的解决方案。如果您愿意滥用理解,则可以通过这种方式短路。

xs = [1, 2, 1]
s = set()
any(x in s or s.add(x) for x in xs)
# You can use a similar approach to actually retrieve the duplicates.
s = set()
duplicates = set(x for x in xs if x in s or s.add(x))

【讨论】:

【参考方案5】:

如果你喜欢函数式编程风格,这里有一个有用的函数,使用 doctest 进行自我记录和测试的代码。

def decompose(a_list):
    """Turns a list into a set of all elements and a set of duplicated elements.

    Returns a pair of sets. The first one contains elements
    that are found at least once in the list. The second one
    contains elements that appear more than once.

    >>> decompose([1,2,3,5,3,2,6])
    (set([1, 2, 3, 5, 6]), set([2, 3]))
    """
    return reduce(
        lambda (u, d), o : (u.union([o]), d.union(u.intersection([o]))),
        a_list,
        (set(), set()))

if __name__ == "__main__":
    import doctest
    doctest.testmod()

从那里您可以通过检查返回对的第二个元素是否为空来测试唯一性:

def is_set(l):
    """Test if there is no duplicate element in l.

    >>> is_set([1,2,3])
    True
    >>> is_set([1,2,1])
    False
    >>> is_set([])
    True
    """
    return not decompose(l)[1]

请注意,这效率不高,因为您正在显式构建分解。但是沿着使用 reduce 的思路,你可以想出一些等效的方法(但效率稍低)来回答 5:

def is_set(l):
    try:
        def func(s, o):
            if o in s:
                raise Exception
            return s.union([o])
        reduce(func, l, set())
        return True
    except:
        return False

【讨论】:

应该先阅读相关问题。这在***.com/questions/1723072/… 中有描述 它在 decompose() 的 lambda 函数上抛出“无效语法”错误 这是因为在 Python 3.x 中取消了 lambda 参数列表中的解包。【参考方案6】:

我最近使用生成器在列表中回答了establish all the duplicates 的相关问题。它的好处是,如果只是用来确定“是否有重复”,则只需获取第一项即可,其余的可以忽略,这是最终的捷径。

这是一个有趣的基于集合的方法,我直接从 moooeeeep 改编而来:

def getDupes(l):
    seen = set()
    seen_add = seen.add
    for x in l:
        if x in seen or seen_add(x):
            yield x

因此,完整的受骗列表为list(getDupes(etc))。为了简单地测试“如果”有一个骗子,它应该被包装如下:

def hasDupes(l):
    try:
        if getDupes(l).next(): return True    # Found a dupe
    except StopIteration:
        pass
    return False

这可以很好地扩展并在列表中的任何地方提供一致的操作时间——我测试了最多 1m 个条目的列表。如果您对数据有所了解,特别是上半场可能会出现骗子,或者其他让您扭曲要求的事情,例如需要获取实际的骗子,那么有几个真正替代的骗子定位器那可能会跑赢。我推荐的两个是...

简单的基于字典的方法,非常易读:

def getDupes(c):
    d = 
    for i in c:
        if i in d:
            if d[i]:
                yield i
                d[i] = False
        else:
            d[i] = True

利用排序列表上的 itertools(本质上是 ifilter/izip/tee),如果你得到所有的欺骗,虽然没有那么快得到第一个,但效率很高:

def getDupes(c):
    a, b = itertools.tee(sorted(c))
    next(b, None)
    r = None
    for k, g in itertools.ifilter(lambda x: x[0]==x[1], itertools.izip(a, b)):
        if k != r:
            yield k
            r = k

这些是我为full dupe list 尝试的方法中表现最好的方法,第一个欺骗发生在从开始到中间的 1m 元素列表中的任何位置。令人惊讶的是,排序步骤增加的开销如此之小。您的里程可能会有所不同,但这是我的具体计时结果:

Finding FIRST duplicate, single dupe places "n" elements in to 1m element array

Test set len change :        50 -  . . . . .  -- 0.002
Test in dict        :        50 -  . . . . .  -- 0.002
Test in set         :        50 -  . . . . .  -- 0.002
Test sort/adjacent  :        50 -  . . . . .  -- 0.023
Test sort/groupby   :        50 -  . . . . .  -- 0.026
Test sort/zip       :        50 -  . . . . .  -- 1.102
Test sort/izip      :        50 -  . . . . .  -- 0.035
Test sort/tee/izip  :        50 -  . . . . .  -- 0.024
Test moooeeeep      :        50 -  . . . . .  -- 0.001 *
Test iter*/sorted   :        50 -  . . . . .  -- 0.027

Test set len change :      5000 -  . . . . .  -- 0.017
Test in dict        :      5000 -  . . . . .  -- 0.003 *
Test in set         :      5000 -  . . . . .  -- 0.004
Test sort/adjacent  :      5000 -  . . . . .  -- 0.031
Test sort/groupby   :      5000 -  . . . . .  -- 0.035
Test sort/zip       :      5000 -  . . . . .  -- 1.080
Test sort/izip      :      5000 -  . . . . .  -- 0.043
Test sort/tee/izip  :      5000 -  . . . . .  -- 0.031
Test moooeeeep      :      5000 -  . . . . .  -- 0.003 *
Test iter*/sorted   :      5000 -  . . . . .  -- 0.031

Test set len change :     50000 -  . . . . .  -- 0.035
Test in dict        :     50000 -  . . . . .  -- 0.023
Test in set         :     50000 -  . . . . .  -- 0.023
Test sort/adjacent  :     50000 -  . . . . .  -- 0.036
Test sort/groupby   :     50000 -  . . . . .  -- 0.134
Test sort/zip       :     50000 -  . . . . .  -- 1.121
Test sort/izip      :     50000 -  . . . . .  -- 0.054
Test sort/tee/izip  :     50000 -  . . . . .  -- 0.045
Test moooeeeep      :     50000 -  . . . . .  -- 0.019 *
Test iter*/sorted   :     50000 -  . . . . .  -- 0.055

Test set len change :    500000 -  . . . . .  -- 0.249
Test in dict        :    500000 -  . . . . .  -- 0.145
Test in set         :    500000 -  . . . . .  -- 0.165
Test sort/adjacent  :    500000 -  . . . . .  -- 0.139
Test sort/groupby   :    500000 -  . . . . .  -- 1.138
Test sort/zip       :    500000 -  . . . . .  -- 1.159
Test sort/izip      :    500000 -  . . . . .  -- 0.126
Test sort/tee/izip  :    500000 -  . . . . .  -- 0.120 *
Test moooeeeep      :    500000 -  . . . . .  -- 0.131
Test iter*/sorted   :    500000 -  . . . . .  -- 0.157

【讨论】:

第二个代码块中的 .next() 调用在 Python 3.x 上不起作用。我认为next(getDupes(l)) 应该可以跨 Python 版本工作,所以改变它可能是有意义的。 另外ifilterìzip可以简单地替换为Python 3.x中内置的filterzip @MSeifert 该解决方案适用于 python 2.x,是的,对于 py3,您可以直接使用 filter 和 map ...但是在 py2 代码库中使用 py3 解决方案的人不会得到好处,因为它不会作为发电机运行。在这种情况下,显式优于隐式 ;)【参考方案7】:

另一种简洁的方法是使用Counter。

仅确定原始列表中是否有任何重复项:

from collections import Counter

def has_dupes(l):
    # second element of the tuple has number of repetitions
    return Counter(l).most_common()[0][1] > 1

或获取具有重复项的列表:

def get_dupes(l):
    return [k for k, v in Counter(l).items() if v > 1]

【讨论】:

【参考方案8】:
my_list = ['one', 'two', 'one']

duplicates = []

for value in my_list:
  if my_list.count(value) > 1:
    if value not in duplicates:
      duplicates.append(value)

print(duplicates) //["one"]

【讨论】:

【参考方案9】:

我发现这样做的性能最好,因为它在第一次复制时会短路操作,然后这个算法的时间和空间复杂度为 O(n),其中 n 是列表的长度:

def has_duplicated_elements(iterable):
    """ Given an `iterable`, return True if there are duplicated entries. """
    clean_elements_set = set()
    clean_elements_set_add = clean_elements_set.add

    for possible_duplicate_element in iterable:

        if possible_duplicate_element in clean_elements_set:
            return True

        else:
            clean_elements_set_add( possible_duplicate_element )

    return False

【讨论】:

【参考方案10】:

如果列表包含不可散列的项目,您可以使用Alex Martelli's solution,但使用列表而不是集合,虽然对于较大的输入来说速度较慢:O(N^2)。

def has_duplicates(iterable):
    seen = []
    for x in iterable:
        if x in seen:
            return True
        seen.append(x)
    return False

【讨论】:

现在回到这个问题,我不确定你为什么要使用它而不是 Alex 的其他解决方案 any(thelist.count(x) > 1 for x in thelist)。我看到的唯一优势是这适用于任何可迭代对象,而 Alex 仅适用于列表(或具有 .count() 方法的其他类型)。【参考方案11】:

我真的不知道 set 在幕后做了什么,所以我只想保持简单。

def dupes(num_list):
    unique = []
    dupes = []
    for i in num_list:
        if i not in unique:
            unique.append(i)
        else:
            dupes.append(i)
    if len(dupes) != 0:
        return False
    else:
        return True

【讨论】:

【参考方案12】:

一个更简单的解决方案如下。只需使用 pandas .duplicated() 方法检查真/假,然后求和。另请参阅pandas.Series.duplicated — pandas 0.24.1 documentation

import pandas as pd

def has_duplicated(l):
    return pd.Series(l).duplicated().sum() > 0

print(has_duplicated(['one', 'two', 'one']))
# True
print(has_duplicated(['one', 'two', 'three']))
# False

【讨论】:

【参考方案13】:

为了简单起见,我使用了 pyrospade 的方法,并在由不区分大小写的 Windows 注册表组成的简短列表中稍作修改。

如果将原始 PATH 值字符串拆分为单独的路径,则可以使用以下方法删除所有“空”路径(空或仅空格字符串):

PATH_nonulls = [s for s in PATH if s.strip()]

def HasDupes(aseq) :
    s = set()
    return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)

def GetDupes(aseq) :
    s = set()
    return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))

def DelDupes(aseq) :
    seen = set()
    return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]

出于测试目的,原始 PATH 既有“空”条目也有重复项:

[list]  Root paths in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH[list]  Root paths in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
  1  C:\Python37\
  2
  3
  4  C:\Python37\Scripts\
  5  c:\python37\
  6  C:\Program Files\ImageMagick-7.0.8-Q8
  7  C:\Program Files (x86)\poppler\bin
  8  D:\DATA\Sounds
  9  C:\Program Files (x86)\GnuWin32\bin
 10  C:\Program Files (x86)\Intel\iCLS Client\
 11  C:\Program Files\Intel\iCLS Client\
 12  D:\DATA\CCMD\FF
 13  D:\DATA\CCMD
 14  D:\DATA\UTIL
 15  C:\
 16  D:\DATA\UHELP
 17  %SystemRoot%\system32
 18
 19
 20  D:\DATA\CCMD\FF%SystemRoot%
 21  D:\DATA\Sounds
 22  %SystemRoot%\System32\Wbem
 23  D:\DATA\CCMD\FF
 24
 25
 26  c:\
 27  %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
 28

空路径已被删除,但仍有重复,例如 (1, 3) 和 (13, 20):

    [list]  Null paths removed from HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH
  1  C:\Python37\
  2  C:\Python37\Scripts\
  3  c:\python37\
  4  C:\Program Files\ImageMagick-7.0.8-Q8
  5  C:\Program Files (x86)\poppler\bin
  6  D:\DATA\Sounds
  7  C:\Program Files (x86)\GnuWin32\bin
  8  C:\Program Files (x86)\Intel\iCLS Client\
  9  C:\Program Files\Intel\iCLS Client\
 10  D:\DATA\CCMD\FF
 11  D:\DATA\CCMD
 12  D:\DATA\UTIL
 13  C:\
 14  D:\DATA\UHELP
 15  %SystemRoot%\system32
 16  D:\DATA\CCMD\FF%SystemRoot%
 17  D:\DATA\Sounds
 18  %SystemRoot%\System32\Wbem
 19  D:\DATA\CCMD\FF
 20  c:\
 21  %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\

最后,骗子已被删除:

[list]  Massaged path list from in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH
  1  C:\Python37\
  2  C:\Python37\Scripts\
  3  C:\Program Files\ImageMagick-7.0.8-Q8
  4  C:\Program Files (x86)\poppler\bin
  5  D:\DATA\Sounds
  6  C:\Program Files (x86)\GnuWin32\bin
  7  C:\Program Files (x86)\Intel\iCLS Client\
  8  C:\Program Files\Intel\iCLS Client\
  9  D:\DATA\CCMD\FF
 10  D:\DATA\CCMD
 11  D:\DATA\UTIL
 12  C:\
 13  D:\DATA\UHELP
 14  %SystemRoot%\system32
 15  D:\DATA\CCMD\FF%SystemRoot%
 16  %SystemRoot%\System32\Wbem
 17  %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\

【讨论】:

【参考方案14】:
def check_duplicates(my_list):
    seen = 
    for item in my_list:
        if seen.get(item):
            return True
        seen[item] = True
    return False

【讨论】:

该功能是如何工作的?我很好奇“看到”的字典是如何填充的。

以上是关于如何检查平面列表中是不是有重复项?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查特定整数是不是在列表中

如何使用重复项编辑列表项

检查一个列表是不是是另一个与重复项一起使用的列表的轮换

反应原生平面列表如何强制列表项具有相同的高度?

Flutter:如何检查列表是不是包含通过项?

如何使用 SSIS 从平面文件中删除重复的行?