Die pythonischste Art, zwei Saiten zu verschachteln

116

Was ist die pythonischste Art, zwei Saiten miteinander zu verbinden?

Beispielsweise:

Eingang:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Ausgabe:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Brandon Deo
quelle
2
Bei den Antworten hier wurde weitgehend davon ausgegangen, dass Ihre beiden Eingabezeichenfolgen dieselbe Länge haben. Ist das eine sichere Annahme oder müssen Sie damit umgehen?
SuperBiasedMan
@SuperBiasedMan Es kann hilfreich sein zu sehen, wie mit allen Bedingungen umgegangen wird, wenn Sie eine Lösung haben. Es ist relevant für die Frage, aber nicht speziell für meinen Fall.
Brandon Deo
3
@drexx Der Top-Antwortende hat sowieso eine Lösung dafür kommentiert, also habe ich sie einfach in ihrem Beitrag bearbeitet, damit sie umfassend ist.
SuperBiasedMan

Antworten:

127

Für mich ist der pythonischste * Weg der folgende, der so ziemlich das Gleiche tut, aber den +Operator zum Verketten der einzelnen Zeichen in jeder Zeichenfolge verwendet:

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

Es ist auch schneller als zwei join()Anrufe:

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

Es gibt schnellere Ansätze, die den Code jedoch häufig verschleiern.

Hinweis: Wenn die beiden Eingabezeichenfolgen nicht dieselbe Länge haben, wird die längere abgeschnitten, da zipdie Iteration am Ende der kürzeren Zeichenfolge stoppt. In diesem Fall statt zipsollte man verwenden zip_longest( izip_longestin Python 2) aus dem itertoolsModul , um sicherzustellen , dass beide Saiten voll ausgeschöpft werden.


* Um ein Zitat aus dem Zen von Python zu erhalten : Lesbarkeit zählt .
Pythonic = Lesbarkeit für mich; i + jwird nur visuell leichter analysiert, zumindest für meine Augen.

Dimitris Fasarakis Hilliard
quelle
1
Der Codierungsaufwand für n Zeichenfolgen beträgt jedoch O (n). Trotzdem ist es gut, solange n klein ist.
TigerhawkT3
Ihr Generator verursacht wahrscheinlich mehr Overhead als der Join.
Padraic Cunningham
5
laufen "".join([i + j for i, j in zip(l1, l2)])und es wird definitiv das schnellste sein
Padraic Cunningham
6
"".join(map("".join, zip(l1, l2)))ist noch schneller, wenn auch nicht unbedingt pythonischer.
Aleksi Torhamo
63

Schnellere Alternative

Ein anderer Weg:

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

Ausgabe:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Geschwindigkeit

Sieht so aus, als wäre es schneller:

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

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

als die bisher schnellste Lösung:

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

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

Auch für die größeren Saiten:

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 für Saiten unterschiedlicher Länge

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

Kürzere bestimmt Länge ( 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))

Ausgabe:

AaBbCcDdEeFfGgHhIiJjKkLl

Länger bestimmt die Länge ( 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))

Ausgabe:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ
Mike Müller
quelle
49

Mit join()und zip().

>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
TigerhawkT3
quelle
17
Oder''.join(itertools.chain.from_iterable(zip(u, l)))
Blender
1
Dadurch wird eine Liste abgeschnitten, wenn eine kürzer als die andere ist. Dies wird gestoppt, zipwenn die kürzere Liste vollständig durchlaufen wurde.
SuperBiasedMan
5
@ SuperBiasedMan - Ja. itertools.zip_longestkann verwendet werden, wenn es ein Problem wird.
TigerhawkT3
18

Unter Python 2 ist der weitaus schnellere Weg, Dinge zu erledigen, mit ~ 3x der Geschwindigkeit, mit der Listen für kleine Zeichenfolgen und ~ 30x für lange Zeichenfolgen geschnitten werden

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

Dies würde jedoch unter Python 3 nicht funktionieren. Sie könnten so etwas implementieren

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

Bis dahin haben Sie jedoch bereits die Vorteile des Listen-Slicing für kleine Zeichenfolgen verloren (es ist immer noch 20-mal so schnell wie für lange Zeichenfolgen), und dies funktioniert noch nicht einmal für Nicht-ASCII-Zeichen.

FWIW, wenn Sie werden diese auf massiven Saiten zu tun und müssen in jedem Zyklus, und aus irgendeinem Grund Python - Strings verwenden ... ist hier , wie es geht:

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

Ein spezielles Gehäuse, wie es bei kleineren Typen üblich ist, hilft ebenfalls. FWIW, dies ist nur das Dreifache der Geschwindigkeit des Listenschneidens für lange Zeichenfolgen und ein Faktor von 4 bis 5 langsamer für kleine Zeichenfolgen.

In beiden Fällen bevorzuge ich die joinLösungen, aber da die Timings an anderer Stelle erwähnt wurden, dachte ich, ich könnte genauso gut mitmachen.

Veedrac
quelle
16

Wenn Sie den schnellsten Weg suchen , können Sie itertools kombinieren mit 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

Aber kombinieren izipund chain.from_iterableist wieder schneller

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

