web-dev-qa-db-fra.com

La plupart des méthodes pythoniques pour entrelacer deux chaînes

Quel est le moyen le plus pythonique de mailler deux chaînes?

Par exemple:

Contribution:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Sortie:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
113
Brandon Deo

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.

126

Alternative plus rapide

Autrement:

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

Sortie:

'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 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.

Variation pour les chaînes 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))

Sortie:

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

Sortie:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
62
Mike Müller

Avec join() et Zip().

>>> ''.join(''.join(item) for item in Zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
48
TigerhawkT3

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.

18
Veedrac

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
16
Padraic Cunningham

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.

12
root

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

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:]])
7
Christofer Ohlsson

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)
5
Neal Fultz

Juste pour ajouter une autre approche, plus fondamentale:

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

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)

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
3
scnerd

Vous pouvez utiliser iteration_utilities.roundrobin1

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 .

3
MSeifert

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

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'
2
valeas