Cara paling pythonic untuk menyisipkan dua string

115

Apa cara paling pythonic untuk menyatukan dua string?

Sebagai contoh:

Memasukkan:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Keluaran:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Brandon Deo
sumber
2
Jawaban di sini sebagian besar mengasumsikan bahwa dua string input Anda akan memiliki panjang yang sama. Apakah itu asumsi yang aman atau Anda membutuhkannya untuk ditangani?
SuperBiasedMan
@SuperBiasedMan Mungkin berguna untuk melihat bagaimana menangani semua kondisi jika Anda memiliki solusi. Ini relevan dengan pertanyaan, tetapi tidak secara khusus untuk kasus saya.
Brandon Deo
3
@drexx Penjawab teratas berkomentar dengan solusi untuk itu, jadi saya hanya mengeditnya ke dalam posting mereka sehingga komprehensif.
SuperBiasedMan

Jawaban:

127

Bagi saya, cara paling pythonic * adalah yang berikut ini yang cukup banyak melakukan hal yang sama tetapi menggunakan +operator untuk menggabungkan karakter individu di setiap string:

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

Ini juga lebih cepat daripada menggunakan dua join()panggilan:

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

Ada pendekatan yang lebih cepat, tetapi mereka sering mengaburkan kode.

Catatan: Jika kedua string input tidak memiliki panjang yang sama maka string yang lebih panjang akan dipotong karena zipberhenti melakukan iterasi di ujung string yang lebih pendek. Dalam kasus ini, Anda zipharus menggunakan zip_longest( izip_longestdalam Python 2) dari itertoolsmodul untuk memastikan bahwa kedua string habis sepenuhnya.


* Untuk mengambil kutipan dari Zen of Python : Jumlah keterbacaan .
Pythonic = keterbacaan bagi saya; i + jhanya diurai secara visual dengan lebih mudah, setidaknya untuk mata saya.

Dimitris Fasarakis Hilliard
sumber
1
Upaya pengkodean untuk n string adalah O (n). Tetap saja, itu bagus selama n kecil.
TigerhawkT3
Generator Anda mungkin menyebabkan lebih banyak overhead daripada gabungan.
Padraic Cunningham
5
lari "".join([i + j for i, j in zip(l1, l2)])dan itu pasti akan menjadi yang tercepat
Padraic Cunningham
6
"".join(map("".join, zip(l1, l2)))bahkan lebih cepat, meski belum tentu lebih pythonic.
Aleksi Torhamo
63

Alternatif Lebih Cepat

Cara lain:

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

Keluaran:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Mempercepat

Sepertinya lebih cepat:

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

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

daripada solusi tercepat sejauh ini:

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

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

Juga untuk string yang lebih besar:

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.

Variasi untuk string dengan panjang berbeda

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

Yang lebih pendek menentukan panjang ( zip()setara)

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

Keluaran:

AaBbCcDdEeFfGgHhIiJjKkLl

Yang lebih panjang menentukan panjang ( itertools.zip_longest(fillvalue='')setara)

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

Keluaran:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
Mike Müller
sumber
49

Dengan join()dan zip().

>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
TigerhawkT3
sumber
17
Atau''.join(itertools.chain.from_iterable(zip(u, l)))
Blender
1
Ini akan memotong daftar jika salah satu lebih pendek dari yang lain, karena zipberhenti ketika daftar yang lebih pendek telah diulangi sepenuhnya.
SuperBiasedMan
5
@SuperBiasedMan - Ya. itertools.zip_longestdapat digunakan jika menjadi masalah.
TigerhawkT3
18

Pada Python 2, dengan jauh cara yang lebih cepat untuk melakukan hal-hal, di ~ 3x kecepatan daftar mengiris untuk string kecil dan ~ 30x untuk yang panjang, adalah

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

Ini tidak akan berfungsi pada Python 3. Anda bisa menerapkan sesuatu seperti

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

tetapi saat itu Anda sudah kehilangan keuntungan atas pemotongan daftar untuk string kecil (kecepatannya masih 20x untuk string panjang) dan ini bahkan belum berfungsi untuk karakter non-ASCII.

FWIW, jika Anda sedang melakukan hal ini pada string besar dan perlu setiap siklus, dan untuk beberapa alasan harus menggunakan Python string ... berikut adalah cara untuk melakukannya:

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

Casing khusus, casing umum dari jenis yang lebih kecil juga akan membantu. FWIW, ini hanya 3x kecepatan pemotongan daftar untuk string panjang dan faktor 4 hingga 5 lebih lambat untuk string kecil.

Bagaimanapun saya lebih suka joinsolusinya, tetapi karena pengaturan waktu disebutkan di tempat lain, saya pikir saya sebaiknya bergabung.

Veedrac
sumber
16

Jika Anda menginginkan cara tercepat, Anda dapat menggabungkan itertools dengan 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

Tapi menggabungkan izipdan chain.from_iterablelebih cepat lagi

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

