回答:
set()
すべての値がハッシュ可能である場合、重複を削除するために使用します。
>>> your_list = ['one', 'two', 'one']
>>> len(your_list) != len(set(your_list))
True
短いリストのみに推奨:
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乗)までであり、それについて何も実行できません:-(。
all
on countsが1であることを必要とします)。すべての値がTrueであるディクショナリも、あなたの言うとおり、のばかげて無駄に肥大化した模倣でありset
、付加価値はありません。Big-Oはプログラミングのすべてではありません。
関数型プログラミングスタイルが好きな場合は、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
ここで紹介するさまざまなソリューションのタイミングを比較することは有用だと思いました。このために私は自分のライブラリを使用しました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()
私は最近、ジェネレーターを使用して、リスト内のすべての重複を確立するために関連する質問に回答しました。「重複がある場合」を確立するためだけに使用する場合、最初の項目を取得するだけで、残りを無視できるという利点があります。これは、究極のショートカットです。
これは、私が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のバージョン間で動作するはずです。
ifilter
とìzip
単純にビルトインで置き換えることができるfilter
とzip
のPython 3.xで
これを簡潔に行う別の方法は、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]
最初の複製が見つかったときに操作を短絡するため、これが最高のパフォーマンスを発揮することがわかりました。このアルゴリズムには時間と空間の複雑さ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
より簡単な解決策は次のとおりです。ただ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
リストにハッシュ化できないアイテムが含まれている場合は、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
私は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\
def check_duplicates(my_list):
seen = {}
for item in my_list:
if seen.get(item):
return True
seen[item] = True
return False