フラットリストに重複があるかどうかを確認するにはどうすればよいですか?


185

たとえば、リストが指定された['one', 'two', 'one']場合、アルゴリズムはを返しますTrueが、指定された['one', 'two', 'three']場合はを返しFalseます。

回答:


398

set()すべての値がハッシュ可能である場合、重複を削除するために使用します。

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

17
これを読む前に、your_list!= list(set(your_list))を試しましたが、要素の順序が変わるため機能しません。lenを使用することは、この問題を解決する良い方法です
igniteflow 2012年

1
多くの場合、浮動points.Seeの配列のために動作しませんstackoverflow.com/questions/60914705
マナスDogra

54

短いリストのみに推奨:

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

長いリストに使用しないでください。リストの項目数の2乗に比例して時間がかかる場合があります。

ハッシュ可能なアイテム(文字列、数値、&c)を含む長いリストの場合:

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の2乗)までであり、それについて何も実行できません:-(。


21
Denis Otkidachは、リストから新しいセットを作成し、その長さを確認するソリューションを提供しました。その利点は、Python内のCコードが面倒な作業を実行できることです。あなたの解決策はPythonコードでループしますが、単一の一致が見つかったときに短絡するという利点があります。リストに重複がない可能性が高い場合は、Denis Otkidachのバージョンが好きですが、リストの早い段階で重複がある可能性が高い場合は、このソリューションの方が適しています。
steveha 2009年

1
Denisにはよりきちんとした解決策があったと思いますが、詳細に値する価値があります。
Steve314、2009年

@steveha-時期尚早の最適化?
Steve314、2009年

@ Steve314、どの時期尚早の最適化?私はデニス・オキダッハがそれを書いた方法でそれを書いたでしょう、それで私は(Pythonクックブックの名声の)アレックス・マルテリがなぜそれを違うように書いたのか理解しようとしていました。それについて少し考えた後、アレックスのバージョンが短絡していることに気づき、その違いについていくつかの考えを投稿しました。違いの議論から、すべての悪の根源である時期尚早の最適化にどのように行きますか?
steveha 2009年

3
アイテムがハッシュ可能である場合、セットソリューションはより直接的であり、そして私がそれを表現した方法はより速くなります(答えがわかるとすぐに終了します-「短絡」、stevehaはそれを置きます)。あなたが提案するdictsの構築(collections.Counterとして最速)はもちろんはるかに遅いです(allon countsが1であることを必要とします)。すべての値がTrueであるディクショナリも、あなたの言うとおり、のばかげて無駄に肥大化した模倣でありset、付加価値はありません。Big-Oはプログラミングのすべてではありません。
Alex Martelli 2016

12

これは古いですが、ここでの答えは私に少し異なる解決策を導きました。理解を乱用している場合は、この方法で短絡する可能性があります。

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

9

関数型プログラミングスタイルが好きな場合は、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()

そこから、返されたペアの2番目の要素が空であるかどうかを確認することで、unicityをテストできます。

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

最初に関連する質問を読んでおく必要があります。これは、stackoverflow.com
questions / 1723072 /…で

1
decompose()のラムダ関数で「無効な構文」エラーが発生する
raffaem

これは、ラムダ引数リストでのアンパックがPython 3.xで削除されたためです。
MSeifert

5

ここで紹介するさまざまなソリューションのタイミングを比較することは有用だと思いました。このために私は自分のライブラリを使用しましたsimple_benchmark

ここに画像の説明を入力してください

実際、この場合、Denis Otkidachのソリューションが最速です。

いくつかのアプローチは、より急な曲線も示します。これらは、要素の数で二次的にスケーリングするアプローチです(Alex Martellisの最初のソリューション、wjandreaおよび両方のXavier Decoretsソリューション)。特筆すべきは、Keikuのパンダソリューションには非常に大きな一定の要素があるということです。しかし、より大きなリストの場合、他のソリューションにほぼ追いつきます。

そして、複製が最初の位置にある場合。これは、どのソリューションが短絡しているかを確認するのに役立ちます。

ここに画像の説明を入力してください

ここでいくつかのアプローチは短絡しません: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関数はC記述されています。
ユーザー

5

私は最近、ジェネレーターを使用して、リスト内のすべての重複確立するために関連する質問に回答しました。「重複がある場合」を確立するためだけに使用する場合、最初の項目を取得するだけで、残りを無視できるという利点があります。これは、究極のショートカットです。

これは、私が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

これにより、拡張性が高くなり、重複がリストのどこにある場合でも一貫した動作時間が得られます。私は、最大100万のエントリのリストでテストしました。データについて何か知っている場合、具体的には、前半に重複が現れる可能性が高い、または実際の重複を取得する必要があるなど、要件をゆがめる可能性のある他の事項には、いくつかの本当に代替の重複ロケーターがあります。パフォーマンスが向上する可能性があります。私がお勧めする2つは...

シンプルなdictベースのアプローチ、非常に読みやすい:

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)を活用します。最初の1つだけを取得するのはそれほど速くはありませんが、すべての重複を取得している場合は非常に効率的です。

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

これらは、私が完全なデュープリストに対して試みたアプローチの中で最高のパフォーマンスを発揮し、最初のデュープは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()2番目のコードブロックの呼び出しは、Python 3.xでは機能しません。私が思うにnext(getDupes(l))、それはそれを変更しても意味がありので、Pythonのバージョン間で動作するはずです。
MSeifert

また、ifilterìzip単純にビルトインで置き換えることができるfilterzipのPython 3.xで
MSeifert

@MSeifertソリューションは記述されたとおりにpython 2.xで機能し、そうです、py3の場合、フィルターを使用して直接マップできます...しかし、py2コードベースでpy3ソリューションを使用している人は、それが発生器。この場合、明示的の方が暗黙的よりも優れています;)
F1Rumors

3

これを簡潔に行う別の方法は、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]

2
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"]

1

最初の複製が見つかったときに操作を短絡するため、これが最高のパフォーマンスを発揮することがわかりました。このアルゴリズムには時間と空間の複雑さ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

0

セットが舞台裏で何をするのか本当にわからないので、単純にしておくだけです。

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

0

より簡単な解決策は次のとおりです。ただpandas .duplicated()メソッドでTrue / Falseをチェックして、合計を取ってください。pandas.Series.duplicated — pandas 0.24.1ドキュメントも参照してください

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

0

リストにハッシュ化できないアイテムが含まれている場合は、Alex Martelliのソリューションを使用できますが、セットではなくリストを使用できますが、入力が大きい場合は遅くなります(O(N ^ 2))。

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

0

私はpyrospadeのアプローチを、その単純さのために使用し、大文字と小文字を区別しないWindowsレジストリから作成された短いリストで少し修正しました。

生のPATH値文字列が個々のパスに分割されている場合、次のコマンドを使用して、すべての「null」パス(空または空白のみの文字列)を削除できます。

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には、テストのために「null」エントリと重複の両方があります。

[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

Nullパスは削除されましたが、重複があります。たとえば、(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\

0
def check_duplicates(my_list):
    seen = {}
    for item in my_list:
        if seen.get(item):
            return True
        seen[item] = True
    return False

関数はどのように機能しますか?「見た」辞書がどのように入力されるかについて、私は興味があります。
mountainscaler
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.