web-dev-qa-db-ja.com

順序を維持しながら、リストから重複をどのように削除しますか?

順序を保持しながら、Pythonのリストから重複を削除する組み込みはありますか?重複を削除するためにセットを使用できることを私は知っていますが、それは元の順序を破壊します。私もこのように自分自身をロールバックできることを知っています:

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

unwind のおかげで コードサンプル

しかし、可能であれば、組み込みの、またはもっとPythonicの慣用句を使用したいと思います。

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

685
Josh Glover

ここでは、いくつかの選択肢があります: http://www.peterbe.com/plog/uniqifiers-benchmark

最速のもの:

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

単にseen.addを呼び出すのではなく、seen_addseen.addに割り当てるのはなぜですか? Pythonは動的言語であり、繰り返しごとにseen.addを解決することは、ローカル変数を解決するよりもコストがかかります。 seen.addは繰り返しの間に変更されるかもしれません、そしてランタイムはそれを排除するほど賢くありません。安全にプレイするには、毎回オブジェクトをチェックする必要があります。

同じデータセットでこの関数を多用することを計画している場合は、おそらく順序付けられたセットを使用したほうがよいでしょう: http://code.activestate.com/recipes/528878/ /

_ o _ (1)操作ごとの挿入、削除、およびメンバーチェック。

(ちょっとしたメモ:seen.add()は常にNoneを返すので、上記の または は集合の更新を試みるための手段としてのみ存在し、論理テストの不可欠な部分としては存在しません。)

689
Markus Jarderot

編集2016

Raymond 指摘されているOrderedDictがCで実装されているpython 3.5+では、リスト理解アプローチはOrderedDictより遅くなります(実際に最後にリストが必要な場合を除いて)。ショート)。だから3.5+のための最善の解決策はOrderedDictです。

重要な編集2015

@abarnert メモとして、 more_itertools library(pip install more_itertools)には、 unique_everseenunreadablenot seen.add))mutationin)なしでこの問題を解決するための関数が含まれていますこれも最速の解決策です:

>>> from  more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

ライブラリを1つインポートするだけで、ハックはありません。これはitertoolsレシピ unique_everseen の実装によるものです。

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 filterfalse(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

Pythonでは2.7+ 一般的な慣用句 (これは動作しますが速度のために最適化されていません、私は今 unique_everseen )を使用します collections.OrderedDict

ランタイム:O(N)

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

これは以下よりずっと良さそうです。

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

そして醜いハック:を利用しない

not seen.add(x)

これは、set.addが常にNoneを返すインプレースメソッドであるため、not NoneTrueに評価されます。

ただし、ハックソリューションは実行時の複雑度O(N)が同じでも、生の速度が速いことに注意してください。

317
jamylak

Python 2.7 では、イテラブルから元の順序で複製を削除する新しい方法は、次のとおりです。

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

Python 3.5 では、OrderedDictはCを実装しています。私のタイミングでは、これがPython 3.5のさまざまなアプローチの中で最速でも最短でもあることを示しています。

Python 3.6 では、通常の辞書は規則正しくコンパクトになりました。 (この機能はCPythonとPyPyには有効ですが、他の実装には存在しないかもしれません)。これにより、順序を維持しながら、最短の重複排除の新しい方法が得られます。

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

Python 3.7 では、通常の辞書はすべての実装にわたって順序付けられていることが保証されています。 だから、最短かつ最速の解決策は次のとおりです。

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

@maxへの応答:一度3.6または3.7に移動して OrderedDict の代わりに通常の辞書を使用すると、他の方法でパフォーマンスを上回ることはできません。辞書は密集していて、ほとんどオーバーヘッドなしですぐにリストに変換されます。ターゲットリストは、リスト内包表記で発生するすべてのサイズ変更を保存するlen(d)にあらかじめ設定されています。また、内部キーリストは密集しているので、ポインタのコピーはリストコピーとほぼ同じ速さです。

82
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

ユニーク→['1', '2', '3', '6', '4', '5']

38
dansalmo
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

リストは sorted である必要すらなく、十分な条件は等しい値がまとめられていることです。

編集:私は「順序を保存する」とはリストが実際に順序付けされていることを意味すると仮定した。そうでない場合は、MizardXのソリューションが正しい方法です。

コミュニティ編集:これは「重複する連続した要素を1つの要素に圧縮する」最もエレガントな方法です。

23
Rafał Dowgird

死んだ馬を蹴らないこと(この質問は非常に古く、すでに多くの良い答えがあります)が、ここにパンダを使った解決策があります。

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]
20
Alexander