Es gibt auch einen wesentlichen Unterschied zwischen chain(*und chain.from_iterable(....

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

Es gibt keinen Generator mit Join. Die Übergabe eines Generators wird immer langsamer, da Python zuerst eine Liste mit dem Inhalt erstellt, da zwei Durchgänge über die Daten ausgeführt werden, einer, um die erforderliche Größe zu ermitteln, und einer, um tatsächlich zu tun die Verknüpfung, die mit einem Generator nicht möglich wäre:

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

Auch wenn Sie Zeichenfolgen unterschiedlicher Länge haben und keine Daten verlieren möchten, können Sie izip_longest verwenden :

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'

Für Python 3 heißt es zip_longest

Für python2 ist der Vorschlag von veedrac jedoch bei weitem der schnellste:

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
quelle
2
warum list? wird nicht benötigt
Copperfield
1
Nicht nach meinen Tests verlieren Sie Zeit beim Erstellen der Zwischenliste und das macht den Zweck der Verwendung von Iteratoren zunichte. Timeit das "".join(list(...))gib mir 6.715280318699769 und timeit das "".join(starmap(...))gib mir 6.46332361384313
Copperfield
1
Was ist dann maschinenabhängig? denn egal wo ich den Test durchführe, ich bekomme das gleiche genaue Ergebnis, "".join(list(starmap(add, izip(l1,l2))))das langsamer ist als "".join(starmap(add, izip(l1,l2))). Ich führe den Test auf meinem Computer in Python 2.7.11 und in Python 3.5.1 sogar in der virtuellen Konsole von www.python.org mit Python 3.4.3 aus und alle sagen dasselbe und ich führe ihn ein paar Mal und immer aus gleiche
Copperfield
Ich habe gelesen und ich sehe, dass es eine Liste intern ständig in seiner Puffervariablen erstellt, unabhängig davon, was Sie an ihn übergeben. Je mehr Grund es gibt, NEIN eine Liste zu geben
Copperfield
@Copperfield, sprechen Sie über den Listenaufruf oder das Übergeben einer Liste?
Padraic Cunningham
12

Sie können dies auch mit mapund tun operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Ausgabe :

'AaAaAaAaAa'

Map nimmt jedes Element aus der ersten Iterable uund die ersten Elemente aus der zweiten Iterable lund wendet die als erstes Argument angegebene Funktion an add. Dann schließt sich ihnen einfach an.

Wurzel
quelle
9

Jims Antwort ist großartig, aber hier ist meine Lieblingsoption, wenn Ihnen ein paar Importe nichts ausmachen:

from functools import reduce
from operator import add

reduce(add, map(add, u, l))
stricken
quelle
7
Er sagte am meisten Pythonic, nicht am meisten Haskellic;)
Curt
7

Bei vielen dieser Vorschläge wird davon ausgegangen, dass die Zeichenfolgen gleich lang sind. Vielleicht deckt das alle vernünftigen Anwendungsfälle ab, aber zumindest scheint es mir, dass Sie auch Saiten unterschiedlicher Länge aufnehmen möchten. Oder bin ich der einzige, der denkt, das Netz sollte ein bisschen so funktionieren:

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

Ein Weg, dies zu tun, wäre der folgende:

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
quelle
5

Ich benutze gerne zwei fors, die Variablennamen können einen Hinweis / eine Erinnerung geben, was los ist:

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

Nur um einen weiteren, grundlegenderen Ansatz hinzuzufügen:

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

Fühlt sich ein bisschen unpythonisch an, die Antwort auf das Doppellistenverständnis hier nicht zu berücksichtigen, um n Zeichenfolgen mit O (1) Aufwand zu behandeln:

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

Wo all_stringsist eine Liste der Zeichenfolgen, die Sie verschachteln möchten. In Ihrem Fall all_strings = [u, l]. Ein Beispiel für eine vollständige Verwendung würde folgendermaßen aussehen:

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'

Wie viele Antworten am schnellsten? Wahrscheinlich nicht, aber einfach und flexibel. Ohne zu viel zusätzliche Komplexität ist dies etwas schneller als die akzeptierte Antwort (im Allgemeinen ist das Hinzufügen von Zeichenfolgen in Python etwas langsam):

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
quelle
Immer noch nicht so schnell wie die schnellste Antwort: die 50,3 ms auf den gleichen Daten und Computer bekam
scnerd
3

Potenziell schneller und kürzer als die derzeit führende Lösung:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

In Bezug auf die Geschwindigkeit besteht die Strategie darin, so viel wie möglich auf C-Ebene zu tun. Dieselbe zip_longest () - Korrektur für ungleichmäßige Zeichenfolgen und sie würde aus demselben Modul wie chain () stammen, kann mir also nicht zu viele Punkte geben!

Andere Lösungen, die ich mir auf dem Weg ausgedacht habe:

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

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

Sie könnten 1 verwendeniteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

oder die ManyIterablesKlasse aus demselben Paket:

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

1 Dies ist aus einer Drittanbieter-Bibliothek, die ich geschrieben habe : iteration_utilities.

MSeifert
quelle
2

Ich würde zip () verwenden, um einen lesbaren und einfachen Weg zu finden:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
valeas
quelle