Самый пифонический способ чередования двух строк

Какой самый пифонический способ свести две строки вместе?

Например:

Input:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

Вывод:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Ответ 1

Для меня самый пифонический * путь - это то, что в значительной степени делает то же самое, но использует оператор + для конкатенации отдельных символов в каждой строке:

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, чтобы обе версии были полностью исчерпаны.


* Чтобы сделать цитату из Zen of Python: Показатели удобочитаемости.
Pythonic = читаемость для меня; i + j просто визуально анализируется легче, по крайней мере для моих глаз.

Ответ 2

Более быстрая альтернатива

Другой способ:

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

Вывод:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

Speed ​​

Похоже, что это быстрее:

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

Ответ 3

С join() и zip().

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

Ответ 4

Если вам нужен самый быстрый способ, вы можете комбинировать 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

Ответ 5

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

но к тому времени вы уже потеряли выигрыш над сортировкой списков для небольших строк (это еще 20x скорость для длинных строк), и это даже не работает для символов, отличных от 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, но поскольку тайминги были упомянуты в другом месте, я думал, что могу присоединиться к нему.

Ответ 6

Вы также можете сделать это, используя map и operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

Выход

'AaAaAaAaAa'

Какую карту выполняет каждый элемент из первого итерабельного u и первых элементов второго итерабельного l и применяет функцию, предоставленную в качестве первого аргумента add. Затем соединение просто присоединяется к ним.

Ответ 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:]])

Ответ 8

Джим ответ велик, но вот мой любимый вариант, если вы не возражаете против нескольких импортов:

from functools import reduce
from operator import add

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

Ответ 9

Мне нравится использовать два for s, имена переменных могут давать подсказку/напоминание о том, что происходит:

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

Ответ 10

Просто добавьте еще один, более базовый подход:

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

Ответ 11

Потенциально быстрее и короче, чем текущее ведущее решение:

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

Ответ 12

Чувствует себя немного не-pythonic, чтобы не учитывать ответ на двойной список, здесь обрабатывать 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

Ответ 13

Вы можете использовать iteration_utilities.roundrobin 1

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.

Ответ 14

Я бы использовал zip(), чтобы получить читаемый и простой способ:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'