115 votes

Plus Pythonique moyen d’entrelacement de deux chaînes

Quel est le moyen plus Pythonique à mailler deux cordes ensemble ?

Par exemple :

Entrée :

Sortie :

127voto

Jim Points 8793

Pour moi, le plus pythonic* est la suivante qui assez bien fait la même chose , mais utilise l' + de l'opérateur de concaténation de 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 l'utilisation de 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

Plus vite approches existent, mais ils sont souvent obscurcir le code.

Remarque: Si les deux chaînes d'entrée sont pas de la même longueur, alors le plus long sera tronquée zip l'itération s'arrête à la fin de la chaîne plus courte. Dans ce cas, au lieu de zip il faut utiliser zip_longest (izip_longest en Python 2) de l' itertools module pour vous assurer que les deux chaînes sont entièrement épuisé.


*Pour prendre une citation de la Zen de Python: la Lisibilité compte.
Pythonic = lisibilité pour moi; i + j est juste visuellement analysé plus facilement, au moins pour mes yeux.

62voto

Mike Müller Points 2963

Alternative Plus Rapide

D'une autre manière:

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

Sortie:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Vitesse

Regarde comme il 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 chaînes:

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.

Variations pour cordes de différentes longueurs

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

Plus courte, on 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))

Sortie:

AaBbCcDdEeFfGgHhIiJjKkLl

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

Sortie:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ

49voto

TigerhawkT3 Points 25584

Avec et .

18voto

Veedrac Points 11712

Sur Python 2, par la mesure la plus rapide façon de faire les choses, à ~3x la vitesse de la liste de découpage pour les petites chaînes et ~30x pour longtemps, est

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

Ceci ne fonctionnerait pas sur Python 3, mais. Vous pourriez mettre en place quelque chose comme

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

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

FWIW, si vous êtes de faire cela massive des chaînes et besoin de chaque cycle, et pour une raison quelconque, ont pour utiliser Python chaînes... 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")

Spécial-boîtier le cas le plus courant de plus petits types aidera aussi. FWIW, ce n'est 3x la vitesse de la liste de découpage pour les longues chaînes de caractères et un facteur de 4 à 5 plus lent pour les petites chaînes.

De toute façon, je préfère l' join solutions, mais depuis timings ont été mentionnées ailleurs j'ai pensé que je pourrais aussi bien se joindre à eux.

16voto

Padraic Cunningham Points 87411

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 plus rapide encore

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 y a également une différence importante entre l' 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 une telle chose comme un générateur de rejoindre, en passant de l'un est toujours plus lente que python va tout d'abord créer une liste en utilisant le contenu, car il n'deux passes sur les données, une pour déterminer la taille nécessaire et l'un de vraiment faire la jointure qui ne serait pas possible à l'aide d'un générateur:

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

Aussi, si vous avez différentes chaînes de longueur et vous ne voulez pas perdre vos 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 est appelé zip_longest

Mais pour python2, veedrac la suggestion 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X