2つのリストを交互に組み合わせるPythonの方法は?


88

2つのリストがあり、最初のリストには2番目のリストよりも正確に1つ多いアイテムが含まれていることが保証されています。偶数インデックス値が最​​初のリストから取得され、奇数インデックス値が2番目のリストから取得される新しいリストを作成するための最もPython的な方法を知りたいです。

# example inputs
list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']

# desired output
['f', 'hello', 'o', 'world', 'o']

これは機能しますが、きれいではありません。

list3 = []
while True:
    try:
        list3.append(list1.pop(0))
        list3.append(list2.pop(0))
    except IndexError:
        break

他にどのようにこれを達成できますか?最もPythonicなアプローチは何ですか?



重複ではありません!上記のリンクされた記事で受け入れられた回答は、単一のマージされたリストではなく、タプルのリストを生成します。
Paul Sasik 2010

@Paul:はい、受け入れられた答えは完全な解決策を提供しません。コメントと他の答えを読んでください。質問は基本的に同じであり、他の解決策をここで適用できます。
Felix Kling 2010

3
@フェリックス:私は敬意を表して反対します。確かに、質問は同じ近隣にありますが、実際には重複していません。漠然とした証拠として、ここで考えられる答えを見て、他の質問と比較してください。
Paul Sasik 2010

:これらのチェックアウトstackoverflow.com/questions/7529376/...
wordsforthewise

回答:


110

スライスしてそれを行う1つの方法は次のとおりです。

>>> list1 = ['f', 'o', 'o']
>>> list2 = ['hello', 'world']
>>> result = [None]*(len(list1)+len(list2))
>>> result[::2] = list1
>>> result[1::2] = list2
>>> result
['f', 'hello', 'o', 'world', 'o']

3
ありがとう、ダンカン。スライスするときにステップを指定できることに気づきませんでした。このアプローチで私が気に入っているのは、それがいかに自然に読み取れるかです。1.正しい長さのリストを作成します。2.偶数インデックスにlist1の内容を入力しました。3.奇数インデックスにlist2の内容を入力します。この場合、リストの長さが異なるという事実は問題ではありません。
davidchambers 2010

2
LEN(LIST2)が0または1 -私はそれはときにのみLEN(リスト1)うまくいくと思う
XAN

1
リストが適切な長さである場合は機能しますが、そうでない場合は、元の質問で予想される回答が指定されていません。最も合理的な状況を処理するように簡単に変更できます。たとえば、余分な要素を無視したい場合は、開始する前に長いリストを切り詰めてください。追加の要素をNoneでインターリーブしたい場合は、結果がさらにいくつかのNoneで初期化されていることを確認してください。最後に追加する要素が必要な場合は、それらを無視してから追加します。
ダンカン

1
私も不明確でした。私が言いたかったのは、Duncanのソリューションは、リストされているものの多くとは異なり、リストの長さが等しくないという事実によって複雑にならないということです。確かに、それは限られた範囲の状況にのみ適用可能ですが、私はこのインスタンスで機能する本当にエレガントなソリューションを、任意の2つのリストで機能するあまりエレガントでないソリューションよりも好みます。
davidchambers 2010

1
(len(list1)+ len(list2))の代わりに(2 * len(list1)-1)を使用できます。また、[:: 2]の代わりに[0 :: 2]を使用することもできます。
ロードブリティッシュ

50

itertoolsドキュメントにこれのレシピがあります

from itertools import cycle, islice

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).next for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))

編集:

3より大きいPythonのバージョンの場合。

from itertools import cycle, islice

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))

この方法は、必要以上に複雑だと思います。を使用して、以下のより良いオプションがありますzip_longest
ダブスロー2017年

@Dubslowこの特定のケースでは、ええ、あなたがすでにアクセスできる場合を除いて、これはおそらくやり過ぎです(他の場所のコメントで述べたように)。ただし、他の状況ではいくつかの利点があるかもしれません。このレシピは確かにこの問題のために設計されたものではなく、たまたまそれを解決するだけです。
David Z

1
fyiは動作しなくなったため、itertools ドキュメントのレシピを使用する必要があります.next()
ジョンw。

1
@johnw。を使用する必要があります__next__。ドキュメントに書かれていないので、答えの編集を提案しました。
マリンガランティーヌ

@Marine既存のコードサンプルを変更しただけでいいのですが、自分で修正できます。貢献してくれてありがとう!
David Z

31

これはあなたが望むことをするはずです:

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']

私はあなたの最初の答えが本当に好きでした。質問に完全に対処することはできませんでしたが、同じ長さの2つのリストをマージするエレガントな方法でした。私はあなたの現在の答えで、長さの警告と一緒にそれを保持することを提案します。
Paul Sasik 2010

1
list1が代わりに['f'、 'o'、 'o'、 'd']の場合、その最終項目( 'd')は結果のリストに表示されません(質問の詳細を考えると、これはまったく問題ありません)。これはエレガントなソリューションです!
davidchambers 2010

