通过按特定顺序多次调用 re.sub() 方法来对字符串执行替换,并以这些正则表达式为条件

问题描述 投票:0回答:2
import re

#Example 1
input_str = "creo que hay 330 trillones 2 billones 18 millones 320 mil 459 47475822"


#Example 2
input_str = "sumaria 6 cuatrillones 789 billones 320 mil a esta otra cantidad de elementos  47475822 y eso daría por resultado varios millones o trillones de unidades"
mil = 1000
2 mil = 2000
322 mil = 322000

1 millon = 1000000
2 millones = 2000000
1 billon = 1000000000000
25 billones = 25000000000000
1 trillon = 1000000000000000000
3 trillones = 3000000000000000000
1 cuatrillon = 1000000000000000000000000

mil = 1 位数字后跟 3 位数字

百万 = 1 位数字后跟 6 位数字

十亿 = 1 位数字后面是 6+6 位数字

万亿 = 1 位数字后跟 6+6+6 位数字

cuatrillon = 1 位数字后接 6+6+6+6 位数字

它们之间的差是6,总是6位,如果不完整,则表示为0,因为十进制是位置制(重要数字的位置)。

当用单数表示时,例如millon,是因为前面总有一个1,即

"1 millon"
而不是
"1 millones"
(非单数加es)但如果大于1 ,例如
"2 trillones" = 2000000000000000000
"320 billones" = 320000000000000

"mil"
是一个例外,因为它没有复数,即不使用 2 千
"2 miles"
,而是放置
"2 mil"

另一个例外是1千

"1 mil"
没有写,但我只写了
"mil"
,据了解是
"1000"

xxx mil xxx

的原始正则表达式
r"\d{3}[\s|]*(?:mil)[\s|]*\d{3}"

millon、billon、trillon 和 cuatrillon 的原始正则表达式

r"\d{6}[\s|]*(?:cuatrillones|cuatrillon)[\s|]*\d{6}[\s|]*
(?:trillones|trillon)[\s|]*\d{6}[\s|]*(?:billones|billon)[\s|:]*\d{6}[\s|:]*(?:millones|millon)[\s|:]*\d{6}"

我需要使用像 re.sub() 这样的替换方法获得的输出,这个方法是放置一些正则表达式,因为替换必须条件限制在要完成的数字的中间,否则应该没有完成(如示例 2 的输出所示)

"3000000000000320459 47475822"   #example 1

"sumaria 6000000000789000000320000 a esta otra cantidad de elementos  47475822 y eso daría por resultado varios millones o trillones de unidades"   #example 2

如何改进我的正则表达式才能正确执行这些替换?或者也许使用其他方法更好?

python python-3.x regex string regex-group
2个回答
1
投票

双向:

import re

NUMBERS = [
    (10**15, 'quatrillon', 'es', False),
    (10**12, 'trillon', 'es', False),
    (10**9, 'billon', 'es', False),
    (10**6, 'millon', 'es', False),
    (10**3, 'mil', '', True)
]


def num_to_name(n):
    n = int(n) if isinstance(n, str) else n

    for size, name, multi, alone in NUMBERS:
        if n > size - 1:
            n = n // size
            if n == 1 and alone:
                return f'{name}'
            else:
                return f'{n} {name}{multi if n > 1 else ""}'
    return str(n)


def name_to_num(s, return_f=False):
    s = s[:-2] if s.endswith('es') else s
    for size, name, _, alone in NUMBERS:
        if s.lower().endswith(name):
            result = int(s[:-(len(name) + 1)]) * size if not alone or s.lower() != name else size
            return (result, size) if return_f else result
    return (int(s), 0) if return_f else int(s)


input_str = "creo que hay 330 trillones 2 billones 18 millones 320 mil 459 47475822 1000"
num_str = re.sub('\d+(?: (?:quatr|tr|b|m)illon(?:es)?| mil)?|mil',
                 lambda match: str(name_to_num(match.group(0))), input_str)
print(num_str)

name_str = re.sub('\d+',
                  lambda match: num_to_name(match.group(0)), num_str)
print(name_str)

输出:

creo que hay 330000000000000 2000000000 18000000 320000 459 47475822 1000
creo que hay 330 trillones 2 billones 18 millones 320 mil 459 47 millones mil

请注意,最终结果并不完全是输入字符串,因为输入字符串有一些可以转换的数字(如

'47 millones'
)。另外,您指出
1 mil
写为
mil
,因此向
NUMBERS
添加了一个附加字段来标记这一点,并调整
num_to_name()
来处理这种情况。

