交错两个字符串的最pythonic方法
Posted
技术标签:
【中文标题】交错两个字符串的最pythonic方法【英文标题】:Most pythonic way to interleave two strings 【发布时间】:2016-04-17 19:19:07 【问题描述】:将两个字符串连接在一起的最 Pythonic 方式是什么?
例如:
输入:
u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'
输出:
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
【问题讨论】:
这里的答案很大程度上假设您的两个输入字符串的长度相同。这是一个安全的假设还是您需要处理它? @SuperBiasedMan 如果您有解决方案,了解如何处理所有情况可能会有所帮助。这与问题有关,但与我的情况无关。 【参考方案1】:对我来说,最 Pythonic* 的方式是以下,几乎做同样的事情,但使用 +
运算符连接每个字符串中的各个字符:
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
停止迭代较短字符串的结尾。在这种情况下,应该使用itertools
模块中的zip_longest
(Python 2 中的izip_longest
)而不是zip
,以确保两个字符串都完全用尽.
*引用 the Zen of Python 的话:可读性很重要。
Pythonic = 可读性 对我来说; i + j
只是在视觉上更容易解析,至少对我来说是这样。
【讨论】:
n 个字符串的编码工作量是 O(n)。不过,只要 n 小就很好。 您的生成器可能会导致比连接更多的开销。 运行"".join([i + j for i, j in zip(l1, l2)])
肯定是最快的
"".join(map("".join, zip(l1, l2)))
甚至更快,虽然不一定更 Pythonic。【参考方案2】:
更快的选择
另一种方式:
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
【讨论】:
【参考方案3】:使用join()
和zip()
。
>>> ''.join(''.join(item) for item in zip(u,l))
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'
【讨论】:
或''.join(itertools.chain.from_iterable(zip(u, l)))
如果一个列表比另一个短,这将截断一个列表,因为 zip
在较短的列表被完全迭代后停止。
@SuperBiasedMan - 是的。如果有问题,可以使用itertools.zip_longest
。【参考方案4】:
在 Python 2 上,far 更快的处理方式是
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
解决方案,但由于其他地方提到了时间安排,我想我不妨加入。
【讨论】:
【参考方案5】:如果你想要最快的方式,你可以结合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和定时"".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 的机器上运行测试,甚至在带有 python 3.4.3 的 www.python.org 的虚拟控制台中运行测试,并且都说相同,我运行了几次并且总是相同
我读过并且我看到的是它总是在其缓冲区变量中内部构建一个列表,而不管你传递给它的内容,所以更有理由不给它一个列表
@Copperfield,你说的是列表调用还是传递列表?【参考方案6】:
您也可以使用map
和operator.add
来做到这一点:
from operator import add
u = 'AAAAA'
l = 'aaaaa'
s = "".join(map(add, u, l))
输出:
'AaAaAaAaAa'
map 的作用是从第一个可迭代 u
中获取每个元素,并从第二个可迭代 l
中获取第一个元素,并应用作为第一个参数 add
提供的函数。然后加入就加入他们。
【讨论】:
【参考方案7】:Jim 的回答很棒,但如果您不介意几个进口,这是我最喜欢的选择:
from functools import reduce
from operator import add
reduce(add, map(add, u, l))
【讨论】:
他说的是 Pythonic,而不是 Haskellic ;)【参考方案8】:很多这些建议都假设字符串的长度相同。也许这涵盖了所有合理的用例,但至少在我看来,您可能也想容纳不同长度的字符串。或者我是唯一一个认为网格应该像这样工作的人:
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:]])
【讨论】:
【参考方案9】:我喜欢使用两个for
s,变量名可以提示/提醒正在发生的事情:
"".join(char for pair in zip(u,l) for char in pair)
【讨论】:
【参考方案10】:只是添加另一种更基本的方法:
st = ""
for char in u:
st = "012".format( st, char, l[ u.index( char ) ] )
【讨论】:
【参考方案11】:感觉有点 unpythonic 不考虑这里的双重列表理解答案,以 O(1) 的努力处理 n 字符串:
"".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 毫秒【参考方案12】:可能比当前领先的解决方案更快、更短:
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))
【讨论】:
【参考方案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'
【讨论】:
以上是关于交错两个字符串的最pythonic方法的主要内容,如果未能解决你的问题,请参考以下文章