Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace numbers in string by respective result of a substraction

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.

like image 935
n611x007 Avatar asked Oct 29 '13 13:10

n611x007


1 Answers

You could try using regex, and using 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'

The regex (-?\d+)|(\+1) will either capture an optional - sign and one or more digits, OR the literal sequence +1. That way, the regex will make sure that all of your requirements when converting digits work properly.

The regex (-?\d+) by itself does the right thing most of the time, but the (\+1) exists to make sure that the string +1 always converts to zero, without a sign. If you change your mind, and want +1 to convert to +0, then you can just use only the first part of the regex: (-?d+).

You could probably compress this all into a one-liner if you wanted:

def replace_digits(text):
    return re.sub("(-?\d+)|(\+1)", lambda m: str(int(m.group(0)) - 1), text)
like image 84
Michael0x2a Avatar answered Oct 13 '22 03:10

Michael0x2a