La forma más pitónica de intercalar dos cadenas

115

¿Cuál es la forma más pitónica de unir dos cuerdas?

Por ejemplo:

Entrada:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Salida:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Brandon Deo
fuente
2
Las respuestas aquí han asumido en gran medida que sus dos cadenas de entrada tendrán la misma longitud. ¿Es una suposición segura o necesita que se maneje?
SuperBiasedMan
@SuperBiasedMan Puede ser útil ver cómo manejar todas las condiciones si tiene una solución. Es relevante para la pregunta, pero no mi caso específicamente.
Brandon Deo
3
@drexx El principal respondedor comentó con una solución para él de todos modos, así que simplemente lo edité en su publicación para que sea completo.
SuperBiasedMan

Respuestas:

127

Para mí, la forma más pitónica * es la siguiente, que prácticamente hace lo mismo pero usa el +operador para concatenar los caracteres individuales en cada cadena:

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

También es más rápido que usar dos join()llamadas:

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

Existen enfoques más rápidos, pero a menudo confunden el código.

Nota: Si las dos cadenas de entrada no tienen la misma longitud, la más larga se truncará cuando zipdeje de iterar al final de la cadena más corta. En este caso, en lugar de zipuno, debería usar zip_longest( izip_longesten Python 2) del itertoolsmódulo para asegurarse de que ambas cadenas se agoten por completo.


* Para tomar una cita del Zen de Python : la legibilidad cuenta .
Pythonic = legibilidad para mí; i + jse analiza visualmente más fácilmente, al menos para mis ojos.

Dimitris Fasarakis Hilliard
fuente
1
Sin embargo, el esfuerzo de codificación para n cadenas es O (n). Aún así, es bueno siempre que n sea pequeño.
TigerhawkT3
Su generador probablemente esté causando más gastos generales que la unión.
Padraic Cunningham
5
corre "".join([i + j for i, j in zip(l1, l2)])y definitivamente será el más rápido
Padraic Cunningham
6
"".join(map("".join, zip(l1, l2)))es incluso más rápido, aunque no necesariamente más pitónico.
Aleksi Torhamo
63

Alternativa más rápida

De otra manera:

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

Salida:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Velocidad

Parece que es más 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

que la solución más rápida hasta ahora:

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

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

También para las cuerdas más grandes:

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.

Variación para cuerdas con diferentes longitudes

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

El más corto determina la longitud ( 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))

Salida:

AaBbCcDdEeFfGgHhIiJjKkLl

Uno más largo determina la longitud ( 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))

Salida:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
Mike Müller
fuente
49

Con join()y zip().

>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
TigerhawkT3
fuente
17
O''.join(itertools.chain.from_iterable(zip(u, l)))
Blender
1
Esto truncará una lista si una es más corta que la otra, ya que se zipdetiene cuando la lista más corta se ha iterado por completo.
SuperBiasedMan
5
@SuperBiasedMan - Sí. itertools.zip_longestse puede utilizar si se convierte en un problema.
TigerhawkT3
18

En Python 2, con mucho, la forma más rápida de hacer las cosas, a ~ 3 veces la velocidad de corte de la lista para cadenas pequeñas y ~ 30x para cadenas largas, es

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

Sin embargo, esto no funcionaría en Python 3. Podrías implementar algo como

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

pero para entonces ya ha perdido las ganancias sobre la división de listas para cadenas pequeñas (sigue siendo 20 veces la velocidad para cadenas largas) y esto ni siquiera funciona para caracteres que no son ASCII todavía.

FWIW, si está haciendo esto en cadenas masivas y necesita cada ciclo, y por alguna razón tiene que usar cadenas de Python ... así es como se hace:

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

La carcasa especial, el caso común de tipos más pequeños, también ayudará. FWIW, esto es solo 3 veces la velocidad de corte de listas para cadenas largas y un factor de 4 a 5 más lento para cadenas pequeñas.

De cualquier manera, prefiero las joinsoluciones, pero como los tiempos se mencionaron en otra parte, pensé que también podría unirme.

Veedrac
fuente
16

Si desea la forma más rápida, puede combinar itertools con 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

