4

I have a regex issue which do not seems as common as I thought : I would like to extract all numeric values having px units, apply some calculation, and then re-inject the new value within my string. I don't want to include the px string (see exemple below), but I could use an alternative method which keep them, or change the unit type.

Exemple, multiplying values by 2.5 :

from "2px aperture 12px science 2.1px yummy cake"

I want "5 aperture 30 science 5.25 yummy cake"

I made a sketchy script, but I don't get quite the desired output :

import re
my_string = "2px aperture 12px science 2.1px yummy cake"
nb_list= re.findall(r"([0-9.]+)px", my_string)
splitted_string = re.findall('.*?px', my_string)
print(f"splitted_string = {splitted_string}")
print(f"nb_list = {nb_list}")
new_list = []
for i in range(0, len(nb_list)):
  new_n = str(float(nb_list[i])*2.5)
  new_string = re.sub(r"[0-9.]+px", new_n, splitted_string[i])
  new_list.append(new_string)
new_list = ''.join(new_list)
print(f"new_list = {new_list}")

Result :

new_list = 5.0 aperture 30.0 science 5.25

I understand why I get this result, but I don't know what to change to get the desired output.

2
  • Try to use new_n = re.sub(r"(?<=\d)(.0)(?=[^\d])",'',new_n) after new_n = str(float(nb_list[i])*2.5) Commented Jul 9, 2019 at 14:53
  • 2
    Try re.sub(r"(\d+(?:\.\d+)?)px", lambda x: str(float(x.group(1))*2.5), my_string). Maybe r"(\d+(?:\.\d+)?)px\b" will be more precise though as px will be matched as a whole word. See ideone.com/68NH7f Commented Jul 9, 2019 at 14:54

1 Answer 1

9

Just use re.sub with a callback:

r = re.sub(
    r'(\d+(\.\d+)?)px\b',
    lambda m: '{:g}'.format(float(m.group(1)) * 2.5),
    s)

It's easy to extend this to multiple units, for example:

units = {
    'px': 2.5,
    'em': 4,
}

r = re.sub(
    fr'(\d+(\.\d+)?)({"|".join(units)})\b',
    lambda m: '{:g}'.format(float(m.group(1)) * units[m.group(3)]),
    s)
Sign up to request clarification or add additional context in comments.

2 Comments

TIL about {:g}. I always did .rstrip('0').rstrip('.') but this is shorter. Upvoted.
That is exactly what I was trying to do ! I did not think about lambda within re.sub, and did not know about f+r strings. I will still take some time to fully understand this, but this seems neat and concise, thanks a lot !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.