如何在字符串中每两个字符插入一个字符

52

有没有一种Pythonic的方式可以在字符串的每个第二个元素中插入一个元素?

我有一个字符串:'aabbccdd',我希望最终结果是'aa-bb-cc-dd'。

我不确定该如何实现这一点。

7个回答

72
>>> s = 'aabbccdd'
>>> '-'.join(s[i:i+2] for i in range(0, len(s), 2))
'aa-bb-cc-dd'

3
奇数长度的序列怎么办? - Hamish Grubijan
10
我认为这比批准答案中的 zip 巫术更符合 Python 的风格。在 Python 中,在 for 循环中不需要使用 range(len(string)),这并不意味着人们必须去发明疯狂的事情来避免使用它。 - jsbueno
2
@hamish:它会保留最后一个字符并在其前面插入连字符。这不是期望的行为吗? - SilentGhost

59
假设字符串的长度总是偶数,
>>> s = '12345678'
>>> t = iter(s)
>>> '-'.join(a+b for a,b in zip(t, t))
'12-34-56-78'
< p > 另外,t 也可以通过以下方式消除

>>> '-'.join(a+b for a,b in zip(s[::2], s[1::2]))
'12-34-56-78'

这个算法是将字符串分成一组一组的,然后用 - 字符将它们连接起来。

这段代码是这样写的。首先,它将字符串拆分为奇数位和偶数位。

>>> s[::2], s[1::2]
('1357', '2468')

然后使用zip函数将它们合并为一个元组的可迭代对象。

>>> list( zip(s[::2], s[1::2]) )
[('1', '2'), ('3', '4'), ('5', '6'), ('7', '8')]

但是元组不是我们想要的。这应该是一个字符串列表。这就是列表推导的目的。

>>> [a+b for a,b in zip(s[::2], s[1::2])]
['12', '34', '56', '78']

最后,我们使用 str.join() 来组合列表。
>>> '-'.join(a+b for a,b in zip(s[::2], s[1::2]))
'12-34-56-78'

第一段代码与原始想法相同,但在字符串较长时消耗更少的内存。


1
你能解释一下 zip 部分吗?它是在做什么? - root
@Ham:最后一个字符将会消失。 - kennytm

5
如果您想保留字符串的最后一个字符且该字符串长度为奇数,则可以修改KennyTM的答案并使用itertools.izip_longest
>>> s = "aabbccd"
>>> from itertools import izip_longest
>>> '-'.join(a+b for a,b in izip_longest(s[::2], s[1::2], fillvalue=""))
'aa-bb-cc-d'

或者

>>> t = iter(s)
>>> '-'.join(a+b  for a,b in izip_longest(t, t, fillvalue=""))
'aa-bb-cc-d'

5
我倾向于使用正则表达式来解决这个问题,因为它似乎比所有其他替代方案更简明,并且通常比它们更快。除了需要面对与正则表达式相关的传统智慧之外,我不确定是否存在缺点。
>>> s = 'aabbccdd'
>>> '-'.join(re.findall('..', s))
'aa-bb-cc-dd'

然而,这个版本对于实际的配对要求非常严格:

>>> t = s + 'e'
>>> '-'.join(re.findall('..', t)) 
'aa-bb-cc-dd'

...因此,通过微调,你可以容忍奇长度的字符串:

>>> '-'.join(re.findall('..?', t))
'aa-bb-cc-dd-e'

通常情况下你会多次这样操作,因此可以在事先创建一个快捷方式以提前开始:
PAIRS = re.compile('..').findall

out = '-'.join(PAIRS(in))

或者在真实的代码中我会使用:

def rejoined(src, sep='-', _split=re.compile('..').findall):
    return sep.join(_split(src))

>>> rejoined('aabbccdd', sep=':')
'aa:bb:cc:dd'

我不时使用这样的方法,从6字节的二进制输入中创建MAC地址表示:

>>> addr = b'\xdc\xf7\x09\x11\xa0\x49'
>>> rejoined(addr[::-1].hex(), sep=':')
'49:a0:11:09:f7:dc'

1
我建议使用..?版本开始分享正则表达式的最佳版本。在我看来,事实上它会对于奇数长度的字符串默默地丢弃最后一个字符,这使得..版本成为一个较弱的解决方案。 - Josiah Yoder
1
MAC地址的例子很棒。 - Josiah Yoder

1

这里是一个列表推导式,根据枚举的模数有条件地返回值,奇数的最后一个字符将单独成组:

for s  in ['aabbccdd','aabbccdde']:
    print(''.join([ char if not ind or ind % 2 else '-' + char
                    for ind,char in enumerate(s)
                    ]
                  )
          )
""" Output:
aa-bb-cc-dd
aa-bb-cc-dd-e
"""

0
这个简短的代码可以实现目标。如果您的字符串长度为奇数,它会删除最后一个字符。
"-".join([''.join(item) for item in zip(mystring1[::2],mystring1[1::2])])

0

正如PEP8所述:

不要依赖于CPython对形如a += ba = a + b的语句进行原地字符串连接的高效实现。即使在CPython中,这种优化也很脆弱(它仅适用于某些类型),而且在其他实现中根本不存在。

一种Pythonic的方法是避免这种连接,并允许您连接除字符串以外的可迭代对象:

':'.join(f'{s[i:i+2]}' for i in range(0, len(s), 2))

另一种更加功能化的方式可能是:

':'.join(map('{}{}'.format, *(s[::2], s[1::2]))) 

这种第二种方法有一个特殊的特点(或者说是bug),只能连接成对的字母。所以:

>>> s = 'abcdefghij'
'ab:cd:ef:gh:ij'

并且:

>>> s = 'abcdefghi'
'ab:cd:ef:gh'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接