10

I have an exercise in Python as follows:

  • a polynomial is given as a tuple of coefficients such that the powers are determined by the indexes, e.g.: (9,7,5) means 9 + 7*x + 5*x^2

  • write a function to compute its value for given x

Since I am into functional programming lately, I wrote

def evaluate1(poly, x):
  coeff = 0
  power = 1
  return reduce(lambda accu,pair : accu + pair[coeff] * x**pair[power],
                map(lambda x,y:(x,y), poly, range(len(poly))),
                0)

which I deem unreadable, so I wrote

def evaluate2(poly, x):
  power = 0
  result = 1
  return reduce(lambda accu,coeff : (accu[power]+1, accu[result] + coeff * x**accu[power]),
                poly,
                (0,0)
               )[result]

which is at least as unreadable, so I wrote

def evaluate3(poly, x):
  return poly[0]+x*evaluate(poly[1:],x) if len(poly)>0 else 0

which might be less efficient (edit: I was wrong!) since it uses many multiplications instead of exponentiation, in principle, I do not care about measurements here (edit: How silly of me! Measuring would have pointed out my misconception!) and still not as readable (arguably) as the iterative solution:

def evaluate4(poly, x):
  result = 0
  for i in range(0,len(poly)):
      result += poly[i] * x**i
  return result

Is there a pure-functional solution as readable as the imperative and close to it in efficiency?

Admittedly, a representation change would help, but this was given by the exercise.

Can be Haskell or Lisp aswell, not just Python.

user1358
  • 341
  • 1
  • 7
  • 8
    In my experience, purely functional code in the sense of not using mutable variables (which also implies not using `for` loops, for example) is a bad goal to aim for in Python. Re-binding variables *judiciously* and not mutating *objects* gives you almost all of the benefits and makes the code infinitely more readable. Since number objects are immutable and it only rebinds two local names, your "imperative" solution better realizes functional programming virtues than any "strictly pure" Python code. –  Dec 20 '13 at 17:36
  • 3
    BTW The multiplication method is Horner's method and it's more efficient than exponentiation at each step, as the exponentiation requires the very same multiplications and then some more. –  Dec 20 '13 at 17:45
  • 2
    Python is kinda notoriously ugly when you get into using `lambda`, compared to languages with a lighter anonymous syntax function. Part of that probably contributes to the "unclean" appearance. – KChaloux Dec 20 '13 at 18:00
  • @KChaloux that's exactly what I was going to say. Functional programming support is somewhat of an afterthought in Python in many respects and it kind of shows. Even so I don't think even the first version is so horribly unreadable that you can't figure out what's going on. – Evicatos Dec 20 '13 at 18:05
  • I am really confused by your code, whereas the problem-scope has a mathematical equation which is extremely clear, why don't you just use that math equation verbatim? It's fairly easily turned into a function given any language... not sure what you want to map or reduce or iterate anything for when the question is asking for a function that evaluates a single equation and gives that equation - it doesn't ask for iteration at all... – Jimmy Hoffa Dec 20 '13 at 18:38
  • @JimmyHoffa If you'll look at the comment thread on my answer, you'll see that the OP is looking for a general solution to a polynomial of any order, not just the example given. (This was not quite clear to me too at first.) – paul Dec 20 '13 at 18:47
  • `(9,7,5) means 9 + 7*x + 5*x^2` -- here is the python for this: `def evalPoly(a, b, c, d) a + (b * c) + ((d * c)^2)` -- I really don't understand what your implementations are trying to do that is so different from the exercise? – Jimmy Hoffa Dec 20 '13 at 18:50
  • @JimmyHoffa OP wants to be able to do `a + b*x + c*x^2` or `a + b*x + c*x^2 + d*x^3 + e*x^4 + f*x^5 + g*x^6 + ...` – paul Dec 20 '13 at 18:55
  • maybe I should have used list instead of tuple to avoid confusion, @ Jimmy Hoffa : paul's comment above is correct and clarifies well, "polynomial of ANY order", which I thought is obvious if you only say "polynomial" – user1358 Dec 20 '13 at 19:22
  • @delnan: after reading about Horner's method, I am suddenly satisfied with that 3rd solution of mine – user1358 Dec 20 '13 at 19:37
  • I think evaluate3 would be perfectly readable if you replaced "poly" with "polynomial", un-crunched the internal whitespace, and reversed the condition. def evaluate3(polynomial, x): return 0 if len(polynomial) == 0 else polynomial[0] + x * evaluate(polynomial[1:], x) – Clement Cherlin Mar 18 '19 at 15:58

5 Answers5

14

Horner's method is probably more computationally efficient as @delnan points out, but I would call this pretty readable in Python for the exponentiation solution:

def eval_poly(poly, x):
    return sum( [a * x**i for i,a in enumerate(poly)] )
Frank Riccobono
  • 256
  • 1
  • 4
  • 18
    Drop the square brackets and give the variables more descriptive names, and it's even better: `sum(coeff * X**power for power, coeff in enumerate(poly))` – Izkata Dec 21 '13 at 01:26
  • 1
    It kind of saddens me that the other posted answers are so complex. Use the language to your advantage! – Izkata Dec 21 '13 at 01:27
  • comprehension is like a for-loop "smuggled" into functional programming – user1358 Dec 21 '13 at 10:05
  • 8
    @user1358 No, it's syntactic sugar for the composition of `map` and `filter`. One can also think of it as a for loop of a particular shape, but loops of that shape are equivalent to the aforementioned funcitonal combinator. –  Dec 21 '13 at 16:38
7

Many functional languages have mapi implementations that allow to you have an index weaved through a map. Combine that with a sum and you have the following in F#:

