Наиболее подходящие для Python способ чередования двух строк



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



например:



вход:



u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'


выход:



'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
664   14  

14 ответов:

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

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 = читабельности для меня 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'

на Python 2, by далеко более быстрый способ сделать что-то, при ~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, если вы are делать это на массивных струнах и нужно каждый цикл,и по какой-то причине приходится использовать строки 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, это всего лишь 3x скорость нарезки списка для длинных строк и коэффициент от 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

нет такой вещи, как генератор с join, передача одного всегда будет медленнее, так как python сначала построит список, используя контент, потому что он делает два прохода по данным, один чтобы выяснить размер, необходимый и один на самом деле сделать соединение, которое было бы невозможно с помощью генератора:

присоединиться.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

вы также можете сделать это с помощью map и operator.add:

from operator import add

u = 'AAAAA'
l = 'aaaaa'

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

выход:

'AaAaAaAaAa'

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

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

from functools import reduce
from operator import add

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

многие из этих предложений предполагают строки имеют одинаковую длину. Может быть, это охватывает все разумные случаи использования, но по крайней мере мне кажется, что вы можете захотеть разместить строки разной длины тоже. Или я единственный, кто думает, что сетка должна работать немного так:

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

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

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

стратегия speed-wise заключается в том, чтобы сделать как можно больше на уровне 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))

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

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'

Comments

    Ничего не найдено.