Python:条件に基づいてリストを分割しますか?


272

条件に基づいてアイテムのリストを複数のリストに分割するために、美的にもパフォーマンスの観点からも最良の方法は何ですか?同等のもの:

good = [x for x in mylist if x in goodvals]
bad  = [x for x in mylist if x not in goodvals]

これを行うためのよりエレガントな方法はありますか?

更新:私がやろうとしていることをよりよく説明するために、実際の使用例を次に示します。

# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f[2].lower() not in IMAGE_TYPES]

5
セットビルダーのステートメントで条件を設定する方法を探してここに上陸しました。あなたの質問が私の質問に答えました:)
Anuvrat Parashar

5
splitは、Python文字列に関してすでに特定の意味を持っているため、この操作の残念な説明です。除算は、この操作を説明するためのより正確な(または少なくともPythonの反復可能オブジェクトのコンテキストではオーバーロードが少ない)単語だと思います。に相当するリストを探してここに着陸し、リストを連続したサブリストの順序付けられたコレクションstr.split()分割しました。たとえばsplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])、リストの要素をカテゴリで分割するのではなく、
2015

python-listに関する同じトピックの議論
Xiong Chiamiov

IMAGE_TYPESはタプルではなくセットである必要があります:IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')。n(o / 2)の代わりにn(1)。読みやすさの違いはほとんどありません。
ChaimG 2017年

回答:


110
good = [x for x in mylist if x in goodvals]
bad  = [x for x in mylist if x not in goodvals]

これを行うためのよりエレガントな方法はありますか?

そのコードは完全に読みやすく、非常に明確です!

# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f[2].lower() not in IMAGE_TYPES]

繰り返しますが、これで問題ありません。

セットを使用するとパフォーマンスが少し向上する可能性がありますが、それは些細な違いです。リストの理解がはるかに読みやすく、注文がめちゃくちゃになったり、重複が削除されたりすることを心配する必要はありません。

実際、私は別のステップに「後退」し、単純なforループを使用するだけかもしれません。

images, anims = [], []

for f in files:
    if f.lower() in IMAGE_TYPES:
        images.append(f)
    else:
        anims.append(f)

リスト内包表記または使用set()は、他のチェックまたは別のロジックのビットを追加する必要があるまで問題ありません。たとえば、すべての0バイトのjpegを削除したい場合は、次のように追加します。

if f[1] == 0:
    continue

44
リストを2回ループする必要がないリスト理解方法はありませんか?
balki

35
問題は、これがDRYの原則に違反していることです。これを行うためのより良い方法があればそれは素晴らしいでしょう。
アンチモン2013年

21
関数型プログラミング(Haskell)または関数型スタイル(LINQ)への欲求が高まると、Pythonの[x for x in blah if ...]古臭lambdaさを感じ始めます。当時と同じではありません。
Tomasz Gandor

6
@TomaszGandor FTR、HaskellはPythonより古い(そして実際にその設計に影響を与えた)。リストの内包表記とラムダの構文は、意図的に少し冗長にしておいたため、おそらくそれらを使いすぎないようにしています。これは確かに少しリスクです...私がHaskellを好きなだけでなく、多くの人がPythonが一般的に読みやすくなっている理由を理解できます。
leftaroundabout '09 / 09/30

4
シンプルなforループがこれを行うための最良の方法です...単一のループ、非常に明確で読みやすい
Anentropic

217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)

14
それは信じられないほど独創的です!何が起こっているのか理解するのにしばらく時間がかかりました。他の人がこれを可読コードと見なすことができると考えるかどうか知りたいのですが。
jgpaiva 2013

171
good.append(x) if x in goodvals else bad.append(x)より読みやすいです。
dansalmo 2013年

21
@dansalmo特に、あなたはそれのためのサイクルでワンライナーにすることができるので、あなたがより複雑なものを追加したい場合よりも、xあなたが一つにそれを作ることができ、append唯一:for x in mylist: (good if isgood(x) else bad).append(x)
よ」

2
@MLister、その場合は、おそらく属性ルックアップを含める必要があります(bad.append, good.append)
John La Rooy

11
少し短いバリエーション:(good if x in goodvals else bad).append(x)
Pi Delport

104

遅延イテレータアプローチは次のとおりです。

from itertools import tee

