Cách tốt nhất để đan xen hai dây


115

Cách khó nhất để nối hai dây lại với nhau là gì?

Ví dụ:

Đầu vào:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Đầu ra:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

2
Các câu trả lời ở đây phần lớn giả định rằng hai chuỗi đầu vào của bạn sẽ có cùng độ dài. Đó là một giả định an toàn hay bạn cần phải xử lý điều đó?
SuperBiasedMan

@SuperBiasedMan Có thể hữu ích nếu bạn có giải pháp để xử lý tất cả các điều kiện. Nó liên quan đến câu hỏi, nhưng không phải trường hợp của tôi cụ thể.
Brandon Deo

3
@drexx Người trả lời hàng đầu đã bình luận về giải pháp cho vấn đề đó, vì vậy tôi chỉ chỉnh sửa nó thành bài đăng của họ để nó toàn diện.
SuperBiasedMan

Câu trả lời:


127

Đối với tôi, cách khó hiểu nhất * là cách sau đây thực hiện khá nhiều điều tương tự nhưng sử dụng +toán tử để nối các ký tự riêng lẻ trong mỗi chuỗi:

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

Nó cũng nhanh hơn so với sử dụng hai join()cuộc gọi:

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

Các phương pháp tiếp cận nhanh hơn tồn tại, nhưng chúng thường làm xáo trộn mã.

Lưu ý: Nếu hai chuỗi đầu vào không cùng độ dài thì chuỗi dài hơn sẽ bị cắt ngắn khi zipngừng lặp lại ở cuối chuỗi ngắn hơn. Trong trường hợp này, thay vì zipmột, nên sử dụng zip_longest( izip_longesttrong Python 2) từ itertoolsmô-đun để đảm bảo rằng cả hai chuỗi đều được sử dụng hết.


* Để lấy một trích dẫn từ Zen của Python : Tính dễ đọc .
Pythonic = khả năng đọc đối với tôi; i + jđược phân tích cú pháp trực quan dễ dàng hơn, ít nhất là đối với mắt tôi.


1
Tuy nhiên, nỗ lực mã hóa cho n chuỗi là O (n). Tuy nhiên, nó tốt miễn là n nhỏ.
TigerhawkT3

Trình tạo của bạn có thể gây ra nhiều chi phí hơn tham gia.
Padraic Cunningham

5
chạy "".join([i + j for i, j in zip(l1, l2)])và nó chắc chắn sẽ là nhanh nhất
Padraic Cunningham

6
"".join(map("".join, zip(l1, l2)))thậm chí còn nhanh hơn, mặc dù không nhất thiết phải nhiều pythonic hơn.
Aleksi Torhamo

63

Thay thế nhanh hơn

Cách khác:

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

Đầu ra:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Tốc độ

Có vẻ như nó nhanh hơn:

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

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

hơn giải pháp nhanh nhất cho đến nay:

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

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

Cũng cho các chuỗi lớn hơn:

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.

Biến thể cho các chuỗi có độ dài khác nhau

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijkl'

Ngắn hơn một xác định độ dài ( zip()tương đương)

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

Đầu ra:

AaBbCcDdEeFfGgHhIiJjKkLl

Dài hơn xác định độ dài ( itertools.zip_longest(fillvalue='')tương đương)

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

Đầu ra:

AaBbCcDdEeFfGgHhIiJjKkLlMNOPQRSTUVWXYZ

49

Với join()zip().

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

17
Hoặc''.join(itertools.chain.from_iterable(zip(u, l)))
Máy xay sinh tố

1
Thao tác này sẽ cắt ngắn danh sách nếu một zipdanh sách ngắn hơn danh sách kia, vì nó sẽ dừng khi danh sách ngắn hơn đã được lặp lại hoàn toàn.
SuperBiasedMan

5
@SuperBiasedMan - Đúng. itertools.zip_longestcó thể được sử dụng nếu nó trở thành một vấn đề.
TigerhawkT3

18

Trên Python 2, cho đến nay cách nhanh hơn để thực hiện mọi việc, với tốc độ gấp ~ 3 lần tốc độ cắt danh sách đối với chuỗi nhỏ và ~ 30x đối với chuỗi dài, là

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

Tuy nhiên, điều này sẽ không hoạt động trên Python 3. Bạn có thể thực hiện một cái gì đó như

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

nhưng đến lúc đó bạn đã mất lợi ích khi cắt danh sách đối với các chuỗi nhỏ (tốc độ vẫn gấp 20 lần đối với các chuỗi dài) và điều này thậm chí không hoạt động đối với các ký tự không phải ASCII.

FWIW, nếu bạn đang làm điều này trên các chuỗi lớn và cần mọi chu kỳ vì lý do nào đó phải sử dụng chuỗi Python ... đây là cách thực hiện:

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

Vỏ đặc biệt trong trường hợp phổ biến của các loại nhỏ hơn cũng sẽ hữu ích. FWIW, tốc độ này chỉ gấp 3 lần tốc độ cắt danh sách đối với các chuỗi dài và chậm hơn từ 4 đến 5 đối với các chuỗi nhỏ.

Dù bằng cách nào, tôi thích các joingiải pháp hơn, nhưng vì thời gian đã được đề cập ở những nơi khác, tôi nghĩ tôi cũng có thể tham gia.


16

Nếu bạn muốn một cách nhanh nhất, bạn có thể kết hợp itertools với 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

