web-dev-qa-db-ja.com

Python複数のリストを作成するためのリスト内包表記

2つのリストlistOfAlistOfBを作成して、別のリストからのABのインデックスを保存します。

s=['A','B','A','A','A','B','B']

出力は2つのリストである必要があります

listOfA=[0,2,3,4]
listOfB=[1,5,6]

2つのステートメントでこれを行うことができます。

listOfA=[idx for idx,x in enumerate(s) if x=='A']
listOfB=[idx for idx,x in enumerate(s) if x=='B']

ただし、リスト内包表記のみを使用して、1回の反復でそれを実行する必要があります。単一のステートメントでそれを行うことは可能ですか?何かのようなもの listOfA,listOfB=[--code goes here--]

20
Heisenberg

リスト内包のまさに定義は、oneリストオブジェクトを生成することです。 2つのリストオブジェクトは長さが異なります。あなたはあなたが望むものを達成するために副作用を使わなければならないでしょう。

ここではリスト内包表記を使用しないでください。通常のループを使用するだけです。

_listOfA, listOfB = [], []

for idx, x in enumerate(s):
    target = listOfA if x == 'A' else listOfB
    target.append(idx)
_

これにより、実行するoneループだけが残ります。これは、少なくとも2つのリスト内包表記に勝るでしょう。少なくとも、開発者がリスト内包表記に、個別のlist.append()呼び出しを使用したループの2倍の速さでリストを作成させる方法を見つけるまではです。

入れ子になったリスト内包表記justよりもいつでもこれを選択して、1行に2つのリストを作成できるようにします。 Zen of Python の状態:

読みやすさが重要です。

39
Martijn Pieters

ソート;重要なのは、展開できる2要素のリストを生成することです。

listOfA, listOfB = [[idx for idx, x in enumerate(s) if x == c] for c in 'AB']

そうは言っても、そのようにするのはかなりお手上げだと思います。明示的なループははるかに読みやすくなっています。

12
RemcoGerlich

この問題への素晴らしいアプローチはdefaultdictを使用することです。 @Martinがすでに述べたように、リスト内包は2つのリストを作成するための適切なツールではありません。 defaultdictを使用すると、単一の反復を使用して分離を作成できます。さらに、コードはどのような形でも制限されません。

>>> from collections import defaultdict
>>> s=['A','B','A','A','A','B','B']
>>> listOf = defaultdict(list)
>>> for idx, elem in enumerate(s):
    listOf[elem].append(idx)
>>> listOf['A'], listOf['B']
([0, 2, 3, 4], [1, 5, 6])
5
Abhijit

あなたがやろうとしていることは完全に不可能ではありません、それはただ複雑で、おそらく無駄です。

イテラブルを2つのイテラブルに分割する場合、ソースがリストまたはその他の再利用可能なイテラブルである場合、質問のように、2つのパスで実行する方がよいでしょう。

ソースがイテレータであっても、必要な出力が遅延イテレータのペアではなくリストのペアである場合は、 Martijn's answer を使用するか、list(iterator)を2回パスします。 。)

しかし、本当に任意のイテラブルを2つのイテラブルに遅延分割する必要がある場合、なんらかの中間ストレージなしにそれを行う方法はありません。

_[1, 2, -1, 3, 4, -2]_をpositivesnegativesに分割するとします。次に、next(negatives)を実行します。それはあなたに_-1_を与えるはずですよね?しかし、_1_と_2_を消費せずにそれを行うことはできません。つまり、next(positives)を実行しようとすると、_3_ではなく_1_が取得されます。したがって、_1_および_2_はどこかに保存する必要があります。

あなたが必要とする賢さのほとんどは _itertools.tee_ の中にラップされています。 positivesnegativesを同じイテレータの2つのteedコピーにして、両方をフィルタリングすれば完了です。

実際、これはitertoolsドキュメントのレシピの1つです。

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

(それが理解できない場合は、2つのジェネレーター関数がクロージャーを介してイテレーターとT型を共有するか、クラスの2つのメソッドがselfを介してそれらを共有するかのいずれかで、おそらく明示的に書き出す価値があります。数十行になるはずです。トリッキーなものを必要としないコードの例)

また、_more_itertools_などのサードパーティライブラリからのインポートとしてpartitionを取得することもできます。


これで、これをワンライナーで使用できます。

_lst = [1, 2, -1, 3, 4, -2]
positives, negatives = partition(lst, lambda x: x>=0)
_

…そして、すべての正の値に対するイテレータと、すべての負の値に対するイテレータがあります。それらは完全に独立しているように見えますが、一緒にlstを1回渡すだけです。そのため、lstをリ​​ストではなくジェネレーター式またはファイルなどに割り当てても機能します。


では、なぜこれに何らかのショートカット構文がないのですか?かなり誤解を招くからです。

理解は余分なストレージを必要としません。これが、ジェネレータ式が非常に優れている理由です。ジェネレータ式は、何も保存せずに遅延イテレータを別の遅延イテレータに変換できます。

ただし、これにはO(N)ストレージが必要です。すべての数値が正であると想像してください。最初にnegativeを反復しようとします。何が起こるのですか?すべての数値はtrueqにプッシュされます。実際、そのO(N)infiniteである可能性もあります(たとえば、itertools.count()で試してください)。

これは_itertools.tee_のようなもので問題ありません。ほとんどの初心者は知らないモジュールにスタックされた関数であり、その機能を説明してコストを明確にすることができる素敵なドキュメントがあります。しかし、それを通常の理解のように見える構文糖でそれを行うと、別の話になります。

2
abarnert

エッジに住んでいる人のために;)

listOfA, listOfB = [[i for i in cur_list if i is not None] for cur_list in Zip(*[(idx,None) if value == 'A' else (None,idx) for idx,value in enumerate(s)])]
1
Daniel Braun