def split_on_condition(seq, condition):
    l1, l2 = tee((condition(item), item) for item in seq)
    return (i for p, i in l1 if p), (i for p, i in l2 if not p)

アイテムごとに1回条件を評価し、2つのジェネレータを返します。最初に、条件がtrueであるシーケンスとfalseであるシーケンスから値を生成します。

怠惰なので、無限のイテレータでも使用できます。

from itertools import count, islice

def is_prime(n):
    return n > 1 and all(n % i for i in xrange(2, n))

primes, not_primes = split_on_condition(count(), is_prime)
print("First 10 primes", list(islice(primes, 10)))
print("First 10 non-primes", list(islice(not_primes, 10)))

通常は、非遅延リストを返すアプローチの方が優れています。

def split_on_condition(seq, condition):
    a, b = [], []
    for item in seq:
        (a if condition(item) else b).append(item)
    return a, b

編集:いくつかのキーによってアイテムを異なるリストに分割するより具体的なユースケースのために、これを行う一般的な関数を次に示します。

DROP_VALUE = lambda _:_
def split_by_key(seq, resultmapping, keyfunc, default=DROP_VALUE):
    """Split a sequence into lists based on a key function.

        seq - input sequence
        resultmapping - a dictionary that maps from target lists to keys that go to that list
        keyfunc - function to calculate the key of an input value
        default - the target where items that don't have a corresponding key go, by default they are dropped
    """
    result_lists = dict((key, []) for key in resultmapping)
    appenders = dict((key, result_lists[target].append) for target, keys in resultmapping.items() for key in keys)

    if default is not DROP_VALUE:
        result_lists.setdefault(default, [])
        default_action = result_lists[default].append
    else:
        default_action = DROP_VALUE

    for item in seq:
        appenders.get(keyfunc(item), default_action)(item)

    return result_lists

使用法:

def file_extension(f):
    return f[2].lower()

split_files = split_by_key(files, {'images': IMAGE_TYPES}, keyfunc=file_extension, default='anims')
print split_files['images']
print split_files['anims']

これはおそらくYAGNIの原則に違反していると思います。これは、物事を分割できるさまざまなリストの数が将​​来的に増加するとの想定に基づいています。
アリAasma 09年

17
大量のコードになる可能性がありますが、[ x for x in my_list if ExpensiveOperation(x) ]実行に時間がかかる場合は、2回実行する必要はありません。
ダッシュトムバン

1
+1は、イテレーターベースのソリューションと特定の「X内」ソリューションを含む複数のバリエーションを提供します OPの「goodvals」は小さいかもしれませんが、これを非常に大きな辞書または高価な述語で置き換えると、高価になる可能性があります。また、リスト内包表記を必要な場所に2回記述する必要がなくなるため、タイプミスやユーザーエラーが発生する可能性が低くなります。素晴らしい解決策。ありがとう!
cod3monk3y 2013年

3
tee返すイテレータ間のすべての値を格納することに注意してください。したがって、ジェネレータ全体をループし、次にもう一方をループしても、メモリを節約できません。
John La Rooy、2014

25

提案されているすべてのソリューションの問題は、フィルタリング機能を2回スキャンして適用することです。私はこのような単純な小さな関数を作成します:

def SplitIntoTwoLists(l, f):
  a = []
  b = []
  for i in l:
    if f(i):
      a.append(i)
    else:
      b.append(i)
 return (a,b)

そうすれば、何も2回処理することも、コードを繰り返すこともありません。


同意する。私はリストを2回スキャンすることなくこれを行うための「エレガントな」(つまり、ここでは短い組み込み/暗黙の意味)方法を探していましたが、これは(プロファイリングなしで)進むべき道のようです。もちろん、とにかく大量のデータの場合にのみ問題になります。
マシューFlaschen

私見、CPU使用量を減らして(したがって、電力消費を減らして)方法を知っているなら、それを使用しない理由はありません。
09年

2
@winden ...すべてのPythonをCに移植;)
Elliot Cameron

19

私の見解。私partitionは、出力サブシーケンスの相対的な順序を保持する、レイジーなシングルパス関数を提案します。

1.要件

