How to capture the repeating operands of a multiplication using python regex

Viewed 78

I am processing equations using python regex.
I want to capture the repeating operands of the multiplication like below

5*5 --> 5  
3.14*4 --> no return  
(10-4)*(10-4) --> (10-4)

I found individual patterns to address the scenarios with or without parenthese

pattern = r"(?=(?:\D|\b))(\d+)\*\1(?=(?:\D|\b))"

re.findall(pattern, "15*15") # ['15']
re.findall(pattern, "3.14*4") # []

pattern = r"(\([\d\-\+]+\))\*\1"
re.findall(pattern, "(10-4)*(10-4)") # ['(10-4)']

But I found it's tricky that I cannot combine both patterns together, either by alternation (|) or ? quantifier.

pattern = r"(?=(?:\D|\b))(\d+)\*\1(?=(?:\D|\b))|(\([\d\-\+]+\))\*\1"  # Alternation

re.findall(pattern, "15*15")  # [('15', '')]
re.findall(pattern, "3.14*4") # []
re.findall(pattern, "(10-4)*(10-4)") # []

pattern = r"(?=(?:\D|\b))(\(?[\d\-\+]+\)?)\*\1(?=(?:\D|\b))" # with ? for parenthese

re.findall(pattern, "15*15")  # ['15']
re.findall(pattern, "3.14*4") # []
re.findall(pattern, "(10-4)*(10-4)") # []

Is there any workaround or should I have to do it seperately?

2 Answers

You could use

(?<!\d)((\()?\d+(?:[.-]\d+)?(?(2)\)))\*\1(?!\d)

Explanation

  • (?<!\d) Assert not a digit directly to the left
  • ( Capture group 1
    • (\()? Optionally match ( in group 2
    • \d+ Match 1+ digits
    • (?:[.-]\d+)? Optionally match . or - and 1+ digits
    • (?(2)\)) Match ) if group 2 exists
  • ) Close group 1
  • \*\1 Match * followed by a backreference what is captured in group 1
  • (?!\d) Assert not a digit directly to the right

Regex demo | Python demo

import re
 
regex = r"(?<!\d)((\()?\d+(?:[.-]\d+)?(?(2)\)))\*\1(?!\d)"
 
test_str = ("5*5 --> 5  \n"
    "3.14*4 --> no return  \n"
    "(10-4)*(10-4) --> (10-4)")
 
for m in re.finditer(regex, test_str):
    print(m.group(1))

Output

5
(10-4)

You can use

(?<!\d)(\d+)\*\1(?!\d)|(\([\d+-]+\))\*\2

See the regex demo. Details:

  • (?<!\d)(\d+)\*\1(?!\d) - Any one or more digits (captured into Group 1) not enclosed with digits, followed with a * char and then the same number as in Group 1 (not followed with a digit
  • | - or
  • (\([\d+-]+\))\*\2 - Group 2, capturing (, then one or more digits, - or + and then a ) char, then matches a * char and the same value as in Group 2.

In Python, use

import re
rx = r"(?<!\d)(\d+)\*\1(?!\d)|(\([\d+-]+\))\*\2"
texts = ["5*5", "3.14*4", "(10-4)*(10-4)"]
for text in texts:
  print( [f'{x.group(1) or ""}{x.group(2) or ""}' for x in re.finditer(r'(?<!\d)(\d+)\*\1(?!\d)|(\([\d+-]+\))\*\2', text)] )

See the online Python demo. Output:

['5']
[]
['(10-4)']
Related