2つの文字列をインターリーブする最もPython的な方法


115

2つの文字列をメッシュ化する最もパイソン的な方法は何ですか?

例えば:

入力:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

出力:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

2
ここでの回答は、主に2つの入力文字列が同じ長さであると想定しています。それは安全な仮定ですか、それとも処理する必要がありますか?
SuperBiasedMan 2016年

@SuperBiasedMan解決策がある場合、すべての条件を処理する方法を確認すると役立つ場合があります。それは質問に関連していますが、具体的には私のケースではありません。
Brandon Deo 2016年

3
@drexxとにかくトップアンサーがその解決策についてコメントしたので、私はそれを彼らの投稿に編集して包括的なものにしました。
SuperBiasedMan 2016年

回答:


127

私にとって、最もpythonic *の方法は次のとおりです。これはほとんど同じことを行いますが、+演算子を使用して各文字列の個々の文字を連結します。

res = "".join(i + j for i, j in zip(u, l))
print(res)
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

また、2つのjoin()呼び出しを使用するよりも高速です。

In [5]: l1 = 'A' * 1000000; l2 = 'a' * 1000000

In [6]: %timeit "".join("".join(item) for item in zip(l1, l2))
1 loops, best of 3: 442 ms per loop

In [7]: %timeit "".join(i + j for i, j in zip(l1, l2))
1 loops, best of 3: 360 ms per loop

より高速なアプローチは存在しますが、コードを難読化することがよくあります。

注: 2つの入力文字列が同じ長さではない場合、長い文字列は切り捨てられzip、短い文字列の終わりで反復が停止します。この場合、代わりのzip一つは使用すべきであるzip_longestizip_longestからのPython 2)itertools両方の文字列が完全に排出されることを確実にするためにモジュール。


* Zen of Pythonから引用:読みやすさが重要です。
Pythonic = 私にとって読みやすさi + j少なくとも私の目には、視覚的に解析しやすいだけです。


1
ただし、n個の文字列のコーディングはO(n)です。それでも、nが小さければ問題ありません。
TigerhawkT3

あなたのジェネレータはおそらく結合よりもオーバーヘッドを引き起こしています。
Padraic Cunningham 2016

5
実行して"".join([i + j for i, j in zip(l1, l2)])、それが間違いなく最速になります
Padraic Cunningham 2016

6
"".join(map("".join, zip(l1, l2)))はより高速ではありますが、必ずしもよりパイソンではありません。
Aleksi Torhamo

63

より速い代替

別の方法:

res = [''] * len(u) * 2
res[::2] = u
res[1::2] = l
print(''.join(res))

出力:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

速度

それはより速いように見えます:

%%timeit
res = [''] * len(u) * 2
res[::2] = u
res[1::2] = l
''.join(res)

100000 loops, best of 3: 4.75 µs per loop

これまでの最速のソリューションよりも:

%timeit "".join(list(chain.from_iterable(zip(u, l))))

100000 loops, best of 3: 6.52 µs per loop

より大きな文字列についても:

l1 = 'A' * 1000000; l2 = 'a' * 1000000

%timeit "".join(list(chain.from_iterable(zip(l1, l2))))
1 loops, best of 3: 151 ms per loop


%%timeit
res = [''] * len(l1) * 2
res[::2] = l1
res[1::2] = l2
''.join(res)

10 loops, best of 3: 92 ms per loop

Python 3.5.1。

長さが異なる文字列のバリエーション

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

短い方が長さを決定します(zip()同等)

min_len = min(len(u), len(l))
res = [''] * min_len * 2 
res[::2] = u[:min_len]
res[1::2] = l[:min_len]
print(''.join(res))

出力:

AaBbCcDdEeFfGgHhIiJjKkLl

長い方が長さを決定します(itertools.zip_longest(fillvalue='')同等)

min_len = min(len(u), len(l))
res = [''] * min_len * 2 
res[::2] = u[:min_len]
res[1::2] = l[:min_len]
res += u[min_len:] + l[min_len:]
print(''.join(res))

出力:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ

49

join()zip()

>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

17
または''.join(itertools.chain.from_iterable(zip(u, l)))
Blender

1
zip短いリストが完全に繰り返されたときに停止するため、一方が他方より短い場合、これはリストを切り捨てます。
SuperBiasedMan 2016年

5
@SuperBiasedMan-はい。itertools.zip_longest問題が発生した場合に使用できます。
TigerhawkT3

18

