5

I have a string like this:

"foo 15 bar -2hello 4 asdf+2"

I'd like to get:

"foo 14 bar -3hello 3 asdf+1"

I would like to replace every number (sequence of digits as signed base-10 integers) with the result of a subtraction executed on each of them, one for each number.

I've written a ~50 LOC function that iterates on characters, separating signs, digits and other text, applying the function and recombining the parts. Although it has one issue my intent with the question is not to review it. Instead I'm trying to ask, what is the pythonic way to solve this, is there an easier way?

For reference, here is my function with the known issue, but my intention is not asking for a review but finding the most pythonic way instead.

edit to answer the wise comment of Janne Karila:

  • preferred: retain sign if given: +2 should become +1
  • preferred: zero has no sign: +1 should become 0
  • preferred: no spaces: asdf - 4 becomes asdf - 3
  • required: only one sign: -+-2 becomes -+-3

edit on popular demand here is my buggy code :)

DISCLAIMER: Please note I'm not interested in fixing this code. I'm asking if there is a better approach than something like mine.

def apply_to_digits(some_str,handler):
    sign = "+"
    started = 0
    number = []
    tmp = []
    result = []
    for idx,char in enumerate(some_str):
        if started:
            if not char.isdigit():
                if number:
                    ss = sign + "".join(number)
                    rewritten = str(handler(int(ss)))
                    result.append(rewritten)
                elif tmp:
                    result.append("".join(tmp))
                number = []
                tmp = []
                sign = "+"
                started = 0
                # char will be dealt later
            else:
                number.append(char)
                continue
        if char in "-+":
            sign = char
            started = 1
            if tmp:
                result.append("".join(tmp))
                tmp = []
            tmp.append(char)
            continue
        elif char.isdigit():
            started = 1
            if tmp:
                result.append("".join(tmp))
                tmp = []
            number.append(char)
        else:
            tmp.append(char)
    if number:
        ss = sign + "".join(number)
        rewritten = str(handler(int(ss)))
        result.append(rewritten)
    if tmp:
        result.append("".join(tmp)), tmp
    return "".join(result)
#

DISCLAIMER: Please note I'm not interested in fixing this code. I'm asking if there is a better approach than something like mine.

4

1 回答 1

12

您可以尝试使用正则表达式,并使用re.sub

>>> pattern = "(-?\d+)|(\+1)"
>>> def sub_one(match):
        return str(int(match.group(0)) - 1)

>>> text = "foo 15 bar -2hello 4 asdf+2"
>>> re.sub(pattern, sub_one, text)
'foo 14 bar -3hello 3 asdf+1'

正则表达式(-?\d+)|(\+1)将捕获一个可选的-符号和一个或多个数字,或者是文字序列+1。这样,正则表达式将确保您在转换数字时的所有要求都能正常工作。

正则表达式(-?\d+)本身在大多数情况下都会做正确的事情,但它的(\+1)存在是为了确保字符串+1始终转换为零,没有符号。如果您改变主意并想+1转换为+0,那么您可以只使用正则表达式的第一部分:(-?d+).

如果您愿意,您可以将这一切压缩成一个单行:

def replace_digits(text):
    return re.sub("(-?\d+)|(\+1)", lambda m: str(int(m.group(0)) - 1), text)
于 2013-10-29T14:22:34.730 回答