Pero combinando izipy chain.from_iterablees más rápido de nuevo

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

También hay una diferencia sustancial entre chain(*y chain.from_iterable(....

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

No existe un generador con combinación, pasar uno siempre será más lento, ya que Python primero creará una lista utilizando el contenido porque hace dos pasadas sobre los datos, una para determinar el tamaño necesario y otra para hacer realmente la unión que no sería posible usando un generador:

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

Además, si tiene cadenas de diferente longitud y no desea perder datos, puede 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 se llama zip_longest

Pero para python2, la sugerencia de veedrac es, con mucho, la más 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
Padraic Cunningham
fuente
2
porque list?? es innecesario
Copperfield
1
no según mis pruebas, pierdes tiempo haciendo la lista de intermediarios y eso frustra el propósito de usar iteradores. Timeit the "".join(list(...))dame 6.715280318699769 y timeit the "".join(starmap(...))dame 6.46332361384313
Copperfield
1
Entonces, ¿depende de la máquina? porque no importa dónde ejecute la prueba, obtengo el mismo resultado exacto "".join(list(starmap(add, izip(l1,l2))))es más lento que "".join(starmap(add, izip(l1,l2))). Ejecuto la prueba en mi máquina en python 2.7.11 y en python 3.5.1 incluso en la consola virtual de www.python.org con python 3.4.3 y todos dicen lo mismo y lo ejecuto un par de veces y siempre el mismo
Copperfield
Leí y lo que veo es que construye una lista internamente todo el tiempo en sus búferes variables independientemente de lo que le pase, así que la razón más para NO darle una lista
Copperfield
@Copperfield, ¿estás hablando de la lista de llamadas o de pasar una lista?
Padraic Cunningham
12

También puede hacer esto usando mapy operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Salida :

'AaAaAaAaAa'

Lo que hace el mapa es que toma todos los elementos del primer iterable uy los primeros elementos del segundo iterable ly aplica la función proporcionada como primer argumento add. Entonces únete solo se une a ellos.

raíz
fuente
9

La respuesta de Jim es excelente, pero esta es mi opción favorita, si no le importan un par de importaciones:

from functools import reduce
from operator import add

reduce(add, map(add, u, l))
knite
fuente
7
Dijo más Pythonic, no más Haskellic;)
Curt
7

Muchas de estas sugerencias asumen que las cadenas tienen la misma longitud. Tal vez eso cubra todos los casos de uso razonables, pero al menos a mí me parece que es posible que también desee acomodar cadenas de diferentes longitudes. ¿O soy el único que piensa que la malla debería funcionar un poco así:

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

Una forma de hacerlo sería la siguiente:

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
fuente
5

Me gusta usar dos fors, los nombres de las variables pueden dar una pista / recordatorio de lo que está sucediendo:

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

Solo para agregar otro enfoque más básico:

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

Se siente un poco poco pitónico no considerar la respuesta de comprensión de doble lista aquí, para manejar n cadena con O (1) esfuerzo:

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

donde all_stringses una lista de las cadenas que desea intercalar. En tu caso all_strings = [u, l],. Un ejemplo de uso completo se vería así:

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 muchas respuestas, ¿más rápido? Probablemente no, pero simple y flexible. Además, sin demasiada complejidad adicional, esto es un poco más rápido que la respuesta aceptada (en general, la adición de cadenas es un poco lenta en 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
fuente
Sin embargo, todavía no es tan rápido como la respuesta más rápida: que obtuvo 50.3 ms en estos mismos datos y computadora
scnerd
3

Potencialmente más rápido y más corto que la solución líder actual:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

La estrategia en cuanto a velocidad es hacer todo lo posible en el nivel C. La misma solución zip_longest () para cadenas desiguales y saldría del mismo módulo que chain (), ¡así que no puedo marcarme demasiados puntos allí!

Otras soluciones que se me ocurrieron en el camino:

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

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

Podrías usar 1iteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

o la ManyIterablesclase del mismo paquete:

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

1 Se trata de una biblioteca de terceros que he escrito: iteration_utilities.

MSeifert
fuente
2

Usaría zip () para obtener una manera fácil y legible:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
valeas
fuente