web-dev-qa-db-ja.com

Pythonで、リストから重複を削除してすべての要素が*順序を保持*しながら一意になるようにするための最速のアルゴリズムは何ですか?

例えば:

>>> x = [1, 1, 2, 'a', 'a', 3]
>>> unique(x)
[1, 2, 'a', 3]

リスト要素はハッシュ可能であると想定します。

説明:結果はリストの最初の重複を保持する必要があります。たとえば、[1、2、3、2、3、1]は[1、2、3]になります。

42
J Miller
def unique(items):
    found = set([])
    keep = []

    for item in items:
        if item not in found:
            found.add(item)
            keep.append(item)

    return keep

print unique([1, 1, 2, 'a', 'a', 3])
31
Terhorst

使用:

lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5]

そして、timeitモジュールを使用します:

$ python -m timeit -s 'import uniquetest' 'uniquetest.etchasketch(uniquetest.lst)'

他のさまざまな機能(ポスターにちなんで名付けました)についても同様です(第1世代Intel MacBook Proで)次の結果が得られました。

Allen:                  14.6 µs per loop [1]
Terhorst:               26.6 µs per loop
Tarle:                  44.7 µs per loop
ctcherry:               44.8 µs per loop
Etchasketch 1 (short):  64.6 µs per loop
Schinckel:              65.0 µs per loop
Etchasketch 2:          71.6 µs per loop
Little:                 89.4 µs per loop
Tyler:                 179.0 µs per loop

[1]アレンがリストを適切に変更していることに注意してください。timeitモジュールがコードを100000回実行し、そのうちの99999が重複のないリストに含まれているため、これは時間を歪めたと思います。


概要:セットを使用した単純明快な実装は、紛らわしいワンライナーよりも優れています:-)

18
John Fouhy

これがこれまでの最速のソリューションです(次の入力の場合)。

def del_dups(seq):
    seen = {}
    pos = 0
    for item in seq:
        if item not in seen:
            seen[item] = True
            seq[pos] = item
            pos += 1
    del seq[pos:]

lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 
       13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 
       5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 
       9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5]
del_dups(lst)
print(lst)
# -> [8, 9, 7, 15, 2, 20, 13, 24, 6, 11, 12, 4, 10, 18, 23, 3, 5, 22, 19, 14, 
#     21, 1, 0, 16, 17]

辞書の検索は、セットのPython 3。

14
jfs

何が最も速くなるかは、リストの何パーセントが重複しているかによって異なります。重複がほとんどあり、一意のアイテムがほとんどない場合は、新しいリストを作成するほうがおそらく高速です。それがほとんど一意のアイテムである場合、元のリスト(またはコピー)からそれらを削除する方が速くなります。

以下は、リストを適切に変更するためのものです。

def unique(items):
  seen = set()
  for i in xrange(len(items)-1, -1, -1):
    it = items[i]
    if it in seen:
      del items[i]
    else:
      seen.add(it)

インデックスを逆方向に反復すると、項目を削除しても繰り返しには影響しません。

13
Allen

これは私が見つけた最速のインプレースメソッドです(重複の大部分を想定しています)。

def unique(l):
    s = set(); n = 0
    for x in l:
        if x not in s: s.add(x); l[n] = x; n += 1
    del l[n:]

これは、ベースとなっているアレンの実装より10%高速です(timeit.repeat、psycoによってコンパイルされたJITで計時)。重複の最初のインスタンスを保持します。

repton-infinity:私のタイミングを確認できたら興味があります。

9
James Hopkin

必須のジェネレーターベースのバリエーション:

def unique(seq):
  seen = set()
  for x in seq:
    if x not in seen:
      seen.add(x)
      yield x
7
Constantin

これが最も簡単な方法です。

list(OrderedDict.fromkeys(iterable))

Python 3.5以降)、OrderedDictはCで実装されたため、これが最も短く、最もクリーンで、最も高速になりました。

7

一発ギャグ:

new_list = reduce(lambda x,y: x+[y][:1-int(y in x)], my_list, [])
5
Tyler

このためのインプレースワンライナー:

>>> x = [1, 1, 2, 'a', 'a', 3]
>>> [ item for pos,item in enumerate(x) if x.index(item)==pos ]
[1, 2, 'a', 3]
4
Mario Ruggier

http://www.peterbe.com/plog/uniqifiers-benchmark から取得

def f5(seq, idfun=None):  
    # order preserving 
    if idfun is None: 
        def idfun(x): return x 
    seen = {} 
    result = [] 
    for item in seq: 
        marker = idfun(item) 
        # in old Python versions: 
        # if seen.has_key(marker) 
        # but in new ones: 
        if marker in seen: continue 
        seen[marker] = 1 
        result.append(item) 
    return result
4
ctcherry

これは最速のものであり、これからのすべてのものを比較します 長い議論 とここで与えられた他の回答、これを参照して ベンチマーク を比較します。これは、ディスカッションの最速関数f8よりも25%高速です。アイデアを提供してくれたDavid Kirbyに感謝します。

def uniquify(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if x not in seen and not seen_add(x)]

