Какой самый пифонический способ свести две строки вместе?
Например:
Input:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Вывод:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Какой самый пифонический способ свести две строки вместе?
Например:
Input:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Вывод:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Для меня самый пифонический * путь - это то, что в значительной степени делает то же самое, но использует оператор +
для конкатенации отдельных символов в каждой строке:
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
просто визуально анализируется легче, по крайней мере для моих глаз.
Другой способ:
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
С join()
и zip()
.
>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Если вам нужен самый быстрый способ, вы можете комбинировать 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 сначала построит список, используя контент, потому что он выполняет два прохода над данными, один для определения необходимого размера и одного на самом деле сделать соединение, которое было бы невозможно с помощью генератора:
/* 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
На 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
, но поскольку тайминги были упомянуты в другом месте, я думал, что могу присоединиться к нему.
Вы также можете сделать это, используя map
и operator.add
:
from operator import add
u = 'AAAAA'
l = 'aaaaa'
s = "".join(map(add, u, l))
Выход
'AaAaAaAaAa'
Какую карту выполняет каждый элемент из первого итерабельного u
и первых элементов второго итерабельного l
и применяет функцию, предоставленную в качестве первого аргумента add
. Затем соединение просто присоединяется к ним.
Многие из этих предложений предполагают, что строки имеют одинаковую длину. Возможно, это охватывает все разумные варианты использования, но по крайней мере для меня кажется, что вы, возможно, захотите также разместить строки различной длины. Или я единственный, кто думает, что сетка должна работать примерно так:
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:]])
Джим ответ велик, но вот мой любимый вариант, если вы не возражаете против нескольких импортов:
from functools import reduce
from operator import add
reduce(add, map(add, u, l))
Мне нравится использовать два for
s, имена переменных могут давать подсказку/напоминание о том, что происходит:
"".join(char for pair in zip(u,l) for char in pair)
Просто добавьте еще один, более базовый подход:
st = ""
for char in u:
st = "{0}{1}{2}".format( st, char, l[ u.index( char ) ] )
Потенциально быстрее и короче, чем текущее ведущее решение:
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))
Чувствует себя немного не-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
Вы можете использовать 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
.
Я бы использовал zip(), чтобы получить читаемый и простой способ:
result = ''
for cha, chb in zip(u, l):
result += '%s%s' % (cha, chb)
print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'