要件は次のとおりです。

  • 要素の相対的な順序を維持します(したがって、セットや辞書はありません)
  • すべての要素に対して条件を1回だけ評価する(したがって(ifilterまたはを使用しないgroupby
  • どちらかのシーケンスの遅延消費を可能にする(それらを事前計算する余裕がある場合、単純な実装も許容される可能性があります)

2. splitライブラリ

私のpartition関数(以下で紹介)と他の同様の関数がそれを小さなライブラリーにしました:

通常はPyPI経由でインストールできます。

pip install --user split

条件に基づいてリストを分割するには、partition関数を使用します。

>>> from split import partition
>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi') ]
>>> image_types = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> images, other = partition(lambda f: f[-1] in image_types, files)
>>> list(images)
[('file1.jpg', 33L, '.jpg')]
>>> list(other)
[('file2.avi', 999L, '.avi')]

3. partition機能の説明

内部的には2つのサブシーケンスを一度に作成する必要があるため、1つの出力シーケンスのみを使用すると、もう1つの出力シーケンスも強制的に計算されます。また、ユーザーリクエスト間で状態を保持する必要があります(ストアは処理されたが、まだリクエストされていない要素)。状態を維持するために、2つの両端キュー(deques)を使用します。

from collections import deque

SplitSeq クラスがハウスキーピングを処理します。

class SplitSeq:
    def __init__(self, condition, sequence):
        self.cond = condition
        self.goods = deque([])
        self.bads = deque([])
        self.seq = iter(sequence)

魔法はその.getNext()方法で起こります。.next() イテレータとほとんど同じですが、今回はどの種類の要素が必要かを指定できます。背後では拒否された要素は破棄されませんが、代わりにそれらを2つのキューのいずれかに配置します。

    def getNext(self, getGood=True):
        if getGood:
            these, those, cond = self.goods, self.bads, self.cond
        else:
            these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
        if these:
            return these.popleft()
        else:
            while 1: # exit on StopIteration
                n = self.seq.next()
                if cond(n):
                    return n
                else:
                    those.append(n)

エンドユーザーはpartition関数を使用することになっています。条件関数とシーケンス(mapまたはのようなfilter)を取り、2つのジェネレーターを返します。最初のジェネレーターは、条件が満たされる要素のサブシーケンスを作成し、2番目のジェネレーターは相補的なサブシーケンスを作成します。イテレータとジェネレータを使用すると、長いシーケンスや無限のシーケンスでも遅延分割できます。

def partition(condition, sequence):
    cond = condition if condition else bool  # evaluate as bool if condition == None
    ss = SplitSeq(cond, sequence)
    def goods():
        while 1:
            yield ss.getNext(getGood=True)
    def bads():
        while 1:
            yield ss.getNext(getGood=False)
    return goods(), bads()

私は、将来の部分的な適用を容易にするために、最初の引数としてテスト関数を選択しました(テスト関数を最初の引数 として持つ方法mapと同様filter)。


15

アンダースのアプローチは非常に一般的であるため、基本的に私は好きです。これは、カテゴライザーを最初に置き(フィルター構文を一致させるため)、defaultdict(インポートされたと想定)を使用するバージョンです。

def categorize(func, seq):
    """Return mapping from categories to lists
    of categorized items.
    """
    d = defaultdict(list)
    for item in seq:
        d[func(item)].append(item)
    return d

ここで適用されるZenのPythonからのステートメントを選択しようとしましたが、コメントとしては多すぎます。=)素晴らしいコード。
jpmc26 2014年

13

はじめに(OP前編集):セットを使用:

mylist = [1,2,3,4,5,6,7]
goodvals = [1,3,7,8,9]

myset = set(mylist)
goodset = set(goodvals)

print list(myset.intersection(goodset))  # [1, 3, 7]
print list(myset.difference(goodset))    # [2, 4, 5, 6]

これは、読みやすさ(IMHO)とパフォーマンスの両方に適しています。

二度目(OP後編集):

良い拡張機能のリストをセットとして作成します。

IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])

パフォーマンスが向上します。そうでなければ、あなたが持っているものは私には元気に見えます。


4
リストが分割される前にある順序で並べられていて、その順序を維持する必要がある場合は、最善の解決策ではありません。
Daniyar

8
重複を削除しませんか?
mavnn 2006年

セットの作成はO(n log n)です。リストを2回繰り返すのはO(n)です。集合の解は(そもそも正しい場合は)より洗練されているかもしれませんが、nが大きくなるほど遅くなります。
ダッシュトムバン

1
@ dash-tom-bangリストの反復はO(n * n)です。これは、リストの各アイテムをの各アイテムと比較する必要がある場合があるためgoodvalsです。
ChaimG