Python 2では、小さな文字列のリストスライスの速度が最大3倍、長い文字列の場合は最大30倍という、はるかに高速な方法です。

res = bytearray(len(u) * 2)
res[::2] = u
res[1::2] = l
str(res)

ただし、これはPython 3では機能しません。あなたは次のようなものを実装することができます

res = bytearray(len(u) * 2)
res[::2] = u.encode("ascii")
res[1::2] = l.encode("ascii")
res.decode("ascii")

しかし、それまでに小さな文字列のリストスライシングの利点は失われており(長い文字列の速度はまだ20倍です)、これは非ASCII文字ではまだ機能しません。

FWIW、これを大量の文字列で実行しいて、すべてのサイクルが必要、何らかの理由でPython文字列を使用する必要がある場合...これを行う方法は次のとおりです。

res = bytearray(len(u) * 4 * 2)

u_utf32 = u.encode("utf_32_be")
res[0::8] = u_utf32[0::4]
res[1::8] = u_utf32[1::4]
res[2::8] = u_utf32[2::4]
res[3::8] = u_utf32[3::4]

l_utf32 = l.encode("utf_32_be")
res[4::8] = l_utf32[0::4]
res[5::8] = l_utf32[1::4]
res[6::8] = l_utf32[2::4]
res[7::8] = l_utf32[3::4]

res.decode("utf_32_be")

小さい型の一般的なケースを特別なケースにすることも役立ちます。FWIW、これは長い文字列のリストスライスの速度の3倍にすぎず、小さな文字列の場合は4〜5倍遅くなります。

どちらの方法でもjoin解決策を好みますが、タイミングは他で言及されているので、私も参加したほうがよいと思いました。


16

最速の方法が必要な場合は、itertools以下と組み合わせることができますoperator.add

In [36]: from operator import add

In [37]: from itertools import  starmap, izip

In [38]: timeit "".join([i + j for i, j in uzip(l1, l2)])
1 loops, best of 3: 142 ms per loop

In [39]: timeit "".join(starmap(add, izip(l1,l2)))
1 loops, best of 3: 117 ms per loop

In [40]: timeit "".join(["".join(item) for item in zip(l1, l2)])
1 loops, best of 3: 196 ms per loop

In [41]:  "".join(starmap(add, izip(l1,l2))) ==  "".join([i + j   for i, j in izip(l1, l2)]) ==  "".join(["".join(item) for item in izip(l1, l2)])
Out[42]: True

しかし、結合するizipchain.from_iterable再び高速になります

In [2]: from itertools import  chain, izip

In [3]: timeit "".join(chain.from_iterable(izip(l1, l2)))
10 loops, best of 3: 98.7 ms per loop