函数

num_to_name(n)
接受一个整数(或字符串,转换为整数),并使用
NUMBERS
中定义的命名找到将其写为数字的适当方法。如果它与任何尺寸都不匹配,它只会以字符串形式返回数字。

函数

name_to_num(s)
接受一个字符串并检查它是否以
NUMBERS
中定义的任何名称(带或不带复数)结尾。如果是,它会尝试将字符串的其余部分转换为整数,并返回该值乘以匹配因子。否则,它会尝试仅返回字符串的整数值。

在底部,有两个正则表达式匹配输入字符串的相关部分,使用 lambda 来替换使用 2 个函数找到的片段。

从您的评论中,我注意到您实际上希望将后续匹配的大小减小合并为一个单个数字 - 下面没有回答这个问题,我将保持代码不变)

此附加代码与第一部分一起执行此操作:

def full_name_to_num(s):
    subs = []
    last_f = 0

    def sub(s):
        s, end = (s[:-1], ' ') if s[-1] == ' ' else (s, '')
        nonlocal last_f
        n, f = name_to_num(s, True)
        if subs and (f < last_f):
            subs[-1] = subs[-1] + n
            result = ''
        else:
            subs.append(n)
            result = str(len(subs)-1) + end
        last_f = f
        return result

    temp = re.sub('(?:\d+(?: (?:quatr|tr|b|m)illon(?:es)?| mil)?|mil) ?', lambda match: sub(match.group(0)), s)
    return re.sub('\d+', lambda match: str(subs[int(match.group(0))]), temp)


def full_num_to_name(s):
    def sub(s):
        n = int(s)
        result = [str(n % NUMBERS[-1][0])] if n % NUMBERS[-1][0] else []
        for size, _, _, _ in reversed(NUMBERS):
            if (n // size) % 1000:
                result.append(num_to_name(n % (size * 1000)))
        return ' '.join(reversed(result))

    return re.sub('\d+', lambda match: sub(match.group(0)), s)


input_str = "creo que hay 330 trillones 2 billones 18 millones 320 mil 459 47475822"
full_num_str = full_name_to_num(input_str)
print(full_num_str)

full_name_str = full_num_to_name(full_num_str)
print(full_name_str)

额外输出:

creo que hay 330002018320459 47475822
creo que hay 330 trillones 2 billones 18 millones 320 mil 459 47 millones 475 mil 822

1
投票

我认为你不应该使用纯正则表达式,而应该混合一些巧妙的算术解析。这是如何解决它的示例(请注意,它实际上以有意义的方式转换数字,而不仅仅是将它们连接起来,因此结果与您定义的期望有所不同)

import re

input_str1 = "creo que hay 330 trillones 2 billones 18 millones 320 mil 459 47475822"
input_str2 = "sumaria 6 cuatrillones 789 billones 320 mil a esta otra cantidad de elementos  47475822 y eso daría por resultado varios millones o trillones de unidades"


def wrap_word(word: str) -> str:
    return fr"(\d+)\s+\b{word}\b"


def wrap_num(num: int) -> str:
    return f"\\1*{str(num)}"


def eval_mult_exp(text: str) -> str:
    for op1, op2 in re.findall("(\\d+)\*(\\d+)", text):
        text = re.sub(pattern=op1+"\*"+op2, repl=str(int(op1)*int(op2)), string=text)
    return text


def eval_addition_exp(text: str) -> str:
    if not re.search("(\\d+) (\\d+)", text):  # recursion halting condition
        return text

    for op1, op2 in re.findall("(\\d+) (\\d+)", text):
        text = re.sub(pattern=op1+" "+op2, repl=str(int(op1)+int(op2)), string=text)
    return eval_addition_exp(text)


def word_to_num(word: str) -> str:
    for pattern, numeric_replacement in [
        (wrap_word("mil"), wrap_num(10**3)),
        (wrap_word("millones(es)?"), wrap_num(10**6)),
        (wrap_word("billon(es)?"), wrap_num(10**9)),
        (wrap_word("trillon(es)?"), wrap_num(10**12)),
        (wrap_word("cuatrillon(es)?"), wrap_num(10**15)),
    ]:
        word = re.sub(pattern, numeric_replacement, word)
    return word


print(eval_addition_exp(eval_mult_exp(word_to_num(input_str2))))

输出[1]:

sumaria 6000789000320000 a esta otra cantidad de elementos 47475822 y eso daría por resultado varios millones o trillones de unidades

请原谅我的西班牙语:)

© www.soinside.com 2019 - 2024. All rights reserved.