いくつかの時間の比較:

$ python uniqifiers_benchmark.py 
* f8_original 3.76
* uniquify 3.0
* terhorst 5.44
* terhorst_localref 4.08
* del_dups 4.76
4
Michael

Pythonでこれを解決するために実際に何かクールなことをすることができます。構築されているときにそれ自体を参照するリスト内包を作成することができます。以下のように:

   # remove duplicates...
   def unique(my_list):
       return [x for x in my_list if x not in locals()['_[1]'].__self__]

編集:「自分」を削除し、Mac OS Xで動作します、Python 2.5.1。

_ [1]は、新しいリストへのPythonの「秘密の」参照です。もちろん、上記は少し面倒ですが、必要に応じてニーズに合わせて調整できます。たとえば、内包表記への参照を返す関数を実際に作成できます。それは次のようになります:

return [x for x in my_list if x not in this_list()]

3
Jake

重複は必ず最初にリストに含まれている必要がありますか?要素を調べる限りオーバーヘッドはありませんが、要素を追加する際のオーバーヘッドはもう少しあります(ただし、オーバーヘッドはO(1))にする必要があります)。

>>> x  = []
>>> y = set()
>>> def add_to_x(val):
...     if val not in y:
...             x.append(val)
...             y.add(val)
...     print x
...     print y
... 
>>> add_to_x(1)
[1]
set([1])
>>> add_to_x(1)
[1]
set([1])
>>> add_to_x(1)
[1]
set([1])
>>> 
2
Jason Baker

重複を削除して順序を維持する:

これは、リスト内包表記や辞書の組み込み機能を活用する高速な2行です。

x = [1, 1, 2, 'a', 'a', 3]

tmpUniq = {} # temp variable used below 
results = [tmpUniq.setdefault(i,i) for i in x if i not in tmpUniq]

print results
[1, 2, 'a', 3]

Dict.setdefaults()関数は、値を返すだけでなく、リスト内包で直接temp dictに追加します。組み込み関数とdictのハッシュを使用すると、プロセスの効率を最大化できます。

2
Scot

has_key in python is O(1)です。ハッシュからの挿入と取得もO(1)です。n個のアイテムを2回ループするため、O(n)です。

def unique(list):
  s = {}
  output = []
  for x in list:
    count = 1
    if(s.has_key(x)):
      count = s[x] + 1

    s[x] = count
  for x in list:
    count = s[x]
    if(count > 0):
      s[x] = 0
      output.append(x)
  return output
1
etchasketch

ここには、いくつかの優れた効率的なソリューションがあります。ただし、絶対に最も効率的なO(n)ソリューションに関心がない場合は、簡単な1行のO(n^2*log(n))ソリューションを使用します。

_def unique(xs):
    return sorted(set(xs), key=lambda x: xs.index(x))
_

または、より効率的な2行のO(n*log(n))ソリューション:

_def unique(xs):
    positions = dict((e,pos) for pos,e in reversed(list(enumerate(xs))))
    return sorted(set(xs), key=lambda x: positions[x])
_
1
Eli Courtwright

Dictがハッシュの場合はO(n)、O(nlogn) dictがツリーの場合は単純、修正。提案についてはMatthewに感謝します。申し訳ありませんが、基になる型がわかりません。

def unique(x):    
  output = []
  y = {}
  for item in x:
    y[item] = ""

  for item in x:
    if item in y:
      output.append(item)

  return output
1
Wesley Tarle

itertoolsドキュメントからの2つのレシピは次のとおりです。

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in ifilterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

def unique_justseen(iterable, key=None):
    "List unique elements, preserving order. Remember only the element just seen."
    # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
    # unique_justseen('ABBCcAD', str.lower) --> A B C A D
    return imap(next, imap(itemgetter(1), groupby(iterable, key)))
1
x = [] # Your list  of items that includes Duplicates

# Assuming that your list contains items of only immutable data types

dict_x = {} 

dict_x = {item : item for i, item in enumerate(x) if item not in dict_x.keys()}
# Average t.c. = O(n)* O(1) ; furthermore the dict comphrehension and generator like behaviour of enumerate adds a certain efficiency and Pythonic feel to it.

x = dict_x.keys() # if you want your output in list format 
0
BigDataGuy

私はpythonの経験はありませんが、アルゴリズムはリストを並べ替え、重複を削除し(リスト内の前のアイテムと比較して)、最後に古いリストと比較して新しいリスト内の位置を見つけることです。

より長い答え: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/5256

0
solinent
>>> def unique(list):
...   y = []
...   for x in list:
...     if x not in y:
...       y.append(x)
...   return y
0
etchasketch

Terhostの回答でset()の呼び出しから空のリストを取り出すと、速度が少し向上します。

変更:found = set([])
to:found = set()

ただし、セットはまったく必要ありません。

def unique(items):
    keep = []

    for item in items:
        if item not in keep:
            keep.append(item)

    return keep

Timeitを使用して、次の結果を得ました。

set([])を使用-4.97210427363
with set()-4.65712377445
セットなし-3.44865284975

0
user18695