【问题标题】:Most pythonic way to interleave two strings交错两个字符串的最pythonic方法
【发布时间】:2016-04-17 19:19:07
【问题描述】:

将两个字符串连接在一起的最 Pythonic 方式是什么?

例如:

输入:

u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
l = 'abcdefghijklmnopqrstuvwxyz'

输出:

'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'

【问题讨论】:

  • 这里的答案很大程度上假设您的两个输入字符串的长度相同。这是一个安全的假设还是您需要处理它?
  • @SuperBiasedMan 如果您有解决方案,了解如何处理所有情况可能会有所帮助。这与问题有关,但与我的情况无关。

标签: python string python-2.7 python-3.x


【解决方案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】:

      如果你想要最快的方式,你可以结合itertoolsoperator.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 = "{0}{1}{2}".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'
                  

                  【讨论】:

                    猜你喜欢
                    • 2016-06-18
                    • 1970-01-01
                    • 2016-07-15
                    • 1970-01-01
                    • 2011-10-11
                    • 2021-02-24
                    • 2014-10-27
                    • 1970-01-01
                    • 2012-08-02
                    相关资源
                    最近更新 更多