@ChaimGは良い点ですが、交差および差分演算のコストも考慮する必要があります(手元にはわかりませんが、これらも超線形であると確信しています)。
ダッシュトムバン

10

itertools.groupbyは、単一の連続した範囲を確実に取得するために項目をソートする必要があることを除いて、ほぼ望みどおりの動作をします。そのため、最初にキーでソートする必要があります(それ以外の場合は、タイプごとに複数のインターリーブグループを取得します)。例えば。

def is_good(f):
    return f[2].lower() in IMAGE_TYPES

files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file3.gif', 123L, '.gif')]

for key, group in itertools.groupby(sorted(files, key=is_good), key=is_good):
    print key, list(group)

与える:

False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]

他のソリューションと同様に、主要な機能を定義して、必要な数のグループに分割できます。


6
good.append(x) if x in goodvals else bad.append(x)

@dansalmoによるこのエレガントで簡潔な回答はコメントに埋もれてしまったため、ここでは回答として再掲しているので、特に新しい読者にとっては目立つようになります。

完全な例:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)

5

FPスタイルで作成する場合:

good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y])
                                        for y in mylist)) ]

最も読みやすいソリューションではありませんが、少なくともmylistを1回だけ繰り返します。


1
リストを反復するのは1回だけですが、リストが追加されるため、パフォーマンスはそれほど良くありません。リストへの追加は、(たとえば、deque.appendと比較すると)コストがかかる可能性のある操作です。実際、このソリューションは、ここにある他のソリューション(100000個のランダムな整数で21.4秒であり、それらの値をテストする)と比較すると非常に低速です。
rlat 2015

5

個人的に、私はあなたがすでにgoodvalsぶらぶらしているリストを持っていると仮定して、あなたが引用したバージョンが好きです。そうでない場合、次のようなもの:

good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)

もちろん、これは最初に行ったようにリスト内包表記を使用することと非常によく似ていますが、ルックアップの代わりに関数を使用します。

good = [x for x in mylist if is_good(x)]
bad  = [x for x in mylist if not is_good(x)]

一般に、リスト内包表記の美学は非常に楽しいものです。もちろん、実際に順序を維持する必要がなく、重複を必要としない場合は、セットintersectiondifferenceメソッドとメソッドを使用してもうまくいきます。


もちろん、次のfilter(lambda x: is_good(x), mylist)ように削減できますfilter(is_good, mylist)
robru 11/7

追加の関数呼び出しを追加すると、実際にプロファイリングで見たものから、リスト内包表記と比較して実行時間が2倍(!)になります。ほとんどの場合、リストの理解に勝つことは困難です。
Corley Brigman、2015年

4

N条件に基づいてイテラブルを分割する一般化は便利だと思います

from collections import OrderedDict
def partition(iterable,*conditions):
    '''Returns a list with the elements that satisfy each of condition.
       Conditions are assumed to be exclusive'''
    d= OrderedDict((i,list())for i in range(len(conditions)))        
    for e in iterable:
        for i,condition in enumerate(conditions):
            if condition(e):
                d[i].append(e)
                break                    
    return d.values()

例えば:

ints,floats,other = partition([2, 3.14, 1, 1.69, [], None],
                              lambda x: isinstance(x, int), 
                              lambda x: isinstance(x, float),
                              lambda x: True)

print " ints: {}\n floats:{}\n other:{}".format(ints,floats,other)

 ints: [2, 1]
 floats:[3.14, 1.69]
 other:[[], None]

要素が複数の条件を満たす可能性がある場合は、ブレークを削除します。


3
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)

これをチェック


3

時々、リスト内包表記は使用するのに最適ではないようです!

ランダムに生成されたリストでテストして、このトピックに対する人々の回答に基づいて小さなテストを行いました。ここにリストの生成があります(おそらくもっと良い方法がありますが、それがポイントではありません):

good_list = ('.jpg','.jpeg','.gif','.bmp','.png')

