将包含字母和数字的字符串分成多个部分

4
我有一个字符串,它由小写字母和数字(整数或浮点数)的交替字符串组成,长度任意。我希望将其分成部分,每个部分的最大可能大小是一个字符串或者代表数字的字符串。
我不需要考虑数字的特殊形式,例如指数、十六进制等等,只需要简单的浮点型或整数。
以下是一些例子:
>>> split("")
()
>>> split("p")
('p',)
>>> split("2")
('2',)
>>> split("a2b3")
('a', '2', 'b', '3')
>>> split("a2.1b3")
('a', '2.1', 'b', '3')
>>> split("a.1b3")
('a', '.1', 'b', '3')

然而,以下调用应该会引发一些错误:
>>> split(3)
>>> split("a0.10.2")
>>> split("ab.c")

我第一次尝试使用 re.split。然而,这种尝试相当幼稚,它不会保留分隔符,以防我使用这些字母:

>>> re.split("[a-z]", "a.1b3")
['', '.1', '3']

我的第二次尝试是使用itertools.groupby。 问题在于它不关心数字的形式,所以例如:

>>> islowalpha = labmda s: str.isalpha(s) and str.islower(s)
>>> [''.join(g) for _, g in itertools.groupby("a0.10.2b", islowalpha)]  # should raise
['a', '0.10.2', 'b']

注意:对于输出的形式,我并不在意,只要它可以迭代即可。

注意:我已经阅读了这篇文章,但是我无法将解决方案适应到我的问题上。主要的区别是我需要允许仅有可接受的数字,而不是简单的数字和点号列表。


"a0.10.2"有什么问题,为什么它与"a2.1b3"不同? - Roman Bodnarchuk
由于0.10.2既不是一个数字,也不是数字和小写字符串的交替子串。 - Bach
明白了,忘记了“交替”的限制。 - Roman Bodnarchuk
4个回答

3
import re

def split_gen(x):
    for f, s in re.findall(r'([\d.]+)|([^\d.]+)', x):
        if f:
            float(f)
            yield f
        else:
            yield s

def split(x):
    '''
    >>> split("")
    ()
    >>> split("p")
    ('p',)
    >>> split("2")
    ('2',)
    >>> split("a2b3")
    ('a', '2', 'b', '3')
    >>> split("a2.1b3")
    ('a', '2.1', 'b', '3')
    >>> split("a.1b3")
    ('a', '.1', 'b', '3')
    >>> split(3)
    Traceback (most recent call last):
    ...
    TypeError: expected string or buffer
    >>> split("a0.10.2")
    Traceback (most recent call last):
    ...
    ValueError: could not convert string to float: '0.10.2'
    >>> split("ab.c")    
    Traceback (most recent call last):
    ...
    ValueError: could not convert string to float: '.'
    '''
    return tuple(split_gen(x))

if __name__ == '__main__':
    import doctest
    doctest.testmod()

1
一点使用 re.subitertools.cycle 的玩耍:

def split(s):
    res = []

    def replace(matchobj):
        res.append(matchobj.group(0))
        return ''

    letter = re.compile('^([a-z]+)')
    number = re.compile('^(\.\d|\d+\.\d+|\d+)')

    if letter.match(s):
        c = itertools.cycle([letter, number])
    else:
        c = itertools.cycle([number, letter])

    for op in c:
        mods = op.sub(replace, s)
        if len(s) == len(mods):
            return
        elif not mods:
            return res
        s = mods

基本思路 - 创建两个交替的re模式,并尝试将输入字符串与它们匹配。
使用一些示例进行演示:
>>> split("2")
['2']
>>> split("a2b3")
['a', '2', 'b', '3']
>>> split("a.1b3")
['a', '.1', 'b', '3']
>>> split("a0.10.2")
>>> split("ab.c")

0
问题在于你的问题前提是可行的。如何区分浮点数和任意字符串?有很多种解释方式。例如:
0.10.2 这可以表示0.1、0.2或0、.10、.2
如果数字是27.6734.98呢? 你需要先指定数字的类型和格式。例如:每个数字只有小数点后一位数字。

我不同意你的批评。我明确提到像27.6734.98这样的字符串应该引发错误,因为它们不由交替小写字母字符串和数字字符串组成。 - Bach
抱歉如果听起来像是批评。我的意思是,如果数字部分是类似于“27.6734.98”的东西会怎样呢?因此,一个完整的示例输入可能是“ab27.6734.98h”。 - IsThatYou

-1
import re

string = 'a.2b3c4.5d'

REG_STR = r'([a-zA-Z])|(\.\d+)|(\d+\.\d+)|(\d+)'
matches = [m.group() for m in re.finditer(REG_STR, string) if re.finditer(REG_STR, string)]

额,没看清楚。正则表达式应该是 '([a-zA-Z])|(\.\d+)|(\d+\.\d+)|(\d+)' - Toadeeza
请编辑您的帖子并提供您建议使用的答案。 - jww

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