Il modo più pitonico di intercalare due corde


115

Qual è il modo più pitonico per unire due corde insieme?

Per esempio:

Ingresso:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Produzione:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

2
Le risposte qui hanno in gran parte presunto che le due stringhe di input avranno la stessa lunghezza. È un presupposto sicuro o è necessario che venga gestito?
SuperBeddingMan

@SuperBiasMan Potrebbe essere utile vedere come gestire tutte le condizioni se hai una soluzione. È rilevante per la domanda, ma non specificamente per il mio caso.
Brandon Deo

3
@drexx Il miglior risponditore ha comunque commentato una soluzione, quindi l'ho appena modificato nel loro post in modo che sia completo.
SuperBeddingMan

Risposte:


127

Per me, il modo più pitonico * è il seguente che praticamente fa la stessa cosa ma utilizza l' +operatore per concatenare i singoli caratteri in ogni stringa:

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

È anche più veloce rispetto all'utilizzo di due join()chiamate:

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

Esistono approcci più rapidi, ma spesso offuscano il codice.

Nota: se le due stringhe di input non hanno la stessa lunghezza, quella più lunga verrà troncata poiché zipinterrompe l'iterazione alla fine della stringa più corta. In questo caso invece di zipuno dovrebbe usare zip_longest( izip_longestin Python 2) dal itertoolsmodulo per assicurarsi che entrambe le stringhe siano completamente esaurite.


* Per prendere una citazione dallo Zen di Python : la leggibilità conta .
Pythonic = leggibilità per me; i + jè solo visivamente analizzato più facilmente, almeno per i miei occhi.


1
Tuttavia, lo sforzo di codifica per n stringhe è O (n). Tuttavia, è buono finché n è piccolo.
TigerhawkT3

Il tuo generatore sta probabilmente causando più overhead rispetto al join.
Padraic Cunningham

5
corri "".join([i + j for i, j in zip(l1, l2)])e sarà sicuramente il più veloce
Padraic Cunningham

6
"".join(map("".join, zip(l1, l2)))è ancora più veloce, anche se non necessariamente più pitonico.
Aleksi Torhamo

63

Alternativa più veloce

Un altro modo:

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

Produzione:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Velocità

Sembra che sia più veloce:

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

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

rispetto alla soluzione più veloce finora:

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

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

Anche per le corde più grandi:

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.

Variazione per corde di diverse lunghezze

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

Uno più corto determina la lunghezza ( 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))

Produzione:

AaBbCcDdEeFfGgHhIiJjKkLl

Uno più lungo determina la lunghezza ( 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))

Produzione:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ

49

Con join()e zip().

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

17
Oppure''.join(itertools.chain.from_iterable(zip(u, l)))
Blender

1
Questo troncerà un elenco se uno è più corto dell'altro, come si zipinterrompe quando l'elenco più breve è stato completamente iterato.
SuperBeddingMan

5
@SuperBiasMan - Sì. itertools.zip_longestpuò essere utilizzato se diventa un problema.
TigerhawkT3

18

Su Python 2, di gran lunga il modo più veloce di fare le cose, a ~ 3 volte la velocità di suddivisione della lista per stringhe piccole e ~ 30x per stringhe lunghe, è

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

Tuttavia, questo non funzionerebbe su Python 3. Potresti implementare qualcosa di simile

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

ma a quel punto hai già perso i guadagni rispetto all'affettamento della lista per stringhe piccole (è ancora 20 volte la velocità per stringhe lunghe) e questo non funziona ancora per i caratteri non ASCII.

FWIW, se lo stai facendo su stringhe enormi e hai bisogno di ogni ciclo, e per qualche motivo devi usare stringhe Python ... ecco come farlo:

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

Anche l'involucro speciale del caso comune di tipi più piccoli aiuterà. FWIW, questa è solo 3 volte la velocità di suddivisione della lista per stringhe lunghe e un fattore da 4 a 5 più lento per stringhe piccole.

Ad ogni modo preferisco le joinsoluzioni, ma poiché i tempi sono stati menzionati altrove, ho pensato che avrei potuto anche partecipare.


16

Se vuoi il modo più veloce, puoi combinare 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

Ma combinando iziped chain.from_iterableè di nuovo più veloce

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

