web-dev-qa-db-ja.com

Pythonリストから最初のn個の一意の要素を取得する

要素を繰り返すことができるpythonリストがあります。

>>> a = [1,2,2,3,3,4,5,6]

リストから最初のn一意の要素を取得したい。したがって、この場合、最初の5つの一意の要素が必要な場合、次のようになります。

[1,2,3,4,5]

私は発電機を使用した解決策を考え出しました:

def iterate(itr, upper=5):

    count = 0
    for index, element in enumerate(itr):
        if index==0:
            count += 1
            yield element

        Elif element not in itr[:index] and count<upper:
            count += 1
            yield element

使用中で:

>>> i = iterate(a, 5)
>>> [e for e in i]
[1,2,3,4,5]

これが最も最適なソリューションであることに疑問があります。よりPythonicで効率的な方法で記述するために実装できる代替戦略はありますか?

46
xssChauhan

setを使用して、見たものを記憶し、seenが十分にあるときにジェネレーターから戻るようにします。

a = [1,2,2,3,3,4,5,6]

def get_unique_N(iterable, N):
    """Yields (in order) the first N unique elements of iterable. 
    Might yield less if data too short."""
    seen = set()
    for e in iterable:
        if e in seen:
            continue
        seen.add(e)
        yield e
        if len(seen) == N:
            return

k = get_unique_N([1,2,2,3,3,4,5,6], 4)
print(list(k))

出力:

[1,2,3,4]

PEP-479 によると、raise StopIterationではなく、ジェネレーターからreturnを使用する必要があります- @ khelwood@ iBug に感謝そのコメントのために-人は決して学びません。

3.6では非推奨の警告が表示され、3.7ではRuntimeErrorsが表示されます。 Transition Planraise StopIterationを使用している場合


Elif element not in itr[:index] and count<upper:を使用するソリューションは、O(k)ルックアップを使用します-kはスライスの長さです-セットを使用すると、これはO(1)ルックアップに減少しますが、セットは同様に保管してください。これは速度とメモリのトレードオフです-アプリケーション/データ依存性の方が優れています。

[1,2,3,4,4,4,4,5][1]*1000+[2]*1000+[3]*1000+[4]*1000+[5]*1000+[6]を検討してください:

6つの一意の場合(長いリスト):

  • O(1)+O(2)+...+O(5001)のルックアップがあります
  • 私の5001*O(1)ルックアップ+ set( {1,2,3,4,5,6})のメモリがあります
47
Patrick Artner

人気の itertoolsunique_everseenレシピ を適応させることができます:

def unique_everseen_limit(iterable, limit=5):
    seen = set()
    seen_add = seen.add
    for element in iterable:
        if element not in seen:
            seen_add(element)
            yield element
        if len(seen) == limit:
            break

a = [1,2,2,3,3,4,5,6]

res = list(unique_everseen_limit(a))  # [1, 2, 3, 4, 5]

または、@ Chris_Randsが提案するように、 itertools.islice を使用して、制限のないジェネレーターから一定数の値を抽出できます。

from itertools import islice

def unique_everseen(iterable):
    seen = set()
    seen_add = seen.add
    for element in iterable:
        if element not in seen:
            seen_add(element)
            yield element

res = list(islice(unique_everseen(a), 5))  # [1, 2, 3, 4, 5]

unique_everseenレシピは、サードパーティライブラリで more_itertools.unique_everseen または toolz.unique から入手できます。 、あなたは使用できる:

from itertools import islice
from more_itertools import unique_everseen
from toolz import unique

res = list(islice(unique_everseen(a), 5))  # [1, 2, 3, 4, 5]
res = list(islice(unique(a), 5))           # [1, 2, 3, 4, 5]
23
jpp