Nhưng kết hợp lại izipchain.from_iterablenhanh hơn

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

Cũng có một sự khác biệt đáng kể giữa chain(*chain.from_iterable(....

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

Không có thứ gì gọi là trình tạo với phép nối, việc truyền một cái sẽ luôn chậm hơn vì python trước tiên sẽ xây dựng danh sách bằng cách sử dụng nội dung vì nó thực hiện hai lần chuyển dữ liệu, một để tìm ra kích thước cần thiết và một để thực sự làm nối mà sẽ không thể sử dụng trình tạo:

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

Ngoài ra, nếu bạn có các chuỗi độ dài khác nhau và bạn không muốn mất dữ liệu, bạn có thể sử dụng 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'

Đối với python 3, nó được gọi là zip_longest

Nhưng đối với python2, gợi ý của veedrac cho đến nay là nhanh nhất:

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
tại sao list?? là không cần thiết
Copperfield

1
không theo thử nghiệm của tôi, bạn sẽ mất thời gian lập danh sách trung gian và điều đó làm mất đi mục đích sử dụng trình lặp. Timeit sự "".join(list(...))cho tôi 6,715280318699769 và timeit sự "".join(starmap(...))cho tôi 6,46332361384313
Copperfield

1
thì sao, máy có bị phụ thuộc không ?? bởi vì bất kể tôi chạy thử nghiệm ở đâu, tôi nhận được cùng một kết quả chính xác "".join(list(starmap(add, izip(l1,l2))))chậm hơn "".join(starmap(add, izip(l1,l2))). Tôi chạy thử nghiệm trong máy của mình bằng python 2.7.11 và python 3.5.1 ngay cả trong bảng điều khiển ảo của www.python.org với python 3.4.3 và tất cả đều nói như nhau và tôi chạy nó một vài lần và luôn giống nhau
Copperfield

Tôi đọc và tôi những gì tôi thấy là nó xây dựng một danh sách trong nội bộ tất cả các thời gian trong bộ đệm của nó biến regarless của những gì bạn vượt qua nó, vì vậy lý do hơn để NO cho nó một danh sách
Copperfield

@Copperfield, bạn đang nói về cuộc gọi danh sách hay chuyển một danh sách?
Padraic Cunningham

12

Bạn cũng có thể làm điều này bằng cách sử dụng mapoperator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Đầu ra :

'AaAaAaAaAa'

Bản đồ làm gì là nó lấy mọi phần tử từ có thể lặp đầu tiên uvà các phần tử đầu tiên từ có thể lặp thứ hai lvà áp dụng hàm được cung cấp làm đối số đầu tiên add. Sau đó tham gia chỉ cần tham gia cùng họ.


9

Câu trả lời của Jim rất hay, nhưng đây là lựa chọn yêu thích của tôi, nếu bạn không ngại một vài lần nhập khẩu:

from functools import reduce
from operator import add

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

7
Anh ấy nói hầu hết Pythonic, không phải hầu hết Haskellic;)
Curt

7

Rất nhiều đề xuất này giả sử các chuỗi có độ dài bằng nhau. Có thể điều đó bao gồm tất cả các trường hợp sử dụng hợp lý, nhưng ít nhất đối với tôi có vẻ như bạn cũng có thể muốn chứa các chuỗi có độ dài khác nhau. Hay tôi là người duy nhất nghĩ rằng lưới sẽ hoạt động giống như thế này:

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

Một cách để làm điều này sẽ như sau:

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

Tôi thích sử dụng hai fors, tên biến có thể đưa ra gợi ý / nhắc nhở về những gì đang diễn ra:

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

4

Chỉ để thêm một cách tiếp cận khác, cơ bản hơn:

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

4

Cảm thấy hơi khó hiểu khi không xem xét câu trả lời trong danh sách kép ở đây, để xử lý chuỗi n với nỗ lực O (1):

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

đâu all_stringslà danh sách các chuỗi bạn muốn xen kẽ. Trong trường hợp của bạn all_strings = [u, l],. Một ví dụ sử dụng đầy đủ sẽ trông như thế này:

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'

Như nhiều câu trả lời, nhanh nhất? Có lẽ không, nhưng đơn giản và linh hoạt. Ngoài ra, không có quá nhiều phức tạp được thêm vào, điều này nhanh hơn một chút so với câu trả lời được chấp nhận (nói chung, việc thêm chuỗi hơi chậm trong 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

Tuy nhiên, vẫn không nhanh bằng câu trả lời nhanh nhất: có 50,3 mili giây trên cùng dữ liệu và máy tính này
scnerd

3

Có thể nhanh hơn và ngắn hơn so với giải pháp hàng đầu hiện tại:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

Chiến lược khôn ngoan về tốc độ là làm càng nhiều càng tốt ở cấp độ C. Cùng một bản sửa lỗi zip_longest () cho các chuỗi không đồng đều và nó sẽ ra khỏi cùng một mô-đun như chain (), vì vậy không thể cho tôi quá nhiều điểm ở đó!

Các giải pháp khác mà tôi đã đưa ra trong quá trình thực hiện:

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

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

3

Bạn có thể sử dụng 1iteration_utilities.roundrobin

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

hoặc ManyIterableslớp từ cùng một gói:

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

1 Đây là từ một thư viện của bên thứ ba tôi đã viết: iteration_utilities.


2

Tôi sẽ sử dụng zip () để có được một cách dễ đọc và dễ dàng:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.