วิธี pythonic ส่วนใหญ่ในการแทรกสองสตริง


115

อะไรคือวิธี pythonic ที่สุดในการเชื่อมโยงสองสายเข้าด้วยกัน?

ตัวอย่างเช่น:

การป้อนข้อมูล:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

เอาท์พุท:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

2
คำตอบที่นี่ส่วนใหญ่สันนิษฐานว่าสตริงอินพุตสองสายของคุณจะมีความยาวเท่ากัน เป็นข้อสันนิษฐานที่ปลอดภัยหรือคุณจำเป็นต้องจัดการ?
SuperBiasedMan

@SuperBiasedMan อาจเป็นประโยชน์ในการดูวิธีจัดการกับเงื่อนไขทั้งหมดหากคุณมีวิธีแก้ไข มันเกี่ยวข้องกับคำถาม แต่ไม่ใช่กรณีของฉันโดยเฉพาะ
Brandon Deo

3
@drexx ผู้ตอบยอดนิยมแสดงความคิดเห็นพร้อมวิธีแก้ปัญหาดังนั้นฉันเพิ่งแก้ไขในโพสต์ของพวกเขาเพื่อให้ครอบคลุม
SuperBiasedMan

คำตอบ:


127

สำหรับฉันวิธี pythonic * ส่วนใหญ่คือวิธีต่อไปนี้ซึ่งทำในสิ่งเดียวกันแต่ใช้ตัว+ดำเนินการเพื่อต่ออักขระแต่ละตัวในแต่ละสตริง:

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

นอกจากนี้ยังเร็วกว่าการ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

มีแนวทางที่เร็วกว่า แต่มักทำให้โค้ดสับสน

หมายเหตุ:หากสตริงอินพุตสองสตริงมีความยาวไม่เท่ากันสตริงที่ยาวกว่าจะถูกตัดทอนเมื่อzipหยุดการทำซ้ำที่ส่วนท้ายของสตริงที่สั้นกว่า ในกรณีนี้zipควรใช้zip_longest( izip_longestใน Python 2) จากitertoolsโมดูลแทนเพื่อให้แน่ใจว่าสตริงทั้งสองหมดอย่างสมบูรณ์


* ในการใช้คำพูดจากเซนของงูใหญ่ : นับการอ่าน
Pythonic = อ่านง่ายสำหรับฉัน; i + jเพียงแค่แยกวิเคราะห์ทางสายตาได้ง่ายขึ้นอย่างน้อยก็สำหรับดวงตาของฉัน


1
ความพยายามในการเข้ารหัสสำหรับ n สตริงคือ O (n) แม้ว่า ยังคงดีตราบเท่าที่ n มีขนาดเล็ก
TigerhawkT3

เครื่องกำเนิดไฟฟ้าของคุณอาจทำให้เกิดค่าใช้จ่ายมากกว่าการเข้าร่วม
Padraic Cunningham

5
วิ่ง"".join([i + j for i, j in zip(l1, l2)])และมันจะเร็วที่สุดแน่นอน
Padraic Cunningham

6
"".join(map("".join, zip(l1, l2)))เร็วขึ้นแม้ว่าจะไม่จำเป็นต้องเป็น pythonic มากกว่าก็ตาม
Aleksi Torhamo

63

ทางเลือกที่เร็วกว่า

อีกวิธีหนึ่ง:

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

เอาท์พุท:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

ความเร็ว

ดูเหมือนว่าจะเร็วกว่า:

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

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

กว่าวิธีแก้ปัญหาที่เร็วที่สุด:

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

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

นอกจากนี้สำหรับสตริงที่ใหญ่กว่า:

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()เทียบเท่า)

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

เอาท์พุท:

AaBbCcDdEeFfGgHhIiJjKkLl

อีกต่อไปกำหนดความยาว ( itertools.zip_longest(fillvalue='')เทียบเท่า)

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

เอาท์พุท:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ

49

ด้วยjoin()และzip().

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

17
หรือ''.join(itertools.chain.from_iterable(zip(u, l)))
Blender

1
สิ่งนี้จะตัดทอนรายการหากรายการหนึ่งสั้นกว่าอีกรายการหนึ่งเนื่องจากzipจะหยุดเมื่อรายการที่สั้นกว่าได้รับการทำซ้ำจนหมด
SuperBiasedMan

