Quel est le moyen le plus pythonique de mailler deux chaînes?
Par exemple:
Contribution:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Sortie:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Pour moi, la manière la plus pythonique * est la suivante: 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'
_
Il est également plus rapide que d’utiliser deux appels join()
:
_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 sont pas la même longueur, la plus longue sera tronquée Zip
arrête d'itérer à la fin de la chaîne la plus courte. Dans ce cas, au lieu de Zip
, on devrait utiliser _Zip_longest
_ (_izip_longest
_ dans Python 2) à partir du itertools
module pour s’assurer que les deux chaînes sont complètement épuisées.
* Pour prendre une citation de le zen de Python: La lisibilité compte .
Pythonic = lisibilité pour moi; _i + j
_ est simplement analysé visuellement plus facilement, du moins pour mes yeux.
Autrement:
res = [''] * len(u) * 2
res[::2] = u
res[1::2] = l
print(''.join(res))
Sortie:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
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 grandes 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.
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'
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))
Sortie:
AaBbCcDdEeFfGgHhIiJjKkLl
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))
Sortie:
AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
Avec join()
et Zip()
.
>>> ''.join(''.join(item) for item in Zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
On Python 2, by far le moyen le plus rapide de procéder, à environ 3 fois la vitesse de découpage de la liste pour les petites chaînes et à environ 30x pour les longues,
res = bytearray(len(u) * 2)
res[::2] = u
res[1::2] = l
str(res)
Cela ne fonctionnerait pas sur Python 3, cependant. Vous pourriez 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 sur le découpage de liste pour les petites chaînes (il reste 20 fois plus rapide que pour les longues chaînes) et cela ne fonctionne même pas encore pour les caractères non-ASCII.
FWIW, si vous êtes faites ceci sur des chaînes volumineuses et avez besoin de chaque cycle, et pour une raison quelconque, vous devez utiliser Python chaînes ... voici comment le 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")
Le cas habituel des types plus petits aidera aussi. FWIW, cela ne représente que 3 fois la vitesse de coupe de la 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 solutions join
, mais comme les horaires ont été mentionnés ailleurs, j’ai pensé que je pourrais aussi bien participer.
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 en combinant izip
et chain.from_iterable
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’existe pas de générateur avec jointure, en passer un sera toujours plus lent que python construira d’abord une liste en utilisant le contenu car il effectue deux passages sur les données, un pour comprendre la taille requise et une pour effectuer la jointure, ce 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 différentes chaînes de longueur 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
Vous pouvez aussi le faire en utilisant map
et 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’il prend tous les éléments du premier itératif u
et des premiers éléments du deuxième itératif l
et applique la fonction fournie comme premier argument add
. Alors rejoignez juste les rejoint.
La réponse de Jim est excellente, mais voici mon option préférée, si quelques importations vous importent peu:
from functools import reduce
from operator import add
reduce(add, map(add, u, l))
Beaucoup de ces suggestions supposent que les chaînes sont de même longueur. Cela couvre peut-être tous les cas d’utilisation raisonnables, mais au moins, il me semble que vous voudrez peut-être aussi accepter 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:]])
J'aime utiliser deux for
s, 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)
Juste pour ajouter une autre approche, plus fondamentale:
st = ""
for char in u:
st = "{0}{1}{2}".format( st, char, l[ u.index( char ) ] )
Je ne me sens pas un peu pythonique de ne pas prendre en compte la réponse de double liste ici, pour gérer n chaîne avec O(1) effort:
"".join(c for cs in itertools.Zip_longest(*all_strings) for c in cs)
où all_strings
est une liste des chaînes que vous souhaitez entrelacer. Dans ton cas, all_strings = [u, l]
. Un exemple d'utilisation complète 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, ceci est légèrement plus rapide que la réponse acceptée (en général, l'ajout de chaîne 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
Vous pouvez utiliser iteration_utilities.roundrobin
1
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
from iteration_utilities import roundrobin
''.join(roundrobin(u, l))
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
ou la classe ManyIterables
du même paquet:
from iteration_utilities import ManyIterables
ManyIterables(u, l).roundrobin().as_string()
# returns 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
1 C’est d’une bibliothèque tierce que j’ai écrite: iteration_utilities
.
Potentiellement plus rapide et plus court que la solution leader actuelle:
from itertools import chain
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
res = "".join(chain(*Zip(u, l)))
La stratégie en matière de vitesse consiste à faire le plus de choses possible au niveau C. Même correction de Zip_longest () pour les chaînes inégales et il sortira du même module que chain (), donc ne me touchez pas trop de points!
D'autres solutions que j'ai proposé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))
Je voudrais utiliser Zip () pour obtenir un moyen lisible et facile:
result = ''
for cha, chb in Zip(u, l):
result += '%s%s' % (cha, chb)
print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'