交错两个字符串的最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

但是将izipchain.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】:

您也可以使用mapoperator.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】:

我喜欢使用两个fors,变量名可以提示/提醒正在发生的事情:

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

【讨论】:

【参考方案14】:

我会使用 zip() 来获得一种可读且简单的方法:

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

print result
# 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

【讨论】:

以上是关于交错两个字符串的最pythonic方法的主要内容,如果未能解决你的问题,请参考以下文章

如何交错或创建两个字符串的唯一排列(无递归)

两个字符串的交错

java 交错的两个字符串

如何交错来自两个文本文件的行

LeetCode第九十七题—交错字符串—Python实现

为啥这个异步程序不交错输出?