web-dev-qa-db-ja.com

python filter()と同等、2つの出力リスト(つまり、リストのパーティション)を取得

リストとフィルタリング機能があるとしましょう。のようなものを使用して

_>>> filter(lambda x: x > 10, [1,4,12,7,42])
[12, 42]
_

基準に一致する要素を取得できます。一致する要素の1つ、残りの要素の1つ、2つのリストを出力する関数を使用できますか? filter()関数を2回呼び出すこともできますが、それは少し醜いです:)

編集:要素の順序を保存する必要があり、同じ要素を複数回使用する場合があります。

55
F'x

これを試して:

def partition(pred, iterable):
    trues = []
    falses = []
    for item in iterable:
        if pred(item):
            trues.append(item)
        else:
            falses.append(item)
    return trues, falses

使用法:

>>> trues, falses = partition(lambda x: x > 10, [1,4,12,7,42])
>>> trues
[12, 42]
>>> falses
[1, 4, 7]

itertoolsレシピ にも実装の提案があります。

from itertools import filterfalse, tee

def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

レシピは、Python 3.xのドキュメントからのものです。Python 2.x filterfalseは、ifilterfalseと呼ばれます。

45
Mark Byers
>>> def partition(l, p):
...     return reduce(lambda x, y: (x[0]+[y], x[1]) if p(y) else (x[0], x[1]+[y]), l,  ([], []))
... 
>>> partition([1, 2, 3, 4, 5], lambda x: x < 3)
([1, 2], [3, 4, 5])

上記のコードの少し醜いが速いバージョン:

def partition(l, p):
    return reduce(lambda x, y: x[0].append(y) or x if p(y) else x[1].append(y) or x, l,  ([], []))

これは2番目の編集ですが、重要だと思います。

 def partition(l, p):
     return reduce(lambda x, y: x[not p(y)].append(y) or x, l, ([], []))

2番目と3番目は、反復する1つのアッパーと同じくらい高速ですが、コードは少なくなります。

21
Mariy

ここでgroupbyの方が関連性が高いと思います:

http://docs.python.org/library/itertools.html#itertools.groupby

たとえば、リストを奇数と偶数に分割する(または任意の数のグループにすることができます):

>>> l=range(6)
>>> key=lambda x: x % 2 == 0
>>> from itertools import groupby
>>> {k:list(g) for k,g in groupby(sorted(l,key=key),key=key)}
    {False: [1, 3, 5], True: [0, 2, 4]}
6

リストに重複する要素がない場合は、間違いなくsetを使用できます。

>>> a = [1,4,12,7,42]
>>> b = filter(lambda x: x > 10, [1,4,12,7,42])
>>> no_b = set(a) - set(b)
set([1, 4, 7])

または包括的なリストで行うことができます:

>>> no_b = [i for i in a if i not in b]

注:これは関数ではありませんが、最初のfitler()の結果を知るだけで、フィルター条件をあまり満たさなかった要素を推定できます。

3
mouad

TL; DR

受け入れられ、最も投票された回答 [1] by Mark Byers

_def partition(pred, iterable):
    trues = []
    falses = []
    for item in iterable:
        if pred(item):
            trues.append(item)
        else:
            falses.append(item)
    return trues, falses
_

最もシンプルで最速です。

さまざまなアプローチのベンチマーク

提案されたさまざまなアプローチは、大きく3つのカテゴリに分類できます。

  1. _lis.append_による単純なリスト操作、2タプルのリストを返す、
  2. _lis.append_関数のアプローチによって仲介され、2タプルのリストを返します。
  3. itertoolsの詳細なドキュメントに記載されている標準的なレシピを使用して、大まかに言えば2タプルのジェネレータを返します。

ここでは、3つの手法のバニラ実装に従います。最初は機能的アプローチ、次にitertools、最終的には2つの異なる直接リスト操作の実装です。代わりにFalseを使用する方法はゼロ、Trueは1つのトリックです。

これはPython3であることに注意してください—したがって、reducefunctoolsから取得され、OPは_(positives, negatives)_のようなタプルを要求しますが、私の実装はすべて_(negatives, positives)_…を返します。

_$ ipython
Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 13:51:32) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import functools
   ...: 
   ...: def partition_fu(p, l, r=functools.reduce):
   ...:     return r(lambda x, y: x[p(y)].append(y) or x, l, ([], []))
   ...: 

In [2]: import itertools
   ...: 
   ...: def partition_it(pred, iterable,
   ...:               filterfalse=itertools.filterfalse,
   ...:               tee=itertools.tee):
   ...:     t1, t2 = tee(iterable)
   ...:     return filterfalse(pred, t1), filter(pred, t2)
   ...: 

In [3]: def partition_li(p, l):
   ...:     a, b = [], []
   ...:     for n in l:
   ...:         if p(n):
   ...:             b.append(n)
   ...:         else:
   ...:             a.append(n)
   ...:     return a, b
   ...: 

In [4]: def partition_li_alt(p, l):
   ...:     x = [], []
   ...:     for n in l: x[p(n)].append(n)
   ...:     return x
   ...: 
_

リストと操作対象のリスト(ここでも大まかに言えば)に適用する述語が必要です。

_In [5]: p = lambda n:n%2

In [6]: five, ten = range(50000), range(100000)
_

itertoolsアプローチをテストする際の問題を克服するために、2013年10月31日6:17に joeln によって報告されました。