1
@Markうん(私はそれを賛成しました)、違い(そして他の人が異なる行動を望んでいる場合の制限)を指摘するだけです
cobbal 2010

4
述べられた問題を解決するための+1、そしてそれを簡単に行うためにも:-)私はこのようなことが可能であると考えました。正直なところ、このroundrobin機能はこの状況では一種のやり過ぎだと思います。
David Z

1
結果へのイテレータに残っているもの、あなたが簡単に追加することができます任意のサイズのリストで動作するように:list(itertools.chain(map(next, itertools.cycle(iters)), *iters))
パンダ- 34

29
import itertools
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]

これが最もPython的な方法だと思います。


3
なぜこれが受け入れられた答えではないのですか?これは最も短く、最もpythonicであり、さまざまなリストの長さで機能します。
Jairo Vadillo 2016

5
メソッド名はizip_longestではなくzip_longestです
Jairo Vadillo 2016

1
これに伴う問題は、zip_longestのデフォルトのfillin値Noneが、リストにあると想定されるを上書きする可能性があることです。これを修正するために微調整されたバージョンで編集します
Dubslow 2017年

注:これは、リストに値を持つ要素が含まれている場合False、または-式によってのみ評価されるもの(たとえば、Falseif0場合や、や空のリスト。これは、次の方法で(部分的に)回避できます[x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]。もちろん、リストNoneに保存する必要のある要素が含まれている場合、これはまだ機能しません。この場合、Dubslowがすでに提案しているように、のfillvalue引数を変更する必要がありますzip_longest
der_herr_g

None少なくともPython3.7.6以降、問題はなくなったようです(古いバージョンについてはわかりません)。場合alt_chainのように定義されdef alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue))、その後、list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))正常に戻ります[0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99]
ペイム

17

itertoolsがなく、l1がl2より1アイテム長いと仮定すると:

>>> sum(zip(l1, l2+[0]), ())[:-1]
('f', 'hello', 'o', 'world', 'o')

itertoolsを使用し、リストにNoneが含まれていないと仮定します。

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')

これが私のお気に入りの答えです。とても簡潔です。
mbomb007 2017年

@ anishtain4 zipは、要素のペアをリストからタプルとして受け取ります[(l1[0], l2[0]), (l1[1], l2[1]), ...]sumタプルを連結します:(l1[0], l2[0]) + (l1[1], l2[1]) + ...インターリーブされたリストになります。残りの1行は、zipを機能させるための追加要素を含むl1のパディングであり、そのパディングを取り除くために-1までスライスします。
zart

izip_longest(python 3以降のzip_longest)は+ [0]パディングを必要とせず、リストの長さが一致しない場合は暗黙的にNoneを埋めますが、filter(None, ...bool代わりに使用できます、またはNone.__ne__)0、None、空の文字列などの誤った値を削除します。したがって、2番目の式は最初の式と厳密に同等ではありません。
zart

問題は、どうやってそれをsumやらせたのかということです。そこでの2番目の議論の役割は何ですか?ドキュメントでは、2番目の引数はstartです。
anishtain 45

startのデフォルト値は0であり、0 +(some、tuple)を実行できないため、startは空のタプルに変更されます。
zart

13

質問が2つのリストについて尋ねていることは知っていますが、一方が他方よりも1つの項目を持っているので、この質問を見つける可能性のある他の人のためにこれを置くと思いました。

これは、サイズの異なる2つのリストで機能するように適合されたDuncanのソリューションです。

list1 = ['f', 'o', 'o', 'b', 'a', 'r']
list2 = ['hello', 'world']
num = min(len(list1), len(list2))
result = [None]*(num*2)
result[::2] = list1[:num]
result[1::2] = list2[:num]
result.extend(list1[num:])
result.extend(list2[num:])
result

この出力:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 

6

両方のリストの長さが等しい場合は、次のことができます。

[x for y in zip(list1, list2) for x in y]

最初のリストにはもう1つの要素があるので、事後的に追加できます。

[x for y in zip(list1, list2) for x in y] + [list1[-1]]

2
^これが答えになるはずです。Pythonは過去10年間でよりPythonになりました
Tian

5

これがそれを行うワンライナーです:

list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]


2
これは正しく機能しますが、非常に単純なことを達成するために多くのことを行っているため、私はエレガントではないと思います。このアプローチが非効率的であると言っているのではなく、単に読みにくいというだけです。
davidchambers 2010

2
def combine(list1, list2):
    lst = []
    len1 = len(list1)
    len2 = len(list2)

    for index in range( max(len1, len2) ):
        if index+1 <= len1:
            lst += [list1[index]]

        if index+1 <= len2:
            lst += [list2[index]]

    return lst