秩序を守りたいのなら、

あなたはこれを試すことができます:

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

または同様にこれを行うことができます:

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

またこれをすることができます:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

これは次のように書くこともできます。

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 
18
shamrock

もう一つの非常に古い質問に対するもう一つの非常に遅い答えのために:

itertoolsレシピseen setテクニックを使ってこれを行う関数がありますが、

  • 標準のkey関数を処理します。
  • 悪意のないハッキングを使用しません。
  • N回調べるのではなく、seen.addを事前にバインドしてループを最適化します。 (f7もこれを行いますが、バージョンによってはそうではありません。)
  • ifilterfalseを使用してループを最適化します。したがって、すべての要素ではなく、Pythonの固有の要素をループするだけで済みます。 (もちろん、ifilterfalseの中でそれらすべてを繰り返しますが、これはC言語で行われており、はるかに高速です。)

それは実際にf7より速いですか?それはあなたのデータに依存するので、あなたはそれをテストして見なければならないでしょう。最後にリストが欲しいなら、f7はlistcompを使います、そしてここでそれをする方法はありません。 (appendingの代わりに直接yieldを使うことも、list関数にジェネレータを入れることもできますが、どちらもlistcomp内でLIST_APPENDほど速くすることはできません)。装飾するときにDSUを必要としない、わかりやすく、再利用可能な、すでに書かれた関数を持つのと同じくらい重要です。

他のレシピと同様に、 more-iterools にもあります。

No -keyの場合だけを望む場合は、次のように単純化できます。

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element
11
abarnert

外部モジュールからそのような機能の別の(非常に高性能な)実装を追加するためだけに1iteration_utilities.unique_everseen

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

タイミング

私はいくつかのタイミング(Python 3.6)をしました、そしてこれらは私がテストした他のすべての選択肢より速いことを示します、OrderedDict.fromkeysf7およびmore_itertools.unique_everseen

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

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

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

enter image description here

そして、それが違いを生むかどうかをチェックするためだけに、より多くの重複テストも行ったことを確認するためだけに:

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

enter image description here

そして1つだけ値を含むもの:

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

enter image description here

これらすべての場合において、iteration_utilities.unique_everseen関数が(私のコンピューターでは)最も速いです。


このiteration_utilities.unique_everseen関数は、入力内の扱いにくい値を処理することもできます(ただし、値がハッシュ可能な場合は、O(n*n)パフォーマンスの代わりにO(n)パフォーマンスを使用します)。

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1 免責事項:私はそのパッケージの作者です。

10
MSeifert

Python 3.7 以上では、辞書はキーの挿入順序を記憶するために 保証された です。 this の質問に対する答えは、現在の状況を要約したものです。

OrderedDictソリューションは時代遅れになり、import文がなくても簡単に発行できます。

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]
9
timgeb

MizardXのに基づく、ハッシュ可能な型(リストのリストなど)がない場合

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]
6
zmk

5倍速くバリアントを減らすがより洗練された

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

説明:

default = (list(), set())
# use list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]
3

リストに対するHaskellのnub関数を定義するのに使われた再帰的な考えを借りれば、これは再帰的なアプローチになるでしょう:

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

例えば。:

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

私はデータサイズを大きくするためにそれを試してみて、時間的な複雑さが劣っていることを確認しました(決定的ではありませんが、これは通常のデータには問題ないはずです)。

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

