Una soluzione puramente funzionale a questo problema può essere pulita quanto l'imperativo?

10

Ho un esercizio in Python come segue:

  • un polinomio è dato come una tupla di coefficienti tale che i poteri sono determinati dagli indici, ad esempio: (9,7,5) significa 9 + 7 * x + 5 * x ^ 2

  • scrive una funzione per calcolare il suo valore per x dato

Da quando sono in programmazione funzionale ultimamente, ho scritto

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)

che ritengo illeggibile, quindi ho scritto

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]

che è almeno illeggibile, così ho scritto

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

che potrebbe essere meno efficiente (edit: I was wrong!) visto che usa molte moltiplicazioni invece dell'esponenziazione, in linea di principio, non mi importa delle misure qui (modifica: Quanto stupido da parte mia! Misurare avrebbe messo in evidenza il mio equivoco !) e ancora non è leggibile (probabilmente) come soluzione iterativa:

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

Esiste una soluzione puramente funzionale leggibile quanto l'imperativo e vicino all'efficienza?

Certo, un cambio di rappresentazione sarebbe stato d'aiuto, ma questo è stato dato dall'esercizio.

Può essere anche Haskell o Lisp, non solo Python.

    
posta user1358 20.12.2013 - 18:12
fonte

5 risposte

13

Il metodo di Horner è probabilmente più efficiente dal punto di vista computazionale come sottolinea @delnan, ma chiamerei questo piuttosto leggibile in Python per la soluzione di esponenziazione:

def eval_poly(poly, x):
    return sum( [a * x**i for i,a in enumerate(poly)] )
    
risposta data 20.12.2013 - 22:37
fonte
7

Molti linguaggi funzionali hanno implementazioni mapi che ti permettono di avere un indice intrecciato attraverso una mappa. Combinalo con una somma e hai il seguente in F #:

let compute coefficients x = 
    coefficients 
        |> Seq.mapi (fun i c -> c * Math.Pow(x, (float)i))
        |> Seq.sum
    
risposta data 20.12.2013 - 19:14
fonte
4

Non capisco in che modo il tuo codice si riferisce all'ambito del problema che hai definito, quindi darò la mia versione di ciò che il tuo codice ignora l'ambito del problema (basato sul codice imperativo che hai scritto).

haskell abbastanza leggibile (questo approccio può essere facilmente tradotto in qualsiasi linguaggio FP che abbia una lista destrutturante e che sia puro e leggibile):

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

A volte l'approccio ingenuo di haskell è più pulito di un approccio più conciso alle persone meno abituate alla FP.

Un approccio più chiaramente imperativo che è ancora completamente puro è:

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)

Il bonus al secondo approccio è nella ST monad che si esibirà molto bene.

Anche se per essere certi, l'implementazione reale più probabile da un Haskeller sarebbe lo zipwith menzionato in un'altra risposta sopra. zipWith è un approccio molto tipico e credo che Python possa imitare l'approccio zipping della combinazione di funzioni e un indicizzatore che può essere mappato.

    
risposta data 20.12.2013 - 20:13
fonte
4

Se solo ha una tupla (fissa), perché non farlo (in Haskell):

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

Se invece hai una lista di coefficienti, puoi usare:

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

o con una riduzione come avevate:

evalPolyList' coefs x = foldl' (\sum (c, p) -> sum + c*x^p) 0 $ zip coefs [0..]
    
risposta data 20.12.2013 - 18:38
fonte
3

Esiste un insieme generale di passaggi che è possibile utilizzare per migliorare la leggibilità degli algoritmi funzionali:

  • Metti nomi sui risultati intermedi, invece di provare a stipare tutto su una riga.
  • Utilizza funzioni con nome invece di lambda, specialmente nelle lingue con sintassi lambda dettagliata. È molto più facile leggere qualcosa come evaluateTerm di una lunga espressione lambda. Solo perché puoi usare un lambda non significa necessariamente che dovrebbe .
  • Se una delle funzioni ora chiamate assomiglia a qualcosa che verrebbe visualizzata abbastanza frequentemente, è probabile che sia già nella libreria standard. Guardati intorno. Il mio pitone è un po 'arrugginito, ma sembra che tu abbia reinventato sostanzialmente enumerate o zipWith .
  • Spesso, vedere le funzioni e i risultati intermedi nominati rende più facile ragionare su cosa sta succedendo e semplificarlo, a quel punto potrebbe essere utile inserire nuovamente un lambda o combinare alcune linee di nuovo insieme.
  • Se un imperativo per il ciclo sembra più leggibile, è probabile che una comprensione preliminare funzioni bene.
risposta data 21.12.2013 - 16:37
fonte