Qual é a maneira mais pythônica de unir duas cordas?
Por exemplo:
Entrada:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Resultado:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Qual é a maneira mais pythônica de unir duas cordas?
Por exemplo:
Entrada:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Resultado:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Respostas:
Para mim, a maneira mais pythônica * é a seguinte, que faz praticamente a mesma coisa, mas usa o +
operador para concatenar os caracteres individuais em cada string:
res = "".join(i + j for i, j in zip(u, l))
print(res)
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Também é mais rápido do que usar duas join()
chamadas:
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
Existem abordagens mais rápidas, mas muitas vezes ofuscam o código.
Nota: Se as duas strings de entrada não tiverem o mesmo comprimento, a mais longa será truncada, pois zip
para de iterar no final da string mais curta. Neste caso, em vez de zip
um, deve-se usar zip_longest
( izip_longest
no Python 2) do itertools
módulo para garantir que ambas as strings estejam totalmente esgotadas.
* Para fazer uma citação do Zen do Python : Contagens de legibilidade .
Pythônico = legibilidade para mim; i + j
é apenas analisado visualmente com mais facilidade, pelo menos para os meus olhos.
"".join([i + j for i, j in zip(l1, l2)])
e será definitivamente o mais rápido
"".join(map("".join, zip(l1, l2)))
é ainda mais rápido, embora não necessariamente mais python.
Outra maneira:
res = [''] * len(u) * 2
res[::2] = u
res[1::2] = l
print(''.join(res))
Resultado:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Parece que é mais rápido:
%%timeit
res = [''] * len(u) * 2
res[::2] = u
res[1::2] = l
''.join(res)
100000 loops, best of 3: 4.75 µs per loop
do que a solução mais rápida até agora:
%timeit "".join(list(chain.from_iterable(zip(u, l))))
100000 loops, best of 3: 6.52 µs per loop
Também para strings maiores:
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()
equivalente)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))
Resultado:
AaBbCcDdEeFfGgHhIiJjKkLl
itertools.zip_longest(fillvalue='')
equivalente)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))
Resultado:
AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
Com join()
e zip()
.
>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
''.join(itertools.chain.from_iterable(zip(u, l)))
zip
para quando a lista menor tiver sido totalmente iterada.
itertools.zip_longest
pode ser usado se se tornar um problema.
No Python 2, de longe a maneira mais rápida de fazer as coisas, em ~ 3x a velocidade do fatiamento de lista para strings pequenas e ~ 30x para strings longas, é
res = bytearray(len(u) * 2)
res[::2] = u
res[1::2] = l
str(res)
Isso não funcionaria no Python 3, no entanto. Você poderia implementar algo como
res = bytearray(len(u) * 2)
res[::2] = u.encode("ascii")
res[1::2] = l.encode("ascii")
res.decode("ascii")
mas, a essa altura, você já perdeu os ganhos com o fatiamento de lista para strings pequenas (ainda é 20x a velocidade para strings longas) e isso nem funciona para caracteres não ASCII ainda.
FWIW, se você está fazendo isso em strings massivas e precisa de cada ciclo e , por algum motivo, precisa usar strings Python ... veja como fazer:
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")
A embalagem especial, o caso comum de tipos menores, também ajudará. FWIW, esta é apenas 3x a velocidade de corte de lista para cordas longas e um fator de 4 a 5 mais lento para cordas pequenas.
De qualquer forma, prefiro as join
soluções, mas como os horários foram mencionados em outro lugar, achei melhor participar.
Se você quiser a maneira mais rápida, pode combinar itertools com 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
Mas combinar izip
e chain.from_iterable
é mais rápido novamente
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
Também há uma diferença substancial entre
chain(*
e chain.from_iterable(...
.
In [5]: timeit "".join(chain(*izip(l1, l2)))
1 loops, best of 3: 212 ms per loop
Não existe gerador com junção, a passagem de um sempre será mais lenta, pois o python primeiro construirá uma lista usando o conteúdo porque faz duas passagens sobre os dados, uma para descobrir o tamanho necessário e outra para realmente fazer a junção que não seria possível usando um gerador:
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.
*/
Além disso, se você tiver strings de comprimento diferente e não quiser perder dados, pode usar 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'
Para python 3 é chamado zip_longest
Mas para python2, a sugestão de veedrac é de longe a mais rápida:
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
?? é desnecessário
"".join(list(...))
para me dar 6,715280318699769 e timeit "".join(starmap(...))
para me dar 6,46332361384313
"".join(list(starmap(add, izip(l1,l2))))
é mais lento do que "".join(starmap(add, izip(l1,l2)))
. Eu executo o teste em minha máquina em python 2.7.11 e em python 3.5.1 até mesmo no console virtual de www.python.org com python 3.4.3 e todos dizem o mesmo e eu o executo algumas vezes e sempre o mesmo
Você também pode fazer isso usando map
e operator.add
:
from operator import add
u = 'AAAAA'
l = 'aaaaa'
s = "".join(map(add, u, l))
Produto :
'AaAaAaAaAa'
O que o mapa faz é pegar cada elemento do primeiro iterável u
e os primeiros elementos do segundo iterável l
e aplicar a função fornecida como o primeiro argumento add
. Em seguida, junte-se a eles.
Muitas dessas sugestões assumem que as strings têm o mesmo comprimento. Talvez isso cubra todos os casos de uso razoáveis, mas pelo menos para mim parece que você pode querer acomodar strings de comprimentos diferentes também. Ou eu sou o único pensando que a malha deveria funcionar um pouco assim:
u = "foobar"
l = "baz"
mesh(u,l) = "fboaozbar"
Uma maneira de fazer isso seria a seguinte:
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:]])
Eu gosto de usar dois for
s, os nomes das variáveis podem dar uma dica / lembrete do que está acontecendo:
"".join(char for pair in zip(u,l) for char in pair)
Parece um pouco não-pythônico não considerar a resposta de compreensão de lista dupla aqui, para lidar com n strings com esforço O (1):
"".join(c for cs in itertools.zip_longest(*all_strings) for c in cs)
onde all_strings
está uma lista das strings que você deseja intercalar. No seu caso all_strings = [u, l]
,. Um exemplo de uso completo ficaria assim:
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'
Como muitas respostas, mais rápido? Provavelmente não, mas simples e flexível. Além disso, sem adicionar muita complexidade, isso é um pouco mais rápido do que a resposta aceita (em geral, a adição de string é um pouco lenta em 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
Potencialmente mais rápido e mais curto do que a solução líder atual:
from itertools import chain
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
res = "".join(chain(*zip(u, l)))
A estratégia em termos de velocidade é fazer o máximo possível no nível C. A mesma correção zip_longest () para strings desiguais e ela sairia do mesmo módulo que chain (), portanto, não pode me dar muitos pontos aí!
Outras soluções que encontrei ao longo do caminho:
res = "".join(u[x] + l[x] for x in range(len(u)))
res = "".join(k + l[i] for i, k in enumerate(u))
Você poderia usar 1iteration_utilities.roundrobin
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
from iteration_utilities import roundrobin
''.join(roundrobin(u, l))
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
ou a ManyIterables
classe do mesmo pacote:
from iteration_utilities import ManyIterables
ManyIterables(u, l).roundrobin().as_string()
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
1 Esta é a partir de uma biblioteca de terceiros que tenho escrito: iteration_utilities
.