import random
import string
my_origin_list = []
for i in xrange(10000):
    fname = ''.join(random.choice(string.lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(good_list)
    else:
        fext = "." + ''.join(random.choice(string.lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

さあ、いくぞ

# Parand
def f1():
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def f2():
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def f3():
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# Ants Aasma
def f4():
    l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
    return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def f5():
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def f6():
    return filter(lambda e: e[2] in good_list, my_origin_list), filter(lambda e: not e[2] in good_list, my_origin_list)

cmpthese関数を使用すると、最良の結果はdbrの答えになります。

f1     204/s  --    -5%   -14%   -15%   -20%   -26%
f6     215/s     6%  --    -9%   -11%   -16%   -22%
f3     237/s    16%    10%  --    -2%    -7%   -14%
f4     240/s    18%    12%     2%  --    -6%   -13%
f5     255/s    25%    18%     8%     6%  --    -8%
f2     277/s    36%    29%    17%    15%     9%  --

ここで更新されたベンチマークより高速な機能。
ChaimG

2

この問題のさらに別の解決策。可能な限り高速なソリューションが必要でした。これは、リスト全体で1回の反復のみを意味し、できれば、結果のリストの1つにデータを追加するためのO(1)を意味します。これは、非常に短いことを除いて、sastaninによって提供されるソリューションと非常に似ています。

from collections import deque

def split(iterable, function):
    dq_true = deque()
    dq_false = deque()

    # deque - the fastest way to consume an iterator and append items
    deque((
      (dq_true if function(item) else dq_false).append(item) for item in iterable
    ), maxlen=0)

    return dq_true, dq_false

その後、次の方法で関数を使用できます。

lower, higher = split([0,1,2,3,4,5,6,7,8,9], lambda x: x < 5)

selected, other = split([0,1,2,3,4,5,6,7,8,9], lambda x: x in {0,4,9})

結果がうまくいかない場合 dequeオブジェクトに問題がある場合は、好きなように簡単にlist、に変換できsetます(たとえば、list(lower))。変換ははるかに速く、リストを直接作成することです。

このメソッドは、アイテムの順序と重複を保持します。


2

たとえば、リストを偶数と奇数で分割する

arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))

または一般的に:

def split(predicate, iterable):
    return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))

利点:

  • 可能な最短の方法
  • 述語は各要素に1回だけ適用されます

短所

  • 関数型プログラミングパラダイムの知識が必要

2

@gnibblerの素晴らしい(しかし簡潔です!)答えに触発されて、そのアプローチを適用して複数のパーティションにマップできます。

from collections import defaultdict

def splitter(l, mapper):
    """Split an iterable into multiple partitions generated by a callable mapper."""

    results = defaultdict(list)

    for x in l:
        results[mapper(x)] += [x]

    return results

その後splitter、次のように使用できます。

>>> l = [1, 2, 3, 4, 2, 3, 4, 5, 6, 4, 3, 2, 3]
>>> split = splitter(l, lambda x: x % 2 == 0)  # partition l into odds and evens
>>> split.items()
>>> [(False, [1, 3, 3, 5, 3, 3]), (True, [2, 4, 2, 4, 6, 4, 2])]

これは、より複雑なマッピングを使用する3つ以上のパーティションで機能します(イテレータでも同様)。

>>> import math
>>> l = xrange(1, 23)
>>> split = splitter(l, lambda x: int(math.log10(x) * 5))
>>> split.items()
[(0, [1]),
 (1, [2]),
 (2, [3]),
 (3, [4, 5, 6]),
 (4, [7, 8, 9]),
 (5, [10, 11, 12, 13, 14, 15]),
 (6, [16, 17, 18, 19, 20, 21, 22])]

または、辞書を使用してマッピングします。

>>> map = {'A': 1, 'X': 2, 'B': 3, 'Y': 1, 'C': 2, 'Z': 3}
>>> l = ['A', 'B', 'C', 'C', 'X', 'Y', 'Z', 'A', 'Z']
>>> split = splitter(l, map.get)
>>> split.items()
(1, ['A', 'Y', 'A']), (2, ['C', 'C', 'X']), (3, ['B', 'Z', 'Z'])]

...これは基本的に@ alan-isaacがすでに回答したものと同じであることに気づきました。
Josh Bode 2013年

2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

appendはNoneを返すので、機能します。


1

パフォーマンスについては、を試してくださいitertools

itertoolsのモジュールは、高速のコアセット、それ自体で、または組み合わせで有用であるメモリ効率的なツールを標準化しています。これらが一緒になって「反復代数」を形成し、純粋なPythonで簡潔かつ効率的に専用ツールを構築することができます。

itertools.ifilterまたはimapを参照してください。

