La manière la plus pythonique d'entrelacer deux cordes

115

Quelle est la manière la plus pythonique de mailler deux cordes ensemble?

Par exemple:

Contribution:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Production:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Brandon Deo
la source
2
Les réponses ici ont largement supposé que vos deux chaînes d'entrée auront la même longueur. Est-ce une hypothèse sûre ou avez-vous besoin que cela soit traité?
SuperBiasMan
@SuperBiasMan Il peut être utile de voir comment gérer toutes les conditions si vous avez une solution. Cela concerne la question, mais pas mon cas en particulier.
Brandon Deo
3
@drexx Le premier répondant a quand même commenté une solution, alors je l'ai juste édité dans son message pour qu'il soit complet.
SuperBeasedMan

Réponses:

127

Pour moi, la manière la plus pythonique * est la suivante qui fait à peu près la même chose mais utilise l' +opérateur pour concaténer les caractères individuels dans chaque chaîne:

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

C'est également plus rapide que d'utiliser deux join()appels:

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

Des approches plus rapides existent, mais elles obscurcissent souvent le code.

Remarque: Si les deux chaînes d'entrée ne sont pas de la même longueur, la plus longue sera tronquée car l' zipitération s'arrête à la fin de la chaîne la plus courte. Dans ce cas, au lieu d' zipun, il faut utiliser zip_longest( izip_longesten Python 2) du itertoolsmodule pour s'assurer que les deux chaînes sont complètement épuisées.


* Pour reprendre une citation du Zen of Python : la lisibilité compte .
Pythonic = lisibilité pour moi; i + jest simplement analysé visuellement plus facilement, du moins pour mes yeux.

Dimitris Fasarakis Hilliard
la source
1
Cependant, l'effort de codage pour n chaînes est O (n). Pourtant, c'est bon tant que n est petit.
TigerhawkT3
Votre générateur cause probablement plus de surcharge que la jointure.
Padraic Cunningham
5
courir "".join([i + j for i, j in zip(l1, l2)])et ce sera certainement le plus rapide
Padraic Cunningham
6
"".join(map("".join, zip(l1, l2)))est encore plus rapide, mais pas nécessairement plus pythonique.
Aleksi Torhamo
63

Alternative plus rapide

Autrement:

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

Production:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

La vitesse

On dirait que c'est plus rapide:

%%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 solution la plus rapide à ce jour:

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

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

Aussi pour les plus grosses cordes:

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.

Variation pour les cordes de différentes longueurs

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

Le plus court détermine la longueur ( zip()équivalent)

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

Production:

AaBbCcDdEeFfGgHhIiJjKkLl

Le plus long détermine la longueur ( itertools.zip_longest(fillvalue='')équivalent)

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

Production:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
Mike Müller
la source
49

Avec join()et zip().

>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
TigerhawkT3
la source
17
Ou''.join(itertools.chain.from_iterable(zip(u, l)))
Blender
1
Cela tronquera une liste si l'une est plus courte que l'autre, comme zips'arrête lorsque la liste plus courte a été entièrement itérée.
SuperBiasMan
5
@SuperBiasMan - Ouais. itertools.zip_longestpeut être utilisé si cela devient un problème.
TigerhawkT3
18

Sur Python 2, le moyen de loin le plus rapide de faire les choses, à ~ 3x la vitesse de découpage de liste pour les petites chaînes et ~ 30x pour les longues, est

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

Cela ne fonctionnerait pas sur Python 3, cependant. Vous pouvez implémenter quelque chose comme

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