可変のデフォルト引数を使用する場合は、十分に注意してください。lstはその後のすべての呼び出しで再利用されるため、これは最初に呼び出されたときにのみ正解を返します。これは、lst = None ...と書く方がよいでしょう。lstがNoneの場合:lst = []ですが、ここにリストされている他のアプローチよりもこのアプローチを選択する説得力のある理由はわかりません。
davidchambers 2010

lstは関数内で定義されているため、ローカル変数も定義されています。潜在的な問題は、異なるリストで関数を呼び出した場合でも、関数を使用するたびにlist1とlist2が再利用されることです。docs.python.org/tutorial/…を
blokeley

1
@blokeley:間違っています。combine(list1 = [...]、list2 = [...])の場合は再利用されます
2010

このソリューションが最初に投稿されたとき、最初の行が読まdef combine(list1, list2, lst=[]):れたので、私のコメントです。しかし、私がそのコメントを提出するまでに、killownは必要な変更を加えていました。
davidchambers 2010

2

これは、上記のCarlos Valienteの貢献に基づいており、複数のアイテムのグループを交互に変更して、すべてのアイテムが出力に存在することを確認するオプションがあります。

A=["a","b","c","d"]
B=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

def cyclemix(xs, ys, n=1):
    for p in range(0,int((len(ys)+len(xs))/n)):
        for g in range(0,min(len(ys),n)):
            yield ys[0]
            ys.append(ys.pop(0))
        for g in range(0,min(len(xs),n)):
            yield xs[0]
            xs.append(xs.pop(0))

print [x for x in cyclemix(A, B, 3)]

これにより、リストAとBがそれぞれ3つの値のグループでインターレースされます。

['a', 'b', 'c', 1, 2, 3, 'd', 'a', 'b', 4, 5, 6, 'c', 'd', 'a', 7, 8, 9, 'b', 'c', 'd', 10, 11, 12, 'a', 'b', 'c', 13, 14, 15]

2

さらに別のPythonワンライナーを購入するのは少し遅いかもしれません。これは、2つのリストのサイズが等しいか等しくない場合に機能します。何の価値もないことの1つは、aとbを変更することです。それが問題である場合は、他の解決策を使用する必要があります。

a = ['f', 'o', 'o']
b = ['hello', 'world']
sum([[a.pop(0), b.pop(0)] for i in range(min(len(a), len(b)))],[])+a+b
['f', 'hello', 'o', 'world', 'o']


1

これは、他のライブラリを使用せずに、リスト内包表記を使用した1つのライナーです。

list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]]

副作用によって最初のlist1の変更を許可する場合、別のアプローチがあります。

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]

1
from itertools import chain
list(chain(*zip('abc', 'def')))  # Note: this only works for lists of equal length
['a', 'd', 'b', 'e', 'c', 'f']

0

最短で停止します:

def interlace(*iters, next = next) -> collections.Iterable:
    """
    interlace(i1, i2, ..., in) -> (
        i1-0, i2-0, ..., in-0,
        i1-1, i2-1, ..., in-1,
        .
        .
        .
        i1-n, i2-n, ..., in-n,
    )
    """
    return map(next, cycle([iter(x) for x in iters]))

確かに、next / __next__メソッドの解決はより速いかもしれません。


0

これは厄介ですが、リストのサイズに関係なく機能します。

list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]

0

別の質問への回答に触発された複数のワンライナー:

import itertools

list(itertools.chain.from_iterable(itertools.izip_longest(list1, list2, fillvalue=object)))[:-1]

[i for l in itertools.izip_longest(list1, list2, fillvalue=object) for i in l if i is not object]

[item for sublist in map(None, list1, list2) for item in sublist][:-1]

0

numpyはどうですか?文字列でも機能します。

import numpy as np

np.array([[a,b] for a,b in zip([1,2,3],[2,3,4,5,6])]).ravel()

結果:

array([1, 2, 2, 3, 3, 4])

0

機能的で不変の方法での代替手段(Python 3):

from itertools import zip_longest
from functools import reduce

reduce(lambda lst, zipped: [*lst, *zipped] if zipped[1] != None else [*lst, zipped[0]], zip_longest(list1, list2),[])

-1

私は簡単にやります:

chain.from_iterable( izip( list1, list2 ) )

追加のストレージを必要とせずにイテレータを作成します。


1
本当に簡単ですが、同じ長さのリストでのみ機能します。
Jochen Ritzel 2010

chain.from_iterable(izip(list1, list2), list1[len(list2):])ここで尋ねられた特定の問題に対してそれを修正することができます... list1はより長いものであるはずです。
Jochen Ritzel 2010

ええ、でも私は、任意の長さのコンテナで機能するソリューションか、上記で提案されたソリューションに譲るソリューションを考え出したいと思います。
ウィーティーズ2010

-2

私は年を取りすぎているので、リスト内包表記を理解できません。

import operator
list3 = reduce(operator.add, zip(list1, list2))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.