İki dizeyi harmanlamanın en pitonik yolu


115

İki dizgiyi birbirine bağlamanın en pitonik yolu nedir?

Örneğin:

Giriş:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Çıktı:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

2
Buradaki cevaplar büyük ölçüde iki giriş dizenizin aynı uzunlukta olacağını varsaymıştır. Bu güvenli bir varsayım mı yoksa bunun ele alınması mı gerekiyor?
SuperBiasedMan

@SuperBiasedMan Bir çözümünüz varsa tüm koşulların nasıl üstesinden gelineceğini görmek yardımcı olabilir. Soruyla alakalı, ama benim durumumla ilgili değil.
Brandon Deo

3
@drexx En iyi yanıtlayan kişi yine de bunun için bir çözümle yorum yaptı, ben de kapsamlı olması için yayınlarına ekledim.
SuperBiasedMan

Yanıtlar:


127

Benim için en pitonik * yol, hemen hemen aynı şeyi yapan, ancak +her dizedeki ayrı karakterleri birleştirmek için operatörü kullanan şudur :

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

Ayrıca iki join()çağrı kullanmaktan daha hızlıdır :

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

Daha hızlı yaklaşımlar vardır, ancak bunlar genellikle kodu karıştırır.

Not: İki giriş dizisi aynı uzunlukta değilse , daha uzun zipolan, daha kısa dizinin sonunda yinelenen duraklar olarak kesilecektir . Bu durumda , her iki dizenin de tamamen tükendiğinden emin olmak için modülden zipbiri yerine zip_longest( izip_longestPython 2'de) kullanılmalıdır itertools.


* Zen of Python'dan bir alıntı yapmak gerekirse : Okunabilirlik önemlidir .
Pythonic = benim için okunabilirlik ; i + jen azından gözlerim için görsel olarak daha kolay ayrıştırılıyor.


1
Ancak n dizge için kodlama çabası O (n) 'dur. Yine de, n küçük olduğu sürece iyidir.
TigerhawkT3

Jeneratörünüz muhtemelen birleşimden daha fazla ek yüke neden oluyor.
Padraic Cunningham

5
koş "".join([i + j for i, j in zip(l1, l2)])ve kesinlikle en hızlı olacak
Padraic Cunningham

6
"".join(map("".join, zip(l1, l2)))daha pitonik olmasa da daha hızlıdır.
Aleksi Torhamo

63

Daha Hızlı Alternatif

Diğer yol:

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

Çıktı:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

hız

Daha hızlı gibi görünüyor:

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

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

şimdiye kadarki en hızlı çözümden daha fazla:

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

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

Ayrıca daha büyük dizeler için:

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.

Farklı uzunluklara sahip dizeler için varyasyon

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

Daha kısa olan uzunluğu belirler ( zip()eşdeğer)

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

Çıktı:

AaBbCcDdEeFfGgHhIiJjKkLl

Daha uzun olan uzunluğu belirler ( itertools.zip_longest(fillvalue='')eşdeğer)

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

Çıktı:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ

49

İle join()ve zip().

>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

17
Veya''.join(itertools.chain.from_iterable(zip(u, l)))
Blender

1
Bu, biri diğerinden daha kısaysa zip, daha kısa liste tamamen yinelendiğinde duracağı gibi , bir listeyi kesecektir.
SuperBiasedMan

5
@SuperBiasedMan - Evet. itertools.zip_longestbir sorun haline gelirse kullanılabilir.
TigerhawkT3

18

Python 2'de işleri yapmanın çok daha hızlı yolu, küçük dizeler için liste dilimlemenin ~ 3 katı ve uzun dizeler için ~ 30 kat daha hızlıdır.

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

Yine de bu Python 3'te çalışmaz. Gibi bir şey uygulayabilirsin

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

ancak o zamana kadar, küçük dizgiler için liste dilimlemenin kazançlarını çoktan kaybettiniz (uzun dizelerin hızının hala 20 katıdır) ve bu henüz ASCII olmayan karakterler için bile işe yaramıyor.

FWIW, eğer edilmektedir masif dizeleri bu yapıyor ve her devirde ihtiyaç ve nedense Python dizeleri kullanmak zorunda ... Burada bunu nasıl açıklanmıştır:

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

Daha küçük türlerin ortak durumunu özel kasaya koymak da yardımcı olacaktır. FWIW, bu, uzun dizeler için liste dilimleme hızının yalnızca 3 katı ve küçük dizeler için 4 ila 5 arasında daha yavaş bir faktördür .

Her iki durumda da joinçözümleri tercih ederim , ancak başka yerlerde zamanlamalardan bahsedildiği için katılabileceğimi düşündüm.


16

Eğer hızlı yolu istiyorsanız, birleştirebilirsiniz itertools ile 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

Ama birleşiyor izipve chain.from_iterableyine daha hızlı

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

Ayrıca chain(*ve arasında önemli bir fark vardır chain.from_iterable(....

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

Birleştirme ile bir oluşturucu diye bir şey yoktur, birini geçmek her zaman daha yavaş olacaktır, çünkü python içeriği kullanarak bir liste oluşturacaktır, çünkü veri üzerinden iki geçiş yapar, biri gereken boyutu bulmak ve diğeri gerçekten yapmak için bir jeneratör kullanarak mümkün olmayan birleştirme:

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

Ayrıca farklı uzunluk dizeleriniz varsa ve veri kaybetmek istemiyorsanız izip_longest'i kullanabilirsiniz :

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'

Python 3 için buna denir zip_longest

Ancak python2 için veedrac'ın önerisi açık ara en hızlı olanıdır:

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

2
neden list?? gerekli değil
Copperfield

1
Benim testlerime göre değil, ara liste yapmak için zaman kaybediyorsunuz ve bu da yineleyici kullanım amacını ortadan kaldırıyor. Sürümüyle gelen timeit "".join(list(...))bana 6.715280318699769 verip sürümüyle gelen timeit "".join(starmap(...))bana 6.46332361384313 vermek
Copperfield

1
sonra ne, makineye bağımlı mı? çünkü testi nerede çalıştırdığım önemli değil, aynı sonucu elde ettiğimden daha "".join(list(starmap(add, izip(l1,l2))))yavaş "".join(starmap(add, izip(l1,l2))). Testi makinemde python 2.7.11'de ve python 3.5.1'de hatta www.python.org'un python 3.4.3 ile sanal konsolunda çalıştırıyorum ve hepsi aynı şeyi söylüyor ve birkaç kez çalıştırıyorum ve her zaman aynı
Copperfield

Okudum ve gördüğüm şey, ona ne ilettiğinize bakılmaksızın, tampon değişkeninde her zaman dahili olarak bir liste oluşturması, bu yüzden NO'nun bir liste vermesi için daha fazla neden
Copperfield

@Copperfield, liste aramasından mı yoksa bir listeyi geçmekten mi bahsediyorsun?
Padraic Cunningham

12

Bunu mapve kullanarak da yapabilirsiniz operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Çıktı :

'AaAaAaAaAa'

Eşlemin yaptığı şey, her öğeyi ilk yinelenebilirden uve ilk öğeleri ikinci yinelemeden alır lve ilk argüman olarak sağlanan işlevi uygular add. O zaman katılmak onlara katılır.


9

Jim'in cevabı harika, ancak birkaç ithalatın sakıncası yoksa işte en sevdiğim seçenek:

from functools import reduce
from operator import add

reduce(add, map(add, u, l))

7
Çoğu Pythonic dedi, çoğu Haskellik değil;)
Curt

7

Bu önerilerin çoğu, dizelerin eşit uzunlukta olduğunu varsayar. Belki bu, tüm makul kullanım durumlarını kapsıyor, ancak en azından bana göre, farklı uzunluklardaki dizeleri de yerleştirmek isteyebilirsiniz. Yoksa ağın biraz böyle çalışması gerektiğini düşünen tek kişi ben miyim:

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

Bunu yapmanın bir yolu şudur:

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

5

İki fors kullanmayı seviyorum , değişken isimleri neler olup bittiğine dair bir ipucu / hatırlatıcı verebilir:

"".join(char for pair in zip(u,l) for char in pair)

4

Sadece başka, daha basit bir yaklaşım eklemek için:

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

4

Burada çift liste-anlama yanıtını dikkate almamak, n dizeyi O (1) çabasıyla ele almak biraz pitonik değil:

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

all_stringsserpiştirmek istediğiniz dizelerin listesi nerede . Sizin durumunuzda all_strings = [u, l]. Tam kullanım örneği şöyle görünecektir:

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'

Birçok cevap gibi, en hızlı mı? Muhtemelen hayır, ama basit ve esnek. Ayrıca, çok fazla ek karmaşıklık olmadan, bu kabul edilen yanıttan biraz daha hızlıdır (genel olarak, dize eklenmesi python'da biraz yavaştır):

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

Yine de en hızlı cevap kadar hızlı değil: bu aynı veri ve bilgisayarda 50.3 ms aldı
2016

3

Mevcut lider çözümden potansiyel olarak daha hızlı ve daha kısa:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

Hızlı strateji, mümkün olduğu kadar C seviyesinde yapmaktır. Eşit olmayan dizeler için aynı zip_longest () düzeltmesi ve chain () ile aynı modülden çıkıyor, bu yüzden orada çok fazla puan alamazsın!

Yol boyunca bulduğum diğer çözümler:

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

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

3

1'i kullanabilirsiniteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

veya ManyIterablesaynı paketteki sınıf:

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

1 Bu yazdım bir üçüncü taraf kitaplığından geçerli: iteration_utilities.


2

Okunabilir ve kolay bir yol elde etmek için zip () kullanırdım:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.