I've made a code in Python 3 which converts a polynomial with integer coefficients (in simplified integer form) from string form to a list of tuples of the form (coefficient, exponent, variable). It is assumed the variable name will be one character long, and will be a character in the alphabet.
For example:
>>> get_list_of_powers("3x^2 - x + 1")
[(3, 2, 'x'), (-1, 1, 'x'), (1, 0, None)]
>>> get_list_of_powers("t^3 - 3t^2 + t + 1")
[(1, 3, 't'), (-3, 2, 't'), (1, 1, 't'), (1, 0, None)]
>>> get_list_of_powers("t^3 - 3t^2 + t +") # the + doesn't have a term just after it, so this is invalid
...
ValueError: Invalid polynomial: t^3-3t^2+t+.
>>> get_list_of_powers("x + 2^5") # 2^5 is not in simplified integer form, so this is invalid
...
ValueError: Invalid polynomial: x+2^5.
>>> get_list_of_powers("t^3 - 3t^2 + x + 1")
[(1, 3, 't'), (-3, 2, 't'), (1, 1, 'x'), (1, 0, None)]
>>> get_list_of_powers("x + x + 1") # My output doesn't need to simplify the polynomial
[(1, 1, 'x'), (1, 1, 'x'), (1, 0, None)]
with each tuple (a, b, x) in the list representing an expression ax^b in the polynomial. Note that the constant term of the polynomial will always have None
as the variable name.
When I say "simplified integer form", I mean an integer that is written explicitly in terms of only digits. So, for example, 1e5
is not in "simplified integer form", neither is 2^5
, but 10000
would be.
My code for the function get_list_of_powers
is shown below:
def get_list_of_powers(polynomial):
def not_empty_match(match):
return any(match)
def parse_match(match):
if not match[0] or match[0] == "+":
coefficient = 1
elif match[0] == "-":
coefficient = -1
else:
coefficient = int(match[0])
if match[1] and match[2] and match[3]:
exponent = int(match[3])
elif match[1] and not match[2] and not match[3]:
exponent = 1
elif match[0] and match[0] not in ("+", "-") and not match[1] and not match[2] and not match[3]:
exponent = 0
else:
# Handles cases that my validation pattern doesn't spot e.g. x + 2^5
raise ValueError(f"Invalid polynomial: {polynomial}.")
return (coefficient, exponent, match[1] if match[1] else None)
validation_pattern = re.compile(r"""(
([-+]?[0-9]*) # get sign and value of coefficient
([a-zA-Z]?) # get letter
(\^?) # get power symbol
([0-9]*) # get exponent
)* # any amount of valid singular polynomial expressions
""", re.VERBOSE)
pattern = re.compile(r"""
([-+]?[0-9]*) # get sign and value of coefficient
([a-zA-Z]?) # get letter
(\^?) # get power symbol
([0-9]*) # get exponent
""", re.VERBOSE)
polynomial = polynomial.replace(" ", "")
if not re.fullmatch(validation_pattern, polynomial):
raise ValueError(f"Invalid polynomial: {polynomial}.")
power_list = re.findall(pattern, polynomial)
power_list = filter(not_empty_match, power_list)
return list(map(parse_match, power_list))
I don't like the redundancy of the two patterns validation_pattern
and pattern
looking almost exactly the same, and I also don't like how my input validation is split in two places, since re.fullmatch(validation_pattern, polynomial)
by itself doesn't work for all cases (e.g. the invalid polynomial x + 2^5
). Is there a way for my code to repeat itself less?
I also think that my regular expression could also be improved, as my regular expression currently matches an empty string (and hence empty matches need to be explicitly filtered out). Is there a way for this to be prevented?
x+y
), but for example notx*y
. \$\endgroup\$