Jaki jest najbardziej pytoniczny sposób połączenia dwóch strun?
Na przykład:
Wejście:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Wynik:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Jaki jest najbardziej pytoniczny sposób połączenia dwóch strun?
Na przykład:
Wejście:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Wynik:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Odpowiedzi:
Dla mnie najbardziej pythoniczny * sposób to następujący, który prawie robi to samo, ale używa +operatora do łączenia poszczególnych znaków w każdym ciągu:
res = "".join(i + j for i, j in zip(u, l))
print(res)
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Jest również szybszy niż korzystanie z dwóch join()połączeń:
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
Istnieją szybsze metody, ale często zaciemniają one kod.
Uwaga: Jeśli dwa ciągi wejściowe nie mają takiej samej długości, dłuższy zostanie obcięty jako zipzatrzymanie iteracji na końcu krótszego ciągu. W tym przypadku zamiast zipjednego należy użyć zip_longest( izip_longestw Pythonie 2) z itertoolsmodułu, aby upewnić się, że oba ciągi są całkowicie wyczerpane.
* Cytat z Zen of Python : liczy się czytelność .
Pythonic = czytelność dla mnie; i + jjest po prostu łatwiej analizowany wizualnie, przynajmniej dla moich oczu.
"".join([i + j for i, j in zip(l1, l2)])i na pewno będzie najszybszy
"".join(map("".join, zip(l1, l2)))jest jeszcze szybszy, choć niekoniecznie bardziej pythonowy.
Inny sposób:
res = [''] * len(u) * 2
res[::2] = u
res[1::2] = l
print(''.join(res))
Wynik:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Wygląda na to, że jest szybszy:
%%timeit
res = [''] * len(u) * 2
res[::2] = u
res[1::2] = l
''.join(res)
100000 loops, best of 3: 4.75 µs per loop
niż najszybsze dotychczas rozwiązanie:
%timeit "".join(list(chain.from_iterable(zip(u, l))))
100000 loops, best of 3: 6.52 µs per loop
Również w przypadku większych strun:
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()ekwiwalent)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))
Wynik:
AaBbCcDdEeFfGgHhIiJjKkLl
itertools.zip_longest(fillvalue='')ekwiwalent)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))
Wynik:
AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
Z join()i zip().
>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
''.join(itertools.chain.from_iterable(zip(u, l)))
zipzatrzymanie, gdy krótsza lista została w pełni iterowana.
itertools.zip_longestmożna użyć, jeśli stanie się to problemem.
W Pythonie 2, zdecydowanie szybszym sposobem robienia rzeczy, przy ~ 3-krotnej szybkości cięcia list dla małych łańcuchów i ~ 30-krotnie dla długich, jest
res = bytearray(len(u) * 2)
res[::2] = u
res[1::2] = l
str(res)
To jednak nie zadziała w Pythonie 3. Możesz zaimplementować coś takiego
res = bytearray(len(u) * 2)
res[::2] = u.encode("ascii")
res[1::2] = l.encode("ascii")
res.decode("ascii")
ale do tego czasu straciłeś już korzyści wynikające z krojenia list dla małych ciągów (wciąż jest to 20 razy szybsze dla długich łańcuchów), a to jeszcze nie działa nawet dla znaków spoza ASCII.
FWIW, jeśli są w ten sposób na masywne ciągi i trzeba w każdym cyklu, a z jakiegoś powodu trzeba użyć ciągi Pythona ... oto jak to zrobić:
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")
Pomoże też w tym specjalna obudowa, stosowana w zwykłym przypadku mniejszych typów. FWIW, jest to tylko 3-krotnie szybsze cięcie list dla długich ciągów i współczynnik 4 do 5 wolniejsze dla małych.
Tak czy inaczej wolę joinrozwiązania, ale skoro w innym miejscu wspomniano o czasach , pomyślałem, że równie dobrze mogę się przyłączyć.
Jeśli chcesz najszybszy sposób, możesz połączyć itertools z 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
Ale łączenie izipi chain.from_iterableznowu jest szybsze
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
Istnieje również zasadnicza różnica między
chain(*i chain.from_iterable(....
In [5]: timeit "".join(chain(*izip(l1, l2)))
1 loops, best of 3: 212 ms per loop
Nie ma czegoś takiego jak generator z łączeniem, przepuszczenie jednego będzie zawsze wolniejsze, ponieważ Python najpierw zbuduje listę przy użyciu zawartości, ponieważ wykonuje dwa przejścia przez dane, jeden do określenia potrzebnego rozmiaru, a drugi do faktycznego wykonania połączenie, które nie byłoby możliwe przy użyciu generatora:
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.
*/
Również jeśli masz ciągi o innej długości i nie chcesz stracić danych, możesz użyć 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'
W przypadku Pythona 3 nazywa się to zip_longest
Ale w przypadku Pythona2 sugestia Veedrac jest zdecydowanie najszybsza:
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
list?? jest niepotrzebne
"".join(list(...))give me 6.715280318699769 and timeit the "".join(starmap(...))give me 6.46332361384313
"".join(list(starmap(add, izip(l1,l2))))jest wolniejszy niż "".join(starmap(add, izip(l1,l2))). Uruchamiam test na moim komputerze w pythonie 2.7.11 i pythonie 3.5.1 nawet w wirtualnej konsoli www.python.org z pythonem 3.4.3 i wszyscy mówią to samo i uruchamiam go kilka razy i zawsze to samo
Możesz to również zrobić za pomocą mapi operator.add:
from operator import add
u = 'AAAAA'
l = 'aaaaa'
s = "".join(map(add, u, l))
Wyjście :
'AaAaAaAaAa'
To, co robi mapa, to pobiera każdy element z pierwszej iterowalnej ui pierwsze elementy z drugiej iterowalnej li stosuje funkcję dostarczoną jako pierwszy argument add. Następnie dołącz po prostu dołącz do nich.
Wiele z tych sugestii zakłada, że struny mają jednakową długość. Może to obejmuje wszystkie rozsądne przypadki użycia, ale przynajmniej wydaje mi się, że możesz chcieć pomieścić również struny o różnych długościach. A może tylko ja uważam, że siatka powinna działać trochę tak:
u = "foobar"
l = "baz"
mesh(u,l) = "fboaozbar"
Można to zrobić w następujący sposób:
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:]])
Lubię używać dwóch for, nazwy zmiennych mogą dać wskazówkę / przypomnienie o tym, co się dzieje:
"".join(char for pair in zip(u,l) for char in pair)
Trochę nie-pythonicznie wydaje się nie brać pod uwagę odpowiedzi na podwójne rozumienie listy tutaj, aby obsłużyć n string z wysiłkiem O (1):
"".join(c for cs in itertools.zip_longest(*all_strings) for c in cs)
gdzie all_stringsjest lista ciągów, które chcesz przeplatać. W twoim przypadku all_strings = [u, l]. Pełny przykład użycia wyglądałby następująco:
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'
Jak wiele odpowiedzi, najszybciej? Prawdopodobnie nie, ale proste i elastyczne. Ponadto, bez zbyt dużej złożoności, jest to nieco szybsze niż akceptowana odpowiedź (ogólnie dodawanie ciągów jest nieco powolne w Pythonie):
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
Potencjalnie szybsze i krótsze niż obecne wiodące rozwiązanie:
from itertools import chain
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
res = "".join(chain(*zip(u, l)))
Jeśli chodzi o szybkość strategii, należy zrobić jak najwięcej na poziomie C. Ta sama poprawka zip_longest () dla nierównych łańcuchów i wychodziłaby z tego samego modułu co chain (), więc nie mogę tam dać mi zbyt wielu punktów!
Inne rozwiązania, które wymyśliłem po drodze:
res = "".join(u[x] + l[x] for x in range(len(u)))
res = "".join(k + l[i] for i, k in enumerate(u))
Możesz użyć 1iteration_utilities.roundrobin
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
from iteration_utilities import roundrobin
''.join(roundrobin(u, l))
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
lub ManyIterablesklasa z tego samego pakietu:
from iteration_utilities import ManyIterables
ManyIterables(u, l).roundrobin().as_string()
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
1 Jest to z biblioteki trzeciej mam napisane: iteration_utilities.