mais à ce moment-là, vous avez déjà perdu les gains par rapport au découpage de liste pour les petites chaînes (c'est toujours 20x la vitesse pour les longues chaînes) et cela ne fonctionne même pas encore pour les caractères non ASCII.

FWIW, si vous êtes en train de faire cela sur des chaînes massives et ont besoin de chaque cycle, et pour une raison quelconque doivent utiliser des chaînes Python ... voici comment faire:

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

Un boîtier spécial, le cas courant des types plus petits, aidera également. FWIW, c'est seulement 3 fois la vitesse de découpage de liste pour les longues chaînes et un facteur de 4 à 5 plus lent pour les petites chaînes.

Quoi qu'il en soit, je préfère les joinsolutions, mais comme les horaires ont été mentionnés ailleurs, j'ai pensé que je pourrais aussi bien participer.

Veedrac
la source
16

Si vous voulez le moyen le plus rapide, vous pouvez combiner itertools avec 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

Mais combiner izipet chain.from_iterablec'est encore plus rapide

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

Il existe également une différence substantielle entre chain(*et chain.from_iterable(....

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

Il n'y a pas de générateur avec jointure, en transmettre un sera toujours plus lent car python construira d'abord une liste en utilisant le contenu car il effectue deux passes sur les données, un pour déterminer la taille nécessaire et un pour le faire. la jointure qui ne serait pas possible avec un générateur:

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

De plus, si vous avez des chaînes de longueur différente et que vous ne voulez pas perdre de données, vous pouvez utiliser 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'

Pour python 3, il s'appelle zip_longest

Mais pour python2, la suggestion de veedrac est de loin la plus rapide:

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
la source
2
pourquoi list?? est inutile
Copperfield
1
pas d'après mes tests, vous perdez du temps à faire la liste des intermédiaires et cela va à l'encontre de l'objectif d'utiliser des itérateurs. Timeit the "".join(list(...))give me 6.715280318699769 and timeit the "".join(starmap(...))give me 6.46332361384313
Copperfield
1
alors quoi, est-ce que la machine dépend? car peu importe où je lance le test, j'obtiens le même résultat exact "".join(list(starmap(add, izip(l1,l2))))est plus lent que "".join(starmap(add, izip(l1,l2))). J'exécute le test sur ma machine en python 2.7.11 et en python 3.5.1 même dans la console virtuelle de www.python.org avec python 3.4.3 et tous disent la même chose et je l'exécute plusieurs fois et toujours le same
Copperfield
J'ai lu et ce que je vois, c'est qu'il construit une liste en interne tout le temps dans ses tampons variables indépendamment de ce que vous lui passez, donc la raison de plus de NON lui donner une liste
Copperfield
@Copperfield, parlez-vous de l'appel de liste ou de passer une liste?
Padraic Cunningham
12

Vous pouvez également le faire en utilisant mapet operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Sortie :

'AaAaAaAaAa'

Ce que fait la carte, c'est qu'elle prend chaque élément du premier itérable uet les premiers éléments du deuxième itérable let applique la fonction fournie comme premier argument add. Ensuite, rejoignez-les simplement.

racine
la source
9

La réponse de Jim est excellente, mais voici mon option préférée, si cela ne vous dérange pas quelques importations:

from functools import reduce
from operator import add

reduce(add, map(add, u, l))
tricoter
la source
7
Il a dit le plus pythonique, pas le plus haskellique;)
Curt
7

Un grand nombre de ces suggestions supposent que les chaînes sont de longueur égale. Peut-être que cela couvre tous les cas d'utilisation raisonnables, mais au moins pour moi, il semble que vous souhaitiez peut-être aussi accueillir des chaînes de longueurs différentes. Ou suis-je le seul à penser que le maillage devrait fonctionner un peu comme ceci:

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

Une façon de procéder serait la suivante:

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
la source
5

J'aime utiliser deux fors, les noms de variables peuvent donner un indice / rappel de ce qui se passe:

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

Juste pour ajouter une autre approche plus basique:

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

C'est un peu un-pythonique de ne pas considérer la réponse de compréhension à double liste ici, pour gérer n chaînes avec un effort O (1):

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

all_stringsest une liste des chaînes que vous souhaitez entrelacer. Dans votre cas all_strings = [u, l],. Un exemple d'utilisation complet ressemblerait à ceci:

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'

Comme beaucoup de réponses, le plus rapide? Probablement pas, mais simple et flexible. De plus, sans trop de complexité supplémentaire, c'est légèrement plus rapide que la réponse acceptée (en général, l'ajout de chaînes est un peu lent 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
la source
Cependant, toujours pas aussi rapide que la réponse la plus rapide: qui a obtenu 50,3 ms sur ces mêmes données et cet ordinateur
scnerd
3

Potentiellement plus rapide et plus court que la principale solution actuelle:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

La stratégie en termes de vitesse consiste à faire autant que possible au niveau C. Même correctif de zip_longest () pour les chaînes inégales et il sortirait du même module que chain (), donc je ne peux pas me donner trop de points!

Autres solutions que j'ai trouvées en cours de route:

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

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

Vous pouvez utiliser 1iteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

ou la ManyIterablesclasse du même package:

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

1 Ceci est d'une bibliothèque tierce , je l' ai écrit: iteration_utilities.

MSeifert
la source
2

J'utiliserais zip () pour obtenir un moyen lisible et simple:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
valeas
la source