Ada juga perbedaan substansial antara chain(*dan chain.from_iterable(....

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

Tidak ada yang namanya generator dengan join, melewatkan satu akan selalu lebih lambat karena python pertama-tama akan membuat daftar menggunakan konten karena ia melakukan dua melewati data, satu untuk mengetahui ukuran yang dibutuhkan dan satu untuk benar-benar melakukannya gabungan yang tidak mungkin dilakukan menggunakan generator:

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.
   */

Juga jika Anda memiliki panjang string yang berbeda dan Anda tidak ingin kehilangan data, Anda dapat menggunakan 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'

Untuk python 3 itu disebut zip_longest

Tetapi untuk python2, saran veedrac sejauh ini adalah yang tercepat:

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
Padraic Cunningham
sumber
2
kenapa list?? tidak dibutuhkan
Copperfield
1
tidak menurut pengujian saya, Anda kehilangan waktu membuat daftar perantara dan itu mengalahkan tujuan penggunaan iterator. Timeit "".join(list(...))memberi saya 6.715280318699769 dan timeit "".join(starmap(...))memberi saya 6.46332361384313
Copperfield
1
lalu apa, tergantung mesin ?? karena di mana pun saya menjalankan tes, saya mendapatkan hasil yang persis sama "".join(list(starmap(add, izip(l1,l2))))lebih lambat dari "".join(starmap(add, izip(l1,l2))). Saya menjalankan tes di mesin saya di python 2.7.11 dan di python 3.5.1 bahkan di konsol virtual www.python.org dengan python 3.4.3 dan semua mengatakan hal yang sama dan saya menjalankannya beberapa kali dan selalu sama
Copperfield
Saya membaca dan saya melihat bahwa ia membuat daftar secara internal sepanjang waktu dalam variabel buffer terlepas dari apa yang Anda berikan padanya, jadi semakin banyak alasan untuk TIDAK memberikan daftar
Copperfield
@ Copperfield, apakah Anda berbicara tentang panggilan daftar atau melewati daftar?
Padraic Cunningham
12

Anda juga bisa melakukan ini dengan menggunakan mapdan operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Keluaran :

'AaAaAaAaAa'

Apa yang dilakukan map adalah mengambil setiap elemen dari iterable pertama udan elemen pertama dari iterable kedua ldan menerapkan fungsi yang disediakan sebagai argumen pertama add. Kemudian bergabunglah bergabung saja dengan mereka.

akar
sumber
9

Jawaban Jim bagus, tapi inilah pilihan favorit saya, jika Anda tidak keberatan dengan beberapa impor:

from functools import reduce
from operator import add

reduce(add, map(add, u, l))
rajutan
sumber
7
Dia mengatakan kebanyakan Pythonic, bukan kebanyakan Haskellic;)
Curt
7

Banyak dari saran ini mengasumsikan string memiliki panjang yang sama. Mungkin itu mencakup semua kasus penggunaan yang wajar, tetapi setidaknya bagi saya tampaknya Anda mungkin ingin mengakomodasi string dengan panjang yang berbeda juga. Atau apakah saya satu-satunya yang berpikir mesh harus bekerja seperti ini:

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

Salah satu cara untuk melakukannya adalah sebagai berikut:

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:]])
Christofer Ohlsson
sumber
5

Saya suka menggunakan dua fors, nama variabel dapat memberikan petunjuk / pengingat tentang apa yang sedang terjadi:

"".join(char for pair in zip(u,l) for char in pair)
Neal Fultz
sumber
4

Hanya untuk menambahkan pendekatan lain yang lebih mendasar:

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

Terasa agak tidak pythonic untuk tidak mempertimbangkan jawaban pemahaman daftar ganda di sini, untuk menangani string n dengan usaha O (1):

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

di mana all_stringsdaftar string yang ingin Anda sisipkan. Dalam kasus Anda all_strings = [u, l],. Contoh penggunaan lengkap akan terlihat seperti ini:

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'

Seperti banyak jawaban, tercepat? Mungkin tidak, tapi sederhana dan fleksibel. Juga, tanpa terlalu banyak kerumitan tambahan, ini sedikit lebih cepat daripada jawaban yang diterima (secara umum, penambahan string agak lambat di 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
scnerd
sumber
Namun, masih tidak secepat jawaban tercepat: yang mendapat 50,3 md pada data dan komputer yang sama ini
scnerd
3

Berpotensi lebih cepat dan lebih pendek dari solusi terdepan saat ini:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

Strategi yang bijak dengan kecepatan adalah melakukan sebanyak mungkin pada level C. Zip_longest () yang sama untuk string yang tidak rata dan itu akan keluar dari modul yang sama dengan chain () jadi tidak bisa memberi saya terlalu banyak poin di sana!

Solusi lain yang saya temukan selama ini:

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

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

Anda bisa menggunakan 1iteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

atau ManyIterableskelas dari paket yang sama:

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

1 ini adalah dari pihak ketiga perpustakaan saya telah menulis: iteration_utilities.

MSeifert
sumber
2

Saya akan menggunakan zip () untuk mendapatkan cara yang mudah dan mudah dibaca:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
lembah
sumber