We all know that math notation is idiosyncratic. Canonical representation of math objects often have irregular grammar rules to improve readability. For example we write a polynomial \3ドルx^3 + x^2\$ instead of more uniform but more verbose \3ドルx^3 + 1x^2 + 0x^1 + 0x^0\$. When a coefficient equals 0, you don't write the term, if the power equals \1ドル\,ドル you simply write \$x\,ドル and so on. So I wrote a simple program that outputs a string representation of a polynomial, given a list of coefficients:
def enumerate2(xs, start=0, step=1):
for x in xs:
yield (start, x)
start += step
def poly(xs):
"""Return string representation of a polynomial.
>>> poly([2,1,0])
"2x^2 + x"
"""
res = []
for e, x in enumerate2(xs, len(xs)-1, -1):
variable = 'x'
if x == 1:
coefficient = ''
elif x == -1:
coefficient = '-'
else:
coefficient = str(x)
if e == 1:
power = ''
elif e == 0:
power = ''
variable = ''
else:
power = '^' + str(e)
if x < 0:
coefficient = '(' + coefficient
power = power + ')'
if x != 0:
res.append(coefficient + variable + power)
return ' + '.join(res)
enumerate2
is a custom version of enumerate
that supports variable step. The result looks like this:
>>> poly([2,0,3,-4,-3,2,0,1,10])
'2x^8 + 3x^6 + (-4x^5) + (-3x^4) + 2x^3 + x + 10'
How do I make this code more elegant and probably more generic? Oh, and the result is sub-optimal, as negative terms are enclosed in brackets, instead of changing the preceding plus sign to minus.
3 Answers 3
Your enumerate2
is a nice touch but I am not quite convinced that this is necessary : if you are to play with the length manually, you might as well compute the power from the index manually.
Also, if you were to handle the negative with a minus instead of the plus, you'd be able to get rid of the brackets. On the other hand, you cannot use join
anymore which is a bit of a pain because it is a cool and efficient function.
Anyway, here's my try :
def poly(p, var_string='x'):
res = ''
first_pow = len(p) - 1
for i, coef in enumerate(p):
power = first_pow - i
if coef:
if coef < 0:
sign, coef = (' - ' if res else '- '), -coef
elif coef > 0: # must be true
sign = (' + ' if res else '')
str_coef = '' if coef == 1 and power != 0 else str(coef)
if power == 0:
str_power = ''
elif power == 1:
str_power = var_string
else:
str_power = var_string + '^' + str(power)
res += sign + str_coef + str_power
return res
and the corresponding output :
2x^8 + 3x^6 - 4x^5 - 3x^4 + 2x^3 + x + 10
Bug found
As I was looking at my original implementation, I found a bug which happens to be in yours too : try with [1,1,1,1,1]
.
-
\$\begingroup\$
return
instead ofprint
\$\endgroup\$Maarten Fabré– Maarten Fabré2019年04月26日 10:10:46 +00:00Commented Apr 26, 2019 at 10:10 -
\$\begingroup\$ @Maarten Fabré indeed I've updated my answer. Thanks \$\endgroup\$SylvainD– SylvainD2019年05月06日 21:39:38 +00:00Commented May 6, 2019 at 21:39
I think there's a simpler way to do this:
fmt = [
[ "", "", "" ],
[ "{c:+g}", "{sign:s}x", "{sign:s}x^{n:g}" ],
[ "{c:+g}", "{c:+g}x", "{c:+g}x^{n:g}" ]
]
def term(c, n):
return fmt[cmp(abs(c),1)+1][cmp(n,1)+1].format(sign="- +"[cmp(c,0)+1], c=c, n=n)
def poly(xs):
return "".join(term(xs[i],len(xs)-i-1) for i in xrange(len(xs)))
def suppsign(s):
return s.lstrip('+')
print suppsign(poly([1,1,1]))
The term
function takes a coefficient and power value and uses the characteristics of those two to select the appropriate format string to generate a string representing an individual term.
The poly
function uses a list comprehension to efficiently concatenate the string for each term.
The suppsign
function simply removes the leading +
from the resulting string if desired.
-
1\$\begingroup\$
suppsign(poly([1,1,1]))
would be better written aspoly([1,1,1]).lstrip('+')
\$\endgroup\$Adam– Adam2014年06月19日 04:29:20 +00:00Commented Jun 19, 2014 at 4:29 -
1\$\begingroup\$ You don't have to use square brackets inside
join
, see stackoverflow.com/a/18212201/596361 \$\endgroup\$Mirzhan Irkegulov– Mirzhan Irkegulov2014年06月19日 06:34:55 +00:00Commented Jun 19, 2014 at 6:34 -
\$\begingroup\$ I would also remove
:g
and change:+g
to:+
, as these are equivalent, see docs.python.org/2/library/string.html#formatstrings \$\endgroup\$Mirzhan Irkegulov– Mirzhan Irkegulov2014年06月19日 06:42:01 +00:00Commented Jun 19, 2014 at 6:42
enumerate2
Here you can use itertools.count
or reversed
for e, x in enumerate2(xs, len(xs)-1, -1):
becomes
for e, x in zip(itertools.count(len(xs)-1, -1), xs):
or
for e, x in zip(reversed(range(len(xs)), xs):
continue
You can skip to the next iteration in the for-loop easier by doing instead of if x != 0: ...
:
if x == 0:
continue
at the beginning of the loop
split functions
def coefficient(x):
"""returns the string representation of `x`."""
if x == 1:
return ""
if x == -1:
return "-"
return str(x)
sting multiplication and bool
for the power part, you can use string multiplication and the fact int(True) == 1
and int(False) == 0
result = coefficient(x) + variable + f"^{e}" * (e != 1)
f-string
Since python 3.6, you can do f"({result})" if x < 0 else result
instead of
coefficient = '(' + coefficient
power = power + ')'
yield
Instead of keeping a list of results, you can yield the intermediate terms. This
def poly2(xs, variable="x"):
if set(xs) == {0}:
yield "0"
return
for e, x in zip(reversed(range(len(xs))), xs):
if x == 0:
continue
if e == 0:
result = str(x)
else:
result = coefficient(x) + variable + f"^{e}" * (e != 1)
yield f"({result})" if x < 0 else result
" + ".join(poly2((1,-1,0,)))
'x^2 + (-x)'
-
1\$\begingroup\$ Why
(e not in {1, 0})
? Is that a legacy of an earlier version where the previousif e == 0
special case didn't exist? Or was the special case supposed to be removed when you added the(e not in {1, 0})
? \$\endgroup\$Peter Taylor– Peter Taylor2019年04月26日 12:57:11 +00:00Commented Apr 26, 2019 at 12:57 -
\$\begingroup\$ You are correct. This is a relic of a previous version that had no influence, but is not necessary anymore. \$\endgroup\$Maarten Fabré– Maarten Fabré2019年04月26日 13:45:32 +00:00Commented Apr 26, 2019 at 13:45