8
\$\begingroup\$

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 \$3x^3 + x^2\$ instead of more uniform but more verbose \$3x^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.

\$\endgroup\$
5
\$\begingroup\$

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].

\$\endgroup\$
  • \$\begingroup\$ return instead of print \$\endgroup\$ – Maarten Fabré Apr 26 at 10:10
  • \$\begingroup\$ @Maarten Fabré indeed I've updated my answer. Thanks \$\endgroup\$ – SylvainD May 6 at 21:39
4
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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)'
\$\endgroup\$
  • 1
    \$\begingroup\$ Why (e not in {1, 0})? Is that a legacy of an earlier version where the previous if 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 Apr 26 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é Apr 26 at 13:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.