私はまたこれが他の操作によって独自性に容易に一般化されることができることは興味深いと思う。このような:

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

たとえば、次のように、一意性を保つために「等価」である場合と同じ整数に丸めるという概念を使用する関数を渡すことができます。

def test_round(x,y):
    return round(x) != round(y)

次にunique(some_list、test_round)はリストのユニークな要素を提供します。ここでユニークさは伝統的な同等性を意味するものではなく(セットベースまたは辞書キーベースのアプローチをこの問題に使用することによって意味される)要素が丸める可能性がある整数Kのそれぞれについて、Kに丸められる最初の要素のみ。例えば、

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]
3
ely

MizardXの答えは複数のアプローチの良いコレクションを与えます。

これは私が声を出して考えながら思いついたものです:

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]
2
Saurabh Hirani

リスト内包表記は、シンボル '_ [1]'によって作成されているので、参照することができます。
たとえば、次の関数は、リスト内包表記を参照することによって、要素の順序を変更せずに要素のリストをunique-ifします。

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

デモ:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

出力:

[1, 2, 3, 4, 5]
2
Zhifeng Hu

あなたは一種の醜いリスト内包ハッキングをすることができます。

[l[i] for i in range(len(l)) if l.index(l[i]) == i]
1
user1969453

_sorted_numpy配列を使った比較的効果的なアプローチ:

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in Zip(b[1:], b[:-1]) if x[0]!=x[1]]])

出力:

array([ 1,  3,  8, 12])
1
dominecf

単純な再帰的な解決策:

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]
1
Ilya Prokin
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

セットのO(1)ルックアップを使用して、新しいリストに要素を含めるかどうかを決定するジェネレータ式。

1
kylie.a

日常的に pandas を使用していて、パフォーマンスよりも美観が優先される場合は、組み込み関数pandas.Series.drop_duplicatesを検討してください。

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

タイミング:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop
0
Lei

インポートされたモジュールやセットを使わない解決策

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

出力を与えます:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']
0
Rob Murray

ライナーが1つ必要な場合は、おそらくこれが役立ちます。

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

...うまくいくはずですが、間違っている場合は修正してください

0
code22

シーケンス内の重複する値を削除しますが、残りの項目の順序は保持します。汎用ジェネレータ機能の使用.

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
0
Srivastava

これは順番を保存し、O(n)時間で実行されます。基本的には、重複が見つかった場所に穴を開け、それを下に流し込むというものです。読み書きポインタを利用します。重複が見つかったときはいつでも読み取りポインタだけが進み、書き込みポインタはそれを上書きするために重複エントリの上に留まります。

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]
0
Soham Joshi

zmkのアプローチは、リストの内包表記を使用します。これは非常に高速ですが、順序を自然に保ちます。大文字と小文字を区別する文字列に適用する場合、簡単に変更できます。これにより、元のケースも保持されます。

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

密接に関連する機能は次のとおりです。

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())))
0
Hewey Dewey

インプレース方式

この方法は二次的です。なぜなら、リストのすべての要素についてリストを線形検索するからです(そのため、delsのためにリストを並べ替えるためのコストが追加されます)。

そうは言っても、リストの最後から始めて、左側のサブリストにある各用語を削除してOriginに向かって進めば、その場で操作することは可能です。

このコードの考え方は、単純に

for i in range(len(l)-1,0,-1): 
    if l[i] in l[:i]: del l[i] 

実装の簡単なテスト

In [91]: from random import randint, seed                                                                                            
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
In [93]: for i in range(len(l)-1,0,-1): 
    ...:     print(l) 
    ...:     print(i, l[i], l[:i], end='') 
    ...:     if l[i] in l[:i]: 
    ...:          print( ': remove', l[i]) 
    ...:          del l[i] 
    ...:     else: 
    ...:          print() 
    ...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]

In [94]:                                                                                                                             
0
gboffi