Python regex: Extract volume (mL) from strings

Viewed 418

I have the following string to extract volume (match only ml, not mg/ml)

test = [
"10ML", # 10
"10 ML", # 10
"10.5ML", # 10.5
"1MG/1ML", # [] not match
"1MG/10ML", # [] not match
"10MG/0.5ML", # [] not match
"   10ML and 15ML  ", # 10, 15
"LODEXA (DEXAMETHASONE) 5 MG/ML INJECTION", # []
"NSS.0.9% 1000 ML (PLASTIC BAG)", # 1000
"110 MLM HIDRASEC (RACECADOTIL)10 ML POWDER FOR 1-30 ML", # 10
]

Here is my current pattern and result.

pattern = re.compile("(?<!\/)([0-9]*[.]*[0-9]+)\s*ML(?![\/A-z])")

for i, s in enumerate(test):
    print(test[i], '>>' , pattern.findall(s))

10ML >> ['10']
10 ML >> ['10']
10.5ML >> ['10.5']
1MG/1ML >> []
1MG/10ML >> ['0'] # Wrong []
10MG/0.5ML >> ['.5'] # Wrong []
   10ML and 15ML   >> ['10', '15']
LODEXA (DEXAMETHASONE) 5 MG/ML INJECTION >> []
NSS.0.9% 1000 ML (PLASTIC BAG) >> ['1000']
110 MLM HIDRASEC (RACECADOTIL)10 ML POWDER FOR 1-30 ML >> ['10', '30'] # Wrong ['10']

As you can see, I got the wrong results from ["1MG/10ML", "10MG/0.5ML", "110 MLM HIDRASEC (RACECADOTIL)10 ML POWDER FOR 1-30 ML"]. It should be [[], [], ['10'].

I have tried to fix my pattern but still can't figure it out. Please help me correct my pattern. Thank you!

3 Answers

See this RegExr link for details about the components of the following regex expression.

import re

test = [
    "10ML", # 10
    "10 ML", # 10
    "10.5ML", # 10.5
    "1MG/1ML", # [] not match
    "1MG/10ML", # [] not match
    "10MG/0.5ML", # [] not match
    "   10ML and 15ML  ", # 10, 15
    "LODEXA (DEXAMETHASONE) 5 MG/ML INJECTION", # []
    "NSS.0.9% 1000 ML (PLASTIC BAG)", # 1000
    "110 MLM HIDRASEC (RACECADOTIL)10 ML POWDER FOR 1-30 ML", # 10
]

for s in test:
    re.findall(r'(?<![\-\/])(\d+(?:\.?\d+)) *ML\b', s)

Output

['10']
['10']
['10.5']
[]
[]
[]
['10', '15']
[]
['1000']
['10']

You can use

(?<![/\d])(?<!\d[.-])(\d+(?:\.\d+)?)\s*ML\b(?!/)

See the Python regex demo.

Details:

  • (?<![/\d]) - no / or digit allowed immediately to the left of the current location
  • (?<!\d[.-]) - no digit + . or - allowed immediately to the left of the current location
  • (\d+(?:\.\d+)?) - Group 1: one or more digits, and an optional sequence of a . and one or more digits
  • \s* - zero or more whitespace chars
  • ML\b - ML as a whole word
  • (?!/) - no / allowed immediately to the right of the current location.

See the Python demo:

import re
pattern = re.compile(r'(?<![/\d])(?<!\d[.-])(\d+(?:\.\d+)?)\s*ML\b(?!/)', re.A)
test = ["10ML", "10 ML", "10.5ML", "1MG/1ML", "1MG/10ML", "10MG/0.5ML", "   10ML and 15ML  ",
"LODEXA (DEXAMETHASONE) 5 MG/ML INJECTION", "NSS.0.9% 1000 ML (PLASTIC BAG)", 
"110 MLM HIDRASEC (RACECADOTIL)10 ML POWDER FOR 1-30 ML"]
for i, s in enumerate(test):
    print(test[i], '>>' , pattern.findall(s))

Output:

10ML >> ['10']
10 ML >> ['10']
10.5ML >> ['10.5']
1MG/1ML >> []
1MG/10ML >> []
10MG/0.5ML >> []
   10ML and 15ML   >> ['10', '15']
LODEXA (DEXAMETHASONE) 5 MG/ML INJECTION >> []
NSS.0.9% 1000 ML (PLASTIC BAG) >> ['1000']
110 MLM HIDRASEC (RACECADOTIL)10 ML POWDER FOR 1-30 ML >> ['10']

Another one maybe easier to read:

(?<![/\d-])(\d+\.*\d+)\s*ML\b
Related