Какой самый питонический способ соединить две строки вместе?
Например:
Входные данные:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
Вывод:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
Какой самый питонический способ соединить две строки вместе?
Например:
Входные данные:
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
модуля, чтобы убедиться, что обе строки полностью исчерпаны.
* Приведу цитату из «Дзен Python» : удобочитаемость имеет значение .
Pythonic = читабельность для меня; i + j
просто визуально легче анализируется, по крайней мере, для моих глаз.
"".join([i + j for i, j in zip(l1, l2)])
и это, безусловно , самый быстрый
"".join(map("".join, zip(l1, l2)))
даже быстрее, хотя и не обязательно более питоническим.
По-другому:
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'
''.join(itertools.chain.from_iterable(zip(u, l)))
zip
остановится, когда более короткий список будет полностью повторен.
itertools.zip_longest
можно использовать, если это станет проблемой.
В Python 2 гораздо более быстрый способ делать что-либо, примерно в 3 раза быстрее, чем нарезка списка для небольших строк и в ~ 30 раз для длинных, - это
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
решения, но, поскольку время было упомянуто в другом месте, я подумал, что могу присоединиться.
Если вам нужен самый быстрый способ, вы можете объединить 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
list
?? не нужен
"".join(list(...))
дает мне 6.715280318699769 и timeit "".join(starmap(...))
дает мне 6.46332361384313
"".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, и все говорят то же самое, и я запускаю его пару раз и всегда то же самое
Вы также можете сделать это, используя map
и operator.add
:
from operator import add
u = 'AAAAA'
l = 'aaaaa'
s = "".join(map(add, u, l))
Выход :
'AaAaAaAaAa'
Что делает map, так это то, что она берет каждый элемент из первой итерации 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:]])
Чувствует себя немного непитоничным, чтобы не рассматривать здесь ответ с двойным списком, чтобы обрабатывать 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
Потенциально быстрее и короче, чем текущее ведущее решение:
from itertools import chain
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
res = "".join(chain(*zip(u, l)))
Стратегия с точки зрения скорости - делать как можно больше на высшем уровне. То же самое исправление 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))
Вы можете использовать 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
.