C'è anche una differenza sostanziale tra chain(*e chain.from_iterable(....

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

Non esiste un generatore con join, passarne uno sarà sempre più lento poiché python costruirà prima un elenco utilizzando il contenuto perché esegue due passaggi sui dati, uno per capire la dimensione necessaria e uno per farlo effettivamente il join che non sarebbe possibile utilizzando un generatore:

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

Inoltre, se hai stringhe di lunghezza diversa e non vuoi perdere dati, puoi usare 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'

Per python 3 si chiama zip_longest

Ma per python2, il suggerimento di veedrac è di gran lunga il più veloce:

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
perchè list?? non è necessario
Copperfield

1
non secondo i miei test, si perde tempo a fare la lista degli intermedi e questo vanifica lo scopo di usare gli iteratori. Timeit the "".join(list(...))give me 6.715280318699769 and timeit the "".join(starmap(...))give me 6.46332361384313
Copperfield

1
allora cosa dipende dalla macchina ?? perché non importa dove eseguo il test, ottengo lo stesso risultato esatto "".join(list(starmap(add, izip(l1,l2))))è più lento di "".join(starmap(add, izip(l1,l2))). Eseguo il test sulla mia macchina in python 2.7.11 e in python 3.5.1 anche nella console virtuale di www.python.org con python 3.4.3 e dicono tutti la stessa cosa e lo eseguo un paio di volte e sempre il stesso
Copperfield

Ho letto e quello che vedo è che crea un elenco internamente tutto il tempo nella sua variabile buffer indipendentemente da ciò che gli passi, quindi la ragione in più per NON dargli una lista
Copperfield

@Copperfield, stai parlando della chiamata alla lista o stai passando una lista?
Padraic Cunningham

12

Puoi anche farlo usando mape operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Uscita :

'AaAaAaAaAa'

Quello che fa la mappa è che prende ogni elemento dal primo iterabile ue i primi elementi dal secondo iterabile le applica la funzione fornita come primo argomento add. Quindi unisciti a loro.


9

La risposta di Jim è ottima, ma ecco la mia opzione preferita, se non ti dispiace un paio di importazioni:

from functools import reduce
from operator import add

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

7
Ha detto più pitonico, non più haskellico;)
Curt

7

Molti di questi suggerimenti presuppongono che le stringhe abbiano la stessa lunghezza. Forse questo copre tutti i casi d'uso ragionevoli, ma almeno a me sembra che potresti voler ospitare anche stringhe di lunghezze diverse. O sono l'unico a pensare che la mesh dovrebbe funzionare un po 'così:

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

Un modo per farlo sarebbe il seguente:

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

Mi piace usare due fors, i nomi delle variabili possono dare un suggerimento / promemoria su cosa sta succedendo:

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

4

Solo per aggiungere un altro approccio più semplice:

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

4

Sembra un po 'poco pitonico non considerare la risposta di comprensione della doppia lista qui, per gestire n string con O (1) sforzo:

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

dove all_stringsè un elenco delle stringhe che vuoi intercalare. Nel tuo caso all_strings = [u, l],. Un esempio di utilizzo completo sarebbe simile a questo:

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'

Come molte risposte, più veloce? Probabilmente no, ma semplice e flessibile. Inoltre, senza troppa complessità aggiunta, questo è leggermente più veloce della risposta accettata (in generale, l'aggiunta di stringhe è un po 'lenta in 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

Non ancora veloce come la risposta più veloce, però: che ha ottenuto 50,3 ms su questi stessi dati e computer
scnerd

3

Potenzialmente più veloce e più breve dell'attuale soluzione leader:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

Per quanto riguarda la velocità, la strategia è fare il più possibile a livello C. Stessa correzione di zip_longest () per stringhe irregolari e sarebbe uscito dallo stesso modulo di chain () quindi non posso farmi troppi punti lì!

Altre soluzioni che ho trovato lungo la strada:

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

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

3

Potresti usare 1iteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

o la ManyIterablesclasse dallo stesso pacchetto:

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

1 Questo è da una libreria di terze parti che ho scritto: iteration_utilities.


2

Vorrei usare zip () per ottenere un modo facile e leggibile:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.