Найбільш пітонічний спосіб переплетення двох струн


115

Який найпітонічніший спосіб з’єднати дві струни разом?

Наприклад:

Вхід:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Вихід:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

2
Відповіді тут значною мірою припускають, що два вхідних рядка будуть однакової довжини. Це безпечне припущення чи вам потрібно це впоратися?
SuperBiasedMan

@SuperBiasedMan Можливо, буде корисно подивитися, як вирішити всі умови, якщо у вас є рішення. Це стосується питання, але конкретно не в моєму випадку.
Брендон Део

3
@drexx Верхній відповів так чи інакше прокоментував своє рішення, тому я просто відредагував це на своїй посаді, щоб воно було всеосяжним.
SuperBiasedMan

Відповіді:


127

Для мене найбільш пітонічним * способом є наступний, який в значній мірі робить те саме, але використовує +оператор для об'єднання окремих символів у кожному рядку:

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для забезпечення повного вичерпання обох рядків.


* Щоб взяти цитату з дзен Python : Читання рахується .
Pythonic = читабельність для мене; i + jпросто візуально розбирається легше, принаймні для моїх очей.


1
Напруга кодування для n рядків є O (n). І все-таки добре, поки п є малим.
TigerhawkT3

Можливо, ваш генератор спричиняє більше накладних витрат, ніж з'єднання.
Padraic Cunningham

5
Пробіг, "".join([i + j for i, j in zip(l1, l2)])і це, безумовно, буде найшвидшим
Padraic Cunningham

6
"".join(map("".join, zip(l1, l2)))навіть швидше, хоча не обов'язково більш пітонічне.
Алексі Торхамо

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

Пітон 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)))
Блендер

1
Це скоротить список, якщо один коротший за інший, як і zipзупиняється, коли коротший список був повністю повторений.
SuperBiasedMan

5
@SuperBiasedMan - Так. itertools.zip_longestможе використовуватися, якщо це стає проблемою.
TigerhawkT3

18

На Python 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_lolong :

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?? зайвий
Копперфілд

1
не згідно з моїми тестами, ви втрачаєте час на складання посередницького списку і перемагаєте мету використання ітераторів. Зачекайте, "".join(list(...))будь ласка, 6.715280318699769, а час "".join(starmap(...))мені дасть 6.46332361384313
Копперфілд

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, ти говориш про виклик у списку чи передачу списку?
Padraic Cunningham

12

Ви також можете зробити це з допомогою mapі operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Вихід :

'AaAaAaAaAa'

Що робиться на карті, це забирає кожен елемент з першого ітерабельного, uа перші елементи з другого ітерабельного, lі застосовує функцію, що подається як перший аргумент add. Тоді приєднуйтесь просто приєднуйтесь до них.


9

Відповідь Джима чудова, але ось мій улюблений варіант, якщо ви не заперечуєте над парою імпорту:

from functools import reduce
from operator import add

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

7
Він сказав найбільше пітонічного, не самого хаскельського;)
Керт

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

Відчуває себе трохи непітонічним, щоб не враховувати відповідь на подвійний список розуміння тут, обробляти n рядок зусиллями 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 мс на цих самих даних та комп'ютері
scnerd

3

Потенційно швидше і коротше, ніж поточне провідне рішення:

from itertools import chain

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

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

Стратегія, що залежить від швидкості, полягає в тому, щоб зробити якомога більше на рівні С. Це ж виправлення zip_lo Long () для нерівних рядків, і воно виходитиме з того ж модуля, що і ланцюг (), тому я не можу набрати мені занадто багато очок там!

Інші рішення, які я придумав:

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'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.