Python Regex to start from inner match

Viewed 162

I am working on a regex in Python that is converting a mathematical expression to the power format in Sympy language pow(x,y). For example, it takes 2^3 and returns pow(2,3).

My current pattern is:

# pattern
pattern = re.compile(r'(.+?)\^(.*)')

To find the math for nested expressions, I use a for loop to count the number of ^(hat)s and then use re.sub to generate the power format:

# length of the for loop
num_hat = len(re.findall(r'\^', exp))

# for nested pow
for i in range(num_hat): 
   exp = re.sub(pattern, r'pow(\1,\2)', exp)
return exp

This method does not work for nested ^ expressions such as a^b^c^d or sin(x^2)^3 as the position of the final parenthesis is not correct.

For a^b^c^d it returns pow(pow(pow(a,b,c,d)))

For sin(x^2)^3 it returns pow(pow(sin(x,2),3))

Is there any way to overcome this issue? I tried the negative lookahead but it is still not working

3 Answers

Why don't you use recursion for this? It might not be the best, but will work for your use case if the nested powers are not a lot,

A small demonstration,

import re

#Your approach
def func(exp):
    # pattern
    pattern = re.compile(r'(.+?)\^(.*)')
    
    # length of the for loop
    num_hat = len(re.findall(r'\^', exp))
    
    # for nested pow
    for i in range(num_hat):  # num_hat-1 since we created the first match already
       exp = re.sub(pattern, r'pow(\1,\2)', exp)
    return exp
    
#With recursion
def refined_func(exp):
    # pattern
    pattern = '(.+?)\^(.*)'
    
    # length of the for loop
    num_hat = len(re.findall(r'\^', exp))
    
    #base condition
    if num_hat == 1:
        search = re.search(pattern, exp)
        group1 = search.group(1)
        group2 = search.group(2)
        
        exp = "pow("+group1+", "+group2+")"
        return exp
    
    # for nested pow
    for i in range(num_hat):  # num_hat-1 since we created the first match already
       search = re.search(pattern, exp)
       if not search: # the point where there are no hats in the exp
           break
       group1 = search.group(1)
       group2 = search.group(2)
       
       exp = "pow("+group1+", "+refined_func(group2)+")"
       
    return exp

if __name__=='__main__':
    
    print(func("a^b^c^d"))
    
    print("###############")
    
    print(refined_func("a^b^c^d"))

The output of the above program is,

pow(pow(pow(a,b,c,d)))                                                                                                                
###############                                                                                                                       
pow(a, pow(b, pow(c, d))) 

Problem in your approach:

Initially you start off with the following expression,

a^b^c^d

With your defined regex, two parts are made of the above expression -> part1: a and part2: b^c^d. With these two, you generate pow(a,b^c^d). So the next expression that you work with is,

pow(a,b^c^d)

In this case now, your regex will give part1 to be pow(a,b and part2 will be c^d). Since, the pow statement used to construct the info from part1 and part2 is like pow(part1, part2), you end up having pow( pow(a,b , c^d) ) which is not what you intended.

There is no nice way of saying this, but you have an extreme case of an XY problem. What you apparently want is to convert some mathematical expression to SymPy. Writing your own regular expression seems like a very tedious, error-prone, and possibly impossible approach to this.

Being a vast symbolic library, SymPy comes with an entire parsing submodule, which allows you to tweak the parsing expressions in all detail, in particular convert_xor governs what happens to the ^ character. However, it appears you do not need to do anything since converting ^ to exponentiation is the default. You can therefore simply do:

from sympy import sympify
print( sympy.sympify("a^b^c^d") )    # a**(b**(c**d))
print( sympy.sympify("sin(x^2)^3") ) # sin(x**2)**3

Note that ** is equivalent to pow, so I am not sure why you are insisting on the latter. If you need an output that shall work in yet another programming language, that’s what the printing module is for and it’s comparably easy to change this yourself. Another thing that may help you obtain the desired form is sympy.srepr.

I made an attempt into your examples but I'll still advise you to find a math parser (from my comment) as this regex is very complex.

import re


pattern = re.compile(r"(\w+(\(.+\))?)\^(\w+(\(.+\))?)([^^]*)$")


def convert(string):
    while "^" in string:
        string = pattern.sub(r"pow(\1, \3)\5", string, 1)
    return string

print(convert("a^b^c^d"))  # pow(a, pow(b, pow(c, d)))
print(convert("sin(x^2)^3"))  # pow(sin(pow(x, 2)), 3)

Explanation: Loop while there is a ^ and replace the last match (presence of $)

Related