オブジェクトが hashableintsはhashable)の場合、 fromkeys method of collections.OrderedDict class (またはPython3.7単純なdict公式に 順序付けられたため、

from collections import OrderedDict


def nub(iterable):
    """Returns unique elements preserving order."""
    return OrderedDict.fromkeys(iterable).keys()

そして、iterateの実装は、

from itertools import islice


def iterate(itr, upper=5):
    return islice(nub(itr), upper)

または、常に出力としてlistが必要な場合

def iterate(itr, upper=5):
    return list(nub(itr))[:upper]

改善点

@Chris_Randsが述べたように、このソリューションはコレクション全体をウォークスルーし、他の人がすでにやったようにnubユーティリティを generator の形式で書くことでこれを改善できます:

def nub(iterable):
    seen = set()
    add_seen = seen.add
    for element in iterable:
        if element in seen:
            continue
        yield element
        add_seen(element)
9
Azat Ibrakov

itertools.takewhile()を使用したPythonのアプローチは次のとおりです。

In [95]: from itertools import takewhile

In [96]: seen = set()

In [97]: set(takewhile(lambda x: seen.add(x) or len(seen) <= 4, a))
Out[97]: {1, 2, 3, 4}
6
Kasrâmvd

OrderedDictを使用できます。または、Python 3.7以降、通常のdictを使用できます。これらは、挿入順序を保持するために実装されているためです。これはセットでは機能しないことに注意してください。

N = 3
a = [1, 2, 2, 3, 3, 3, 4]
d = {x: True for x in a}
list(d.keys())[:N]
6
Jindra Helcl

この質問には本当に驚くべき答えがあります。それは速くてコンパクトで素晴らしいです!ここにこのコードを入れている理由は、1マイクロ秒の時間の緩みを気にかけない場合や、単純なタスクを一度だけ解決するためにコードに追加のライブラリが必要な場合が多いと思うからです。

a = [1,2,2,3,3,4,5,6]
res = []
for x in a:
    if x not in res:  # yes, not optimal, but doesnt need additional dict
        res.append(x)
        if len(res) == 5:
            break
print(res)
5
grapes

指定

import itertools as it


a = [1, 2, 2, 3, 3, 4, 5, 6]

コード

単純なリストの理解(@cdlaneの答えに似ています)。

[k for k, _ in it.groupby(a)][:5]
# [1, 2, 3, 4, 5]

または、Python 3.6+で:

list(dict.fromkeys(a))[:5]
# [1, 2, 3, 4, 5]
4
pylang

setsorted+ keyとともに使用する

sorted(set(a), key=list(a).index)[:5]
Out[136]: [1, 2, 3, 4, 5]
4
WeNYoBen

要素が示されているように順序付けられていると仮定すると、これはitertoolsのgroupby関数を楽しむ機会です。

from itertools import groupby, islice

def first_unique(data, upper):
    return islice((key for (key, _) in groupby(data)), 0, upper)

a = [1, 2, 2, 3, 3, 4, 5, 6]

print(list(first_unique(a, 5)))

@ juanpa.arrivillagaごとにisliceの代わりにenumerateを使用するように更新されました。重複を追跡するためにsetも必要ありません。

4
cdlane

このようなものを使用してみませんか?

>>> a = [1, 2, 2, 3, 3, 4, 5, 6]
>>> list(set(a))[:5]
[1, 2, 3, 4, 5]
a = [1,2,2,3,3,4,5,6]

from collections import defaultdict
def function(lis,n):
    dic = defaultdict(int)

    sol=set()

    for i in lis:
            try:
                if dic[i]:
                    pass
                else:
                    sol.add(i)
                    dic[i]=1
                    if len(sol)>=n:
                        break
            except KeyError:
                pass

    return list(sol)

print(function(a,3))

出力

[1, 2, 3]
0
prashant rana

リストの例:

a = [1, 2, 2, 3, 3, 4, 5, 6]

関数は、リストから必要な一意のアイテムのすべてまたはカウントを返します

1番目の引数-動作するリスト、2番目の引数(オプション)-一意のアイテムの数(デフォルト-なし-すべての一意の要素が返されることを意味します)

def unique_elements(lst, number_of_elements=None):
    return list(dict.fromkeys(lst))[:number_of_elements]

以下に動作例を示します。リスト名は「a」であり、2つの一意の要素を取得する必要があります。

print(unique_elements(a, 2))

出力:

output

0
Quanti Monati