74

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

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

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

編集:要素の順序は保存する必要があり、同じ要素を複数回持つ可能性があります。

4

14 に答える 14

61

これを試して:

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)

レシピはPython3.xのドキュメントに基づいています。Pythonでは2.xfilterfalseはと呼ばれifilterfalseます。

于 2011-01-02T13:37:49.613 に答える
28
>>> 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つの上位と同じくらい高速ですが、コードは少なくなります。

于 2011-01-02T15:39:03.160 に答える
10

TL; DR

マーク・バイアーズによって受け入れられ、最も投票された回答[1]

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であることに注意してください—したがってreduce、から来ていますfunctools—そしてOPはのようなタプルを要求し(positives, negatives)ますが、私の実装はすべて(negatives, positives)…</p> を返します

$ 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リストが具体化されないこと、または必要以上に入力を先読みしないことです。Byers et al。の2倍の頻度で呼び出しpred、ほぼ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)]は、ええと、役に立たないです。なぜなら、すべてのステップでデータ構造にインデックスを付ける必要があり、わずかなペナルティが与えられるからです。ただし、pythonizingで衰退する文化の生存者を説得したいかどうかを知るのは良いことです。

代替の実装と機能的に同等である機能的アプローチはappend、おそらく各リスト要素に対して追加の(述語評価用のw / r)関数呼び出しがあるという事実のために、約50%遅くなります。

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

脇白

私は、マリイが問題への機能的なアプローチを示す回答object.mutate() or objectで公開したイディオム に恋をしました。遅かれ早かれ、それを悪用するのではないかと心配しています。

脚注

[1] 2017年9月14日、今日として受け入れられ、最も多く投票されました—しかしもちろん、私はこの私の答えに最大の期待を持っています!

于 2017-09-14T10:48:36.613 に答える
7

ここでは、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]}
于 2012-04-19T20:18:21.097 に答える
4

あなたは解決策を見ることができます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/にあります。

于 2017-08-29T20:26:58.767 に答える
3

リストに重複する要素がない場合は、必ず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()の結果を知っているだけで、フィルター基準をあまり満たしていない要素を推測できます。

于 2011-01-02T13:48:08.857 に答える
3

私はまさにこの要件を持っていました。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])
于 2012-08-15T11:35:52.043 に答える
2
from itertools import ifilterfalse

def filter2(predicate, iterable):
    return filter(predicate, iterable), list(ifilterfalse(predicate, iterable))
于 2011-01-02T13:54:30.947 に答える
2

既存の回答は、反復可能ファイルを2つのリストに分割するか、非効率的に2つのジェネレーターに分割します。これは、iterableを2つのジェネレーターに効率的に分割する実装です。つまり、述語関数は、iterableの各要素に対して最大で1回呼び出されます。このバージョンを使用する可能性のある1つの例は、非常に大規模な(または無限の)反復可能で、計算コストの高い述語をパーティション化する必要がある場合です。

from collections import deque

def partition(pred, iterable):
    seq = iter(iterable)
    true_buffer = deque()
    false_buffer = deque()
    def true_iter():
        while True:
            while true_buffer:
                yield true_buffer.popleft()
            item = next(seq)
            if pred(item):
                yield item
            else:
                false_buffer.append(item)
    def false_iter():
        while True:
            while false_buffer:
                yield false_buffer.popleft()
            item = next(seq)
            if not pred(item):
                yield item
            else:
                true_buffer.append(item)
    return true_iter(), false_iter()

基本的に、これはイテレーターの各項目をステップスルーし、述語をチェックし、対応するジェネレーターが使用されている場合はそれを生成するか、他のイテレーターのバッファーに入れます。さらに、各ジェネレーターは、元のイテラブルをチェックする前に、まずバッファーからアイテムをプルします。各パーティションには独自のバッファがあり、他のパーティションが繰り返されるたびに大きくなるため、この実装は、一方のパーティションがもう一方のパーティションよりもはるかに多く繰り返されるユースケースには適さない場合があることに注意してください。

ユースケースの例:

from itertools import count
from random import random

odds, evens = partition(lambda n: n % 2, count())
for _ in range(500):
    if random() < 0.5:
        print(next(odds))
    else:
        print(next(evens))
于 2019-12-23T16:32:03.550 に答える
1

誰もが自分たちの解決策が最善だと思っているようですので、timeitを使用してすべてをテストすることにしました。述語関数として「defis_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倍上回っています。道徳は、車輪の再発明を続けないことです。

于 2013-08-08T13:05:04.707 に答える
0

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

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 ) )
于 2012-06-06T14:37:56.760 に答える
0

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

    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]
于 2018-10-15T18:33:33.260 に答える
0

同等の質問に対する上位3つの投票回答は、(ここですでに説明したように)使用することを提案しitertools.tee()、さらに2つのより単純なアプローチも使用することを提案します。

于 2021-01-28T12:42:35.557 に答える
0

このcollections.defaultdictメソッドは、ソート操作の優れたヘルパーです。

import collections
input_list = ['a','b','ana','beta','gamma']
filter_key = lambda x: len(x) == 1


## sorting code
cc = collections.defaultdict(list)
for item in input_list: cc[ filter_key(item) ].append( item )

print( cc )
This approach will also work for any number of categories generated by the `filter_key` function.
于 2021-04-07T08:59:11.033 に答える