5
@SuperBiasedMan - อ๋อ. itertools.zip_longestสามารถใช้ได้หากเกิดปัญหาขึ้น
TigerhawkT3

18

เกี่ยวกับงูหลาม 2 โดยไกลวิธีที่เร็วกว่าที่จะทำสิ่งที่ ~ 3x ความเร็วของการหั่นรายการสำหรับสตริงขนาดเล็กและ ~ 30x ยาวสำหรับคนที่เป็น

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

สิ่งนี้จะใช้ไม่ได้กับ Python 3 แม้ว่า คุณสามารถใช้สิ่งต่างๆเช่น

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

แต่ถึงตอนนั้นคุณก็สูญเสียกำไรจากการแบ่งรายการสำหรับสตริงขนาดเล็กไปแล้ว (ยังคงเป็นความเร็ว 20 เท่าสำหรับสตริงแบบยาว) และยังใช้ไม่ได้กับอักขระที่ไม่ใช่ ASCII

FWIW หากคุณกำลังทำสิ่งนี้กับสตริงขนาดใหญ่และต้องการทุก ๆ รอบและด้วยเหตุผลบางอย่างต้องใช้สตริง Python ... นี่คือวิธีการ:

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

การใส่ปลอกพิเศษสำหรับเคสทั่วไปที่มีขนาดเล็กจะช่วยได้เช่นกัน FWIW นี่เป็นเพียง 3 เท่าของความเร็วในการแบ่งส่วนรายการสำหรับสตริงแบบยาวและปัจจัยที่ช้ากว่า 4 ถึง 5 สำหรับสตริงขนาดเล็ก

ไม่ว่าจะด้วยวิธีใดjoinก็ตามฉันชอบวิธีแก้ปัญหา แต่เนื่องจากมีการพูดถึงการกำหนดเวลาที่อื่นฉันจึงคิดว่าฉันอาจเข้าร่วมด้วยเช่นกัน


16

หากคุณต้องการวิธีที่เร็วที่สุดคุณสามารถรวมitertoolsเข้ากับ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

แต่รวมizipและchain.from_iterableเร็วขึ้นอีกครั้ง

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