itertools.ifilter(述語、反復可能)

述語がTrueである要素のみを返すイテラブルから要素をフィルタリングするイテレータを作成します


ifilter / imap(およびジェネレーター一般)はかなり遅いです...一般的に、プロファイリングで、[x for x in a if x > 50000](random.shuffleを介して)100000整数の単純な配列のようなリスト内包を取得するfilter(lambda x: x> 50000, a)と、2倍のifilter(lambda x: x> 50000, a); list(result)時間がかかります約2.3倍の長さ。奇妙だが真実。
Corley Brigman、2015年

1

リストの他の半分は必要ない場合があります。例えば:

import sys
from itertools import ifilter

trustedPeople = sys.argv[1].split(',')
newName = sys.argv[2]

myFriends = ifilter(lambda x: x.startswith('Shi'), trustedPeople)

print '%s is %smy friend.' % (newName, newName not in myFriends 'not ' or '')

1

これが最速の方法です。

それは使用しています if else(dbrの答えのよう、最初にセットを作成します。セットは、操作の数をO(m * n)からO(log m)+ O(n)に減らし、速度が45%+向上します。

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    if item in good_list_set:
        good.append(item)
    else:
        bad.append(item)

少し短い:

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    out = good if item in good_list_set else bad
    out.append(item)

ベンチマーク結果:

filter_BJHomer                  80/s       --   -3265%   -5312%   -5900%   -6262%   -7273%   -7363%   -8051%   -8162%   -8244%
zip_Funky                       118/s    4848%       --   -3040%   -3913%   -4450%   -5951%   -6085%   -7106%   -7271%   -7393%
two_lst_tuple_JohnLaRoy         170/s   11332%    4367%       --   -1254%   -2026%   -4182%   -4375%   -5842%   -6079%   -6254%
if_else_DBR                     195/s   14392%    6428%    1434%       --    -882%   -3348%   -3568%   -5246%   -5516%   -5717%
two_lst_compr_Parand            213/s   16750%    8016%    2540%     967%       --   -2705%   -2946%   -4786%   -5083%   -5303%
if_else_1_line_DanSalmo         292/s   26668%   14696%    7189%    5033%    3707%       --    -331%   -2853%   -3260%   -3562%
tuple_if_else                   302/s   27923%   15542%    7778%    5548%    4177%     343%       --   -2609%   -3029%   -3341%
set_1_line                      409/s   41308%   24556%   14053%   11035%    9181%    3993%    3529%       --    -569%    -991%
set_shorter                     434/s   44401%   26640%   15503%   12303%   10337%    4836%    4345%     603%       --    -448%
set_if_else                     454/s   46952%   28358%   16699%   13349%   11290%    5532%    5018%    1100%     469%       --

Python 3.7の完全なベンチマークコード(FunkySayuから変更):

good_list = ['.jpg','.jpeg','.gif','.bmp','.png']

import random
import string
my_origin_list = []
for i in range(10000):
    fname = ''.join(random.choice(string.ascii_lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(list(good_list))
    else:
        fext = "." + ''.join(random.choice(string.ascii_lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

# Parand
def two_lst_compr_Parand(*_):
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def if_else_DBR(*_):
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def two_lst_tuple_JohnLaRoy(*_):
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# # Ants Aasma
# def f4():
#     l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
#     return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def zip_Funky(*_):
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def filter_BJHomer(*_):
    return list(filter(lambda e: e[2] in good_list, my_origin_list)), list(filter(lambda e: not e[2] in good_list,                                                                             my_origin_list))

# ChaimG's answer; as a list.
def if_else_1_line_DanSalmo(*_):
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list else bad.append(e)
    return good, bad

# ChaimG's answer; as a set.
def set_1_line(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list_set else bad.append(e)
    return good, bad

# ChaimG set and if else list.
def set_shorter(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        out = good if e[2] in good_list_set else bad
        out.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def set_if_else(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_set:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def tuple_if_else(*_):
    good_list_tuple = tuple(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_tuple:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

def cmpthese(n=0, functions=None):
    results = {}
    for func_name in functions:
        args = ['%s(range(256))' % func_name, 'from __main__ import %s' % func_name]
        t = Timer(*args)
        results[func_name] = 1 / (t.timeit(number=n) / n) # passes/sec

    functions_sorted = sorted(functions, key=results.__getitem__)
    for f in functions_sorted:
        diff = []
        for func in functions_sorted:
            if func == f:
                diff.append("--")
            else:
                diff.append(f"{results[f]/results[func]*100 - 100:5.0%}")
        diffs = " ".join(f'{x:>8s}' for x in diff)

        print(f"{f:27s} \t{results[f]:,.0f}/s {diffs}")


if __name__=='__main__':
    from timeit import Timer
cmpthese(1000, 'two_lst_compr_Parand if_else_DBR two_lst_tuple_JohnLaRoy zip_Funky filter_BJHomer if_else_1_line_DanSalmo set_1_line set_if_else tuple_if_else set_shorter'.split(" "))

0

あなたが賢いと主張するなら、あなたはウィンデンの解決策と少しだけ偽の賢さを取ることができます:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d

3
「dまたは{}」は少し危険です。空の辞書が渡された場合、その場所では変更されません。
ブライアン

確かに、それは返されるので...実際、これは、コードに賢く追加したくない理由の完璧な例です。:-P
アンデルスユーレニウス2009年

0

ここにはすでにかなりの数の解決策がありますが、それを行う別の方法は-

anims = []
images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]

リストを1回だけ反復し、もう少しPythonicに見えるため、私には読みやすくなっています。

>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file1.bmp', 33L, '.bmp')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> anims = []
>>> images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]
>>> print '\n'.join([str(anims), str(images)])
[('file2.avi', 999L, '.avi')]
[('file1.jpg', 33L, '.jpg'), ('file1.bmp', 33L, '.bmp')]
>>>

0

私は2パスのアプローチを採用し、述語の評価をリストのフィルタリングから分離します。

def partition(pred, iterable):
    xs = list(zip(map(pred, iterable), iterable))
    return [x[1] for x in xs if x[0]], [x[1] for x in xs if not x[0]]

これの良い点は、パフォーマンスの点で(のpred各メンバーで一度だけ評価することに加えてiterable)、多くのロジックをインタープリターから高度に最適化された反復とマッピングコードに移動することです。この回答で説明さているように、これにより、長いイテラブルでの反復が高速化されます。

表現力については、理解やマッピングなどの表現力豊かなイディオムを利用します。


0

解決

from itertools import tee

def unpack_args(fn):
    return lambda t: fn(*t)

def separate(fn, lx):
    return map(
        unpack_args(
            lambda i, ly: filter(
                lambda el: bool(i) == fn(el),
                ly)),
        enumerate(tee(lx, 2)))

テスト

[even, odd] = separate(
    lambda x: bool(x % 2),
    [1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])

0

外部ライブラリの使用を気にしない場合、この操作をネイティブに実装している2つがわかります。

>>> files = [ ('file1.jpg', 33, '.jpg'), ('file2.avi', 999, '.avi')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
  • iteration_utilities.partition

    >>> from iteration_utilities import partition
    >>> notimages, images = partition(files, lambda x: x[2].lower() in IMAGE_TYPES)
    >>> notimages
    [('file2.avi', 999, '.avi')]
    >>> images
    [('file1.jpg', 33, '.jpg')]
  • more_itertools.partition

    >>> from more_itertools import partition
    >>> notimages, images = partition(lambda x: x[2].lower() in IMAGE_TYPES, files)
    >>> list(notimages)  # returns a generator so you need to explicitly convert to list.
    [('file2.avi', 999, '.avi')]
    >>> list(images)
    [('file1.jpg', 33, '.jpg')]

0

これが良いアプローチかどうかはわかりませんが、この方法でも行うことができます

IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi')]
images, anims = reduce(lambda (i, a), f: (i + [f], a) if f[2] in IMAGE_TYPES else (i, a + [f]), files, ([], []))

0

リストがグループと断続的なセパレーターで構成されている場合は、以下を使用できます。

def split(items, p):
    groups = [[]]
    for i in items:
        if p(i):
            groups.append([])
        groups[-1].append(i)
    return groups

使用法:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

0
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

あなたの例のように、状態がより長い場合にいいです。読者は、否定的な条件や、他のすべてのケースを捉えるかどうかを理解する必要はありません。


0

さらに別の答えは、短いが「悪」(リスト理解の副作用の場合)。

digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]

>>> odd
[1, 3, 5, 7, 9]

>>> digits
[0, 2, 4, 6, 8]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.