ナンセンス。 filterfalseおよびfilterでジェネレーターを構築するのにかかる時間を計算しましたが、入力を反復したことも、predを一度呼び出したこともありません。 itertoolsレシピの利点は、リストを具体化しないか、必要以上に入力を先読みしないことです。 predを2倍の頻度で呼び出し、Byersの2倍近くの時間がかかります。

私は、異なるパーティション関数によって返された2つのイテラブルの要素のすべてのカップルをインスタンス化するだけのvoidループを考えました。

最初に、2つの固定リストを使用して、暗黙のオーバーロードを理解します(非常に便利なIPythonのマジック_%timeit_を使用)

_In [7]: %timeit for e, o in Zip(five, five): pass
4.21 ms ± 39.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
_

次に、さまざまな実装を次々に使用します

_In [8]: %timeit for e, o in Zip(*partition_fu(p, ten)): pass
53.9 ms ± 112 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit for e, o in Zip(*partition_it(p, ten)): pass
44.5 ms ± 3.84 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [10]: %timeit for e, o in Zip(*partition_li(p, ten)): pass
36.3 ms ± 101 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [11]: %timeit for e, o in Zip(*partition_li_alt(p, ten)): pass
37.3 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]:
_

コメント

最も単純なアプローチも最速です。

x[p(n)]トリックの使用は、えーと、役に立たない。すべてのステップでデータ構造にインデックスを付ける必要があるため、slightペナルティが与えられる—ただし、パイソン化で減少する文化の生存者を説得したいかどうかを知るのはいいことです。

代替のappend実装と同等のoperatively機能的なアプローチは、おそらく追加の(w/r(述語評価)の各リスト要素の関数呼び出し。

itertoolsアプローチには、(一般的な)利点があります。❶潜在的に大きなリストがインスタンス化されず、❷コンシューマーループから抜け出した場合、入力リストは完全には処理されませんが、適用する必要があるため、使用すると遅くなります。 teeの両端の述語

さておき

私は Marii で公開されたobject.mutate() or objectイディオムに恋に落ちました その答え 問題への機能的アプローチを示しています—恐れています遅かれ早かれ、私はそれを乱用するつもりです。

脚注

[1] 2017年9月14日現在、承認され、ほとんどの投票が行われました。もちろん、私はこの私の答えに最大の期待を寄せています!

3
gboffi

私はまさにこの要件を持っていました。 itertoolsのレシピは、データを2回別々にパスする必要があるため、あまり熱心ではありません。これが私の実装です:

def filter_twoway(test, data):
    "Like filter(), but returns the passes AND the fails as two separate lists"
    collected = {True: [], False: []}
    for datum in data:
        collected[test(datum)].append(datum)
    return (collected[True], collected[False])
2
Dan Stowell
from itertools import ifilterfalse

def filter2(predicate, iterable):
    return filter(predicate, iterable), list(ifilterfalse(predicate, iterable))
2
Tamás

Django.utils.functional.partition ソリューション:

def partition(predicate, values):
    """
    Splits the values into two sets, based on the return value of the function
    (True/False). e.g.:

        >>> partition(lambda x: x > 3, range(5))
        [0, 1, 2, 3], [4]
    """
    results = ([], [])
    for item in values:
        results[predicate(item)].append(item)
    return results

私の意見では、これはここで提示される最もエレガントなソリューションです。

この部分は文書化されていません。ソースコードのみが https://docs.djangoproject.com/en/dev/_modules/Django/utils/functional/ にあります。

2
vishes_shell

誰もが彼らのソリューションが最高だと思っているようですので、私はtimeitを使用してそれらすべてをテストすることにしました。私の述語関数として「def is_odd(x):return x&1」を使用し、反復可能関数として「xrange(1000)」を使用しました。これが私のPythonバージョンです:

Python 2.7.3 (v2.7.3:70274d53c1dd, Apr  9 2012, 20:52:43) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin

そして、これが私のテストの結果です:

Mark Byers
1000 loops, best of 3: 325 usec per loop

cldy
1000 loops, best of 3: 1.96 msec per loop

Dan S
1000 loops, best of 3: 412 usec per loop

TTimo
1000 loops, best of 3: 503 usec per loop

それらはすべて互いに同等です。ここで、Pythonのドキュメントに記載されている例を使用してみましょう。

import itertools

def partition(pred, iterable,
              # Optimized by replacing global lookups with local variables
              # defined as default values.
              filter=itertools.ifilter,
              filterfalse=itertools.ifilterfalse,
              tee=itertools.tee):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

これは少し速いようです。

100000 loops, best of 3: 2.58 usec per loop

Itertoolsのサンプルコードは、すべてのコーナーを少なくとも100倍上回っています。道徳は、車輪を再発明し続けないことです。

1
samwyse

ターゲットリストに追加するための簡潔なコード

    def partition(cond,inputList):
        a,b= [],[]
        for item in inputList:
            target = a if cond(item) else b
            target.append(item)
        return a, b


    >>> a, b= partition(lambda x: x > 10,[1,4,12,7,42])
    >>> a
    [12, 42]
    >>> b
    [1, 4, 7]
0
Saurabh

すでに良い答えがたくさんあります。私はこれを使いたいです:

def partition( pred, iterable ):
    def _dispatch( ret, v ):
        if ( pred( v ) ):
            ret[0].append( v )
        else:
            ret[1].append( v )
        return ret
    return reduce( _dispatch, iterable, ( [], [] ) )

if ( __name__ == '__main__' ):
    import random
    seq = range( 20 )
    random.shuffle( seq )
    print( seq )
    print( partition( lambda v : v > 10, seq ) )
0
TTimo