web-dev-qa-db-ja.com

Pythonで2つ以上のリストをインターリーブする最良の方法は?

リストがあるとします:

l=['a','b','c']

そしてその接尾辞リスト:

l2 = ['a_1', 'b_1', 'c_1']

目的の出力を次のようにします。

out_l = ['a','a_1','b','b_2','c','c_3']

結果は、上記の2つのリストのインターリーブバージョンです。

通常のforループを記述してこれを実行できますが、もっとPythonicな方法(リスト内包表記やラムダを使用するなど)でそれを実行できるかどうか疑問に思っています。

私はこのようなことを試しました:

list(map(lambda x: x[1]+'_'+str(x[0]+1), enumerate(a)))
# this only returns ['a_1', 'b_2', 'c_3']

さらに、一般的な場合、つまりl2が必ずしもlの派生物ではない2つ以上のリストの場合、どのような変更を加える必要がありますか?

29
user1330974

yield

エレガントなソリューションにはジェネレーターを使用できます。各反復で、twiceを生成します。元の要素で1回、接尾辞が追加された要素で1回です。

発電機を使い果たす必要があります。これは、最後にlist呼び出しを追加することで実行できます。

_def transform(l):
    for i, x in enumerate(l, 1):
        yield x
        yield f'{x}_{i}'  # {}_{}'.format(x, i)
_

ジェネレーターの委任に_yield from_構文を使用して、これを書き直すこともできます。

_def transform(l):
    for i, x in enumerate(l, 1):
        yield from (x, f'{x}_{i}') # (x, {}_{}'.format(x, i))
_
_out_l = list(transform(l))
print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
_

Python-3.6より古いバージョンを使用している場合は、_f'{x}_{i}'_を'{}_{}'.format(x, i)に置き換えてください。

一般化
フォームのリストがN個ある一般的なシナリオを検討してください。

_l1 = [v11, v12, ...]
l2 = [v21, v22, ...]
l3 = [v31, v32, ...]
...
_

インターリーブしたいもの。これらのリストは、必ずしも互いに派生しているわけではありません。

これらのN個のリストでインターリーブ操作を処理するには、ペアを反復処理する必要があります。

_def transformN(*args):
    for vals in Zip(*args):
        yield from vals

out_l = transformN(l1, l2, l3, ...)
_

スライス_list.__setitem___

パフォーマンスの観点からこれをお勧めします。最初に空のリストにスペースを割り当ててから、スライスされたリストの割り当てを使用してリストアイテムを適切な位置に割り当てます。 lは偶数インデックスに入り、_l'_(l変更)は奇数インデックスに入ります。

_out_l = [None] * (len(l) * 2)
out_l[::2] = l
out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]  # [{}_{}'.format(x, i) ...]
_
_print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
_

これは一貫して私のタイミングからの最速です(下)。

一般化
N個のリストを処理するには、スライスに繰り返し割り当てます。

_list_of_lists = [l1, l2, ...]

out_l = [None] * len(list_of_lists[0]) * len(list_of_lists)
for i, l in enumerate(list_of_lists):
    out_l[i::2] = l
_

Zip + _chain.from_iterable_

@chriszのソリューションに似た機能的なアプローチ。 Zipを使用してペアを構築し、_itertools.chain_を使用してフラット化します。

_from itertools import chain
# [{}_{}'.format(x, i) ...]
out_l = list(chain.from_iterable(Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))) 
_
_print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
_

_iterools.chain_は、Pythonのリストのフラット化アプローチとして広く認識されています。

一般化
これは一般化する最も簡単なソリューションであり、Nが大きい場合、複数のリストに対して最も効率的であると考えられます。

_list_of_lists = [l1, l2, ...]
out_l = list(chain.from_iterable(Zip(*list_of_lists)))
_

パフォーマンス

2つのリスト(接尾辞が付いた1つのリスト)の単純なケースのいくつかのパフォーマンステストを見てみましょう。結果はデー​​タによって大きく異なるため、一般的なケースはテストされません。

enter image description here

参照用のベンチマークコード

関数

_def cs1(l):
    def _cs1(l):
        for i, x in enumerate(l, 1):
            yield x
            yield f'{x}_{i}'

    return list(_cs1(l))

def cs2(l):
    out_l = [None] * (len(l) * 2)
    out_l[::2] = l
    out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]

    return out_l

def cs3(l):
    return list(chain.from_iterable(
        Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)])))

def ajax(l):
    return [
        i for b in [[a, '{}_{}'.format(a, i)] 
        for i, a in enumerate(l, start=1)] 
        for i in b
    ]

def ajax_cs0(l):
    # suggested improvement to ajax solution
    return [j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]

def chrisz(l):
    return [
        val 
        for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) 
        for val in pair
    ]
_
66
cs95

次のようなリスト内包表記を使用できます。

l=['a','b','c']
new_l = [i for b in [[a, '{}_{}'.format(a, i)] for i, a in enumerate(l, start=1)] for i in b]

出力:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

オプションの短い方法:

[j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]
6
Ajax1234

Zip を使用できます。

[val for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) for val in pair]

出力:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
5
user3483203

これが私の簡単な実装です

l=['a','b','c']
# generate new list with the indices of the original list
new_list=l + ['{0}_{1}'.format(i, (l.index(i) + 1)) for i in l]
# sort the new list in ascending order
new_list.sort()
print new_list
# Should display ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
2
Isaac Boakye

この問題のリストの理解も簡単です:

l = ['a', 'b', 'c']
print([ele for index, val in enumerate(l) for ele in (val, val + f'_{index + 1}')])

出力:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

これは、2つのリストをインターリーブするための単純なソリューションであることに注意してください。これは、複数のリストのソリューションではありません。 2つのforループを使用する理由は、執筆時点では、リストの内包表記がTupleのアンパックをサポートしていないためです。

_[["a","a_1"],["b","b_2"],["c","c_3"]]_を返したい場合は、次のように書くことができます

_new_l=[[x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)]
_

これはあなたが望むものではなく、代わりに_["a","a_1"]+["b","b_2"]+["c","c_3"]_が必要です。これは、上記の操作の結果からsum();を使用して作成できます。リストを合計しているので、エラーを避けるために空のリストを引数として追加する必要があります。だからそれは与える

_new_l=sum(([x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)),[])
_

これが速度的にどのように比較されるかはわかりませんが(おそらく良くないでしょう)、リスト理解に基づく他の答えよりも何が起こっているかを理解する方が簡単だと思います。

0
Especially Lime

非常にシンプルなソリューション:

out_l=[]
for i,x in enumerate(l,1):
    out_l.extend([x,f"{x}_{i}"])
0
kantal