let compute coefficients x = 
    coefficients 
        |> Seq.mapi (fun i c -> c * Math.Pow(x, (float)i))
        |> Seq.sum
Steven Evers
  • 28,200
  • 10
  • 75
  • 159
  • 2
    And even if they don't, as long as you understand how `map` works, it should be pretty simple to write one of your own. – KChaloux Dec 20 '13 at 18:26
4

If you just have a (fixed) tuple, why not do this (in Haskell):

evalPolyTuple (c, b, a) x = c + b*x + a*x^2

If instead you have a list of coefficients, you can use:

evalPolyList coefs x = sum $ zipWith (\c p -> c*x^p) coefs [0..]

or with a reduce as you had it:

evalPolyList' coefs x = foldl' (\sum (c, p) -> sum + c*x^p) 0 $ zip coefs [0..]
paul
  • 2,074
  • 13
  • 16
  • 1
    It is NOT homework! Not to mention that I did 3 solutions already. – user1358 Dec 20 '13 at 17:40
  • 1
    Half of the time in Python (including in this case), "tuple" means "immutable list" and is hence of arbitrary length. –  Dec 20 '13 at 17:40
  • obviously arbitrary length – user1358 Dec 20 '13 at 17:41
  • @user1358 apologies; I removed that line. – paul Dec 20 '13 at 17:42
  • @delnan I don't do much python, so that didn't register. – paul Dec 20 '13 at 17:42
  • 2
    not because of python, but because polynomial implies arbitrary length, and fixed size would not be a big of an exercise – user1358 Dec 20 '13 at 17:46
  • your "zipWith solution" is much much better – user1358 Dec 20 '13 at 17:48
  • @user1358 true, fixed size would be a rather silly exercise. I was mostly going for using destructuring on the args to make it more readable. – paul Dec 20 '13 at 18:01
  • 1
    @delnan That's interesting. I've always taken `tuple` to mean a fixed-size set of values, each of potentially differing types, that cannot be added to or removed from. I never really understood why a dynamic language with lists, that accept heterogenous inputs, would need them. – KChaloux Dec 20 '13 at 18:03
  • @KChaloux Well, tuples *are* the idiomatic way of passing around fixed-size sets of different (not just in type but in meaning) values. Though that may be mostly due to their syntax being more lightweight (`return x, y` versus `return [x, y]`). –  Dec 20 '13 at 18:28
  • @delnan Fair enough. I spent a lot more time in Ruby during college than in Python, which didn't bother with tuples. I honestly didn't get their point at _all_ until I started using Scala and Haskell. They make more **obvious** sense in statically typed languages, though I do see the appeal of having a fixed-size "you can't add to or remove from this" collection in a dynamic language. – KChaloux Dec 20 '13 at 18:30
  • +1 I've always liked Haskell's syntax when it comes to Math. I prefer F# as a language, but my example requires some messy type information. – Steven Evers Dec 20 '13 at 18:33
  • @SteveEvers F# annoys me w/ how often I have to specify types, but Haskell annoys me whenever I have to convert between types (e.g.: `(fromIntegral val)::Integer` to convert from an Int) – paul Dec 20 '13 at 18:51
  • @paul method type annotations can usually force those conversions for you... but yes, I agree it's annoying sometimes but nowhere near as annoying as F# makes it.. though F# has a great many warts larger than just the type annotations.. – Jimmy Hoffa Dec 20 '13 at 20:55
  • @KChaloux As accidentally pointed out by delnan's example, tuples are of more use as a lightweight _object_ that uses position instead of name, than as an immutable list. Hence the creation of [named tuples](http://stackoverflow.com/questions/2970608/what-are-named-tuples-in-python) – Izkata Dec 21 '13 at 01:31
4

I don't understand how your code relates to the problem scope you defined, so I'll give my version of what your code does ignoring the problem scope (based on the imperative code you wrote).

Pretty readable haskell (this approach can be easily translated to any FP language that has list destructuring and come out pure and readable):

eval acc exp val [] = acc
eval acc exp val (x:xs) = eval (acc + execPoly) (exp+1) xs
  where execPoly = x * (val^exp)

Sometimes the naive simple approach in haskell like that is cleaner than the more concise approach to people less accustomed to FP.

A more clearly imperative approach that's still completely pure is:

steval val poly = runST $ do
  accAndExp <- newSTRef (0,1)
  forM_ poly $ \x -> do
    modifySTRef accAndExp (updateAccAndExp x)
  readSTRef accAndExp
  where updateAccAndExp x (acc, exp) = (acc + x*(val^exp), exp + 1)

bonus to the second approach is being in the ST monad it will perform very well.

Though to be certain, the most likely real implementation from a Haskeller would be the zipwith mentioned in another answer above. zipWith is a very typical approach and I believe Python can mimic the zipping approach of combining functions and an indexer which can be mapped.

Jimmy Hoffa
  • 16,039
  • 3
  • 69
  • 80
3

There is a general set of steps you can use to improve readability of functional algorithms:

  • Put names on your intermediate results, instead of trying to cram everything on one line.
  • Use named functions instead of lambdas, especially in languages with verbose lambda syntax. It's much easier to read something like evaluateTerm than a long lambda expression. Just because you can use a lambda doesn't necessarily mean you should.
  • If one of your now-named functions looks like something that would come up pretty frequently, chances are it's already in the standard library. Look around. My python is a little rusty, but it looks like you basically reinvented enumerate or zipWith.
  • Often, seeing the functions and intermediate results named makes it easier to reason about what's going on and simplify it, at which point it might make sense to put a lambda back in or combine some lines back together.
  • If an imperative for loop looks more readable, chances are a for-comprehension would work well.
Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479