นอกจากนี้ยังมีความแตกต่างที่สำคัญระหว่าง และchain(*chain.from_iterable(...

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

ไม่มีสิ่งที่เรียกว่าเครื่องกำเนิดไฟฟ้าที่มีการเข้าร่วมการส่งผ่านจะช้ากว่าเสมอเนื่องจาก python จะสร้างรายการโดยใช้เนื้อหาเป็นครั้งแรกเนื่องจากจะส่งผ่านข้อมูลสองรายการหนึ่งเพื่อหาขนาดที่ต้องการและอีกอันหนึ่งต้องทำจริง การเข้าร่วมซึ่งไม่สามารถทำได้โดยใช้เครื่องกำเนิดไฟฟ้า:

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

นอกจากนี้หากคุณมีสตริงที่มีความยาวต่างกันและไม่ต้องการสูญเสียข้อมูลคุณสามารถใช้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'

สำหรับ python 3 เรียกว่า zip_longest

แต่สำหรับ python2 คำแนะนำของ veedrac นั้นเร็วที่สุด:

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
ทำไมlist?? ไม่จำเป็น
Copperfield

1
ไม่เป็นไปตามการทดสอบของฉันคุณเสียเวลาในการสร้างรายชื่อตัวกลางและทำให้จุดประสงค์ของการใช้ตัววนซ้ำ Timeit the "".join(list(...))give me 6.715280318699769 and timeit the "".join(starmap(...))give me 6.46332361384313
Copperfield

1
แล้วเครื่องขึ้นอยู่กับอะไร ?? เพราะเรื่องที่ผมใช้ทดสอบไม่มีฉันจะได้รับผลที่แน่นอนเดียวกันจะช้ากว่า"".join(list(starmap(add, izip(l1,l2)))) "".join(starmap(add, izip(l1,l2)))ฉันเรียกใช้การทดสอบในเครื่องของฉันใน python 2.7.11 และใน python 3.5.1 แม้ในคอนโซลเสมือนของwww.python.orgด้วย python 3.4.3 และทุกคนก็พูดเหมือนกันและฉันเรียกใช้มันสองสามครั้งและเสมอ เหมือนกัน
Copperfield

ฉันอ่านและสิ่งที่ฉันเห็นคือมันสร้างรายการภายในตลอดเวลาในตัวแปรบัฟเฟอร์โดยไม่คำนึงถึงสิ่งที่คุณส่งผ่านไปดังนั้นยิ่งมีเหตุผลมากขึ้นที่จะไม่ให้รายการ
Copperfield

@Copperfield คุณกำลังพูดถึงรายการโทรหรือส่งผ่านรายการ?
Padraic Cunningham

12

คุณสามารถทำได้โดยใช้mapและoperator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

เอาท์พุต :

'AaAaAaAaAa'

อะไรจะเป็นแผนที่จะใช้เวลาทุกองค์ประกอบจาก iterable แรกuองค์ประกอบและเป็นครั้งแรกจาก iterable ที่สองและใช้ฟังก์ชั่นที่ให้มาเป็นอาร์กิวเมนต์แรกl addจากนั้นเข้าร่วมเพียงแค่เข้าร่วม


9

คำตอบของ Jim นั้นยอดเยี่ยม แต่นี่คือตัวเลือกที่ฉันชอบถ้าคุณไม่สนใจการนำเข้าสองสามอย่าง:

from functools import reduce
from operator import add

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

7
เขาบอกว่า Pythonic ส่วนใหญ่ไม่ใช่ Haskellic ส่วนใหญ่)
Curt

7

คำแนะนำเหล่านี้จำนวนมากถือว่าสตริงมีความยาวเท่ากัน อาจจะครอบคลุมกรณีการใช้งานที่สมเหตุสมผลทั้งหมด แต่อย่างน้อยสำหรับฉันดูเหมือนว่าคุณอาจต้องการรองรับสตริงที่มีความยาวต่างกันด้วย หรือฉันเป็นคนเดียวที่คิดว่าตาข่ายควรทำงานแบบนี้:

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

วิธีหนึ่งในการดำเนินการดังต่อไปนี้:

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

ฉันชอบใช้สองfors ชื่อตัวแปรสามารถให้คำใบ้ / เตือนสิ่งที่เกิดขึ้น:

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

4

เพียงเพื่อเพิ่มแนวทางพื้นฐานอื่น ๆ :

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

4

รู้สึกอึดอัดเล็กน้อยที่จะไม่พิจารณาคำตอบแบบ double-list-comp understandion ที่นี่เพื่อจัดการ n string ด้วยความพยายาม O (1):

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

ที่all_stringsเป็นรายการของสตริงที่คุณต้องการแทรก ในกรณีของคุณall_strings = [u, l]. ตัวอย่างการใช้งานแบบเต็มจะมีลักษณะดังนี้:

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'

ชอบคำตอบมากมายเร็วที่สุด? อาจไม่ใช่ แต่เรียบง่ายและยืดหยุ่น นอกจากนี้หากไม่มีความซับซ้อนเพิ่มมากเกินไปจะเร็วกว่าคำตอบที่ยอมรับเล็กน้อย (โดยทั่วไปการเพิ่มสตริงจะช้าเล็กน้อยใน 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

ยังไม่เร็วเท่าคำตอบที่เร็วที่สุดแม้ว่าจะมี 50.3 ms สำหรับข้อมูลและคอมพิวเตอร์เครื่องเดียวกันนี้
scnerd

3

อาจเร็วกว่าและสั้นกว่าโซลูชันชั้นนำในปัจจุบัน:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

กลยุทธ์ที่ชาญฉลาดคือการทำในระดับ C ให้ได้มากที่สุด zip_longest () เดียวกันสำหรับสตริงที่ไม่สม่ำเสมอและมันจะออกมาจากโมดูลเดียวกันกับ chain () ดังนั้นฉันจึงไม่สามารถทำคะแนนให้ฉันมากเกินไปได้!

วิธีแก้ปัญหาอื่น ๆ ที่ฉันคิดขึ้นระหว่างทาง:

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

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

3

คุณสามารถใช้1iteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

หรือManyIterablesคลาสจากแพ็คเกจเดียวกัน:

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

1 นี่มาจากไลบรารีของบุคคลที่สามที่ฉันเขียน: iteration_utilities.


2

ฉันจะใช้ zip () เพื่อให้อ่านง่ายและสะดวก:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.