との間にも大きな違いが chain(*ありchain.from_iterable(...ます。

In [5]: timeit "".join(chain(*izip(l1, l2)))
1 loops, best of 3: 212 ms per loop

ジョイン付きのジェネレータなどというものはありません。Pythonは最初にコンテンツを使用してリストを作成するため、1つを渡すと常に遅くなります。1つは必要なサイズを把握するために、もう1つは実際に行うために、データを2回渡すためです。ジェネレーターを使用して不可能である結合:

join.h

 /* Here is the general case.  Do a pre-pass to figure out the total
  * amount of space we'll need (sz), and see whether all arguments are
  * bytes-like.
   */

また、長さが異なる文字列があり、データを失いたくない場合は、izip_longestを使用できます。

In [22]: from itertools import izip_longest    
In [23]: a,b = "hlo","elworld"

In [24]:  "".join(chain.from_iterable(izip_longest(a, b,fillvalue="")))
Out[24]: 'helloworld'

python 3の場合は zip_longest

しかし、python2の場合、veedracの提案は断然最速です。

In [18]: %%timeit
res = bytearray(len(u) * 2)
res[::2] = u
res[1::2] = l
str(res)
   ....: 
100 loops, best of 3: 2.68 ms per loop

2
なんでlist?不要です
コッパーフィールド

1
私のテストによると、あなたは中間リストを作成する時間を失い、それは反復子を使用する目的を無効にします。Timeit the "".join(list(...))Give Me 6.715280318699769およびTimeit the "".join(starmap(...))Give Me 6.46332361384313
Copperfield

1
次に、何がマシンに依存していますか?テストをどこで実行しても、まったく同じ結果"".join(list(starmap(add, izip(l1,l2))))が得られるのはに比べて遅いため"".join(starmap(add, izip(l1,l2)))です。私は自分のマシンでpython 2.7.11とpython 3.5.1のテストをwww.python.orgの仮想コンソールでもpython 3.4.3で実行し、すべて同じことを言って、何度か実行します。同じ
コッパーフィールド

私が読んだところ、私が見ているのは、渡されたものに関係なく、内部的に常にバッファ変数内にリストが作成されるということです。そのため、リストにNOを指定する理由はそれ以上ありません
Copperfield

@Copperfield、あなたはリストの呼び出しについて話しているのですか、それともリストを渡すのですか?
Padraic Cunningham 2016

12

mapand を使用してこれを行うこともできますoperator.add

from operator import add

u = 'AAAAA'
l = 'aaaaa'

s = "".join(map(add, u, l))

出力

'AaAaAaAaAa'

マップが行うことは、最初の反復可能オブジェクトのすべての要素uと2番目の反復可能要素の最初の要素を取得lし、最初の引数として指定された関数を適用することaddです。その後、参加するだけで参加します。


9

ジムの答えは素晴らしいですが、いくつかのインポートを気にしなければ、ここに私のお気に入りのオプションがあります。

from functools import reduce
from operator import add

reduce(add, map(add, u, l))

7
彼はほとんどのハスケリックではなく、ほとんどのPythonicを言いました;)
Curt

7

これらの提案の多くは、文字列が同じ長さであることを前提としています。多分それはすべての合理的なユースケースをカバーしますが、少なくとも私にとっては、長さが異なる文字列にも対応したいと思うかもしれません。または、メッシュが次のように機能するのは私だけだと思いますか?

u = "foobar"
l = "baz"
mesh(u,l) = "fboaozbar"

これを行う1つの方法は次のとおりです。

def mesh(a,b):
    minlen = min(len(a),len(b))
    return "".join(["".join(x+y for x,y in zip(a,b)),a[minlen:],b[minlen:]])

5

2つforのs を使用するのが好きです。変数名は、何が起こっているのかについてのヒント/リマインダーを与えることができます。

"".join(char for pair in zip(u,l) for char in pair)

4

別のより基本的なアプローチを追加するだけです。

st = ""
for char in u:
    st = "{0}{1}{2}".format( st, char, l[ u.index( char ) ] )

4

O(1)の努力でn個の文字列を処理するために、ここでは二重リスト理解の答えを考慮しないために、少しun-pythonicに感じます:

"".join(c for cs in itertools.zip_longest(*all_strings) for c in cs)

ここall_stringsで、インターリーブする文字列のリストです。あなたの場合は、all_strings = [u, l]。完全な使用例は次のようになります。

import itertools
a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
b = 'abcdefghijklmnopqrstuvwxyz'
all_strings = [a,b]
interleaved = "".join(c for cs in itertools.zip_longest(*all_strings) for c in cs)
print(interleaved)
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

多くの答えのように、最速ですか?おそらくそうではありませんが、シンプルで柔軟です。また、あまり複雑にしないと、これは受け入れられた回答よりもわずかに速くなります(一般に、文字列の追加はPythonでは少し遅いです)。

In [7]: l1 = 'A' * 1000000; l2 = 'a' * 1000000;

In [8]: %timeit "".join(a + b for i, j in zip(l1, l2))
1 loops, best of 3: 227 ms per loop

In [9]: %timeit "".join(c for cs in zip(*(l1, l2)) for c in cs)
1 loops, best of 3: 198 ms per loop

ただし、最速の回答ほど速くはありません。これは、同じデータとコンピューターで50.3 msを得たものです
scnerd

3

現在の主要なソリューションよりも高速で短い可能性があります。

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

res = "".join(chain(*zip(u, l)))

戦略的な速度は、Cレベルで可能な限り多くを行うことです。同じzip_longest()が不均一な文字列を修正し、それはchain()と同じモジュールから出てくるので、そこにあまり多くのポイントを指定することはできません!

途中で私が思いついた他の解決策:

res = "".join(u[x] + l[x] for x in range(len(u)))

res = "".join(k + l[i] for i, k in enumerate(u))

3

あなたが使用することができます1iteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

from iteration_utilities import roundrobin
''.join(roundrobin(u, l))
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

またはManyIterables同じパッケージのクラス:

from iteration_utilities import ManyIterables
ManyIterables(u, l).roundrobin().as_string()
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

1これは私が書いたサードパーティライブラリからのものですiteration_utilities


2

私はzip()を使用して、読みやすく簡単な方法を取得します。

result = ''
for cha, chb in zip(u, l):
    result += '%s%s' % (cha, chb)

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.