web-dev-qa-db-ja.com

この問題の純粋に機能的なソリューションは、命令型と同じくらいクリーンであることができますか?

私はPythonで次のように練習します:

  • 多項式は、累乗がインデックスによって決定されるような係数のタプルとして与えられます。例:(9,7,5)は、9 + 7 * x + 5 * x ^ 2を意味します

  • 与えられたxの値を計算する関数を書く

最近関数型プログラミングに夢中になっているので、私は書きました

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)

私は判読できないと思うので、私は書きました

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]

少なくとも読めないので、私は書きました

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

これはあまり効率的ではないかもしれません(編集:私は間違っていました!)指数の代わりに多くの乗算を使用するため、原則として、ここでは測定については気にしません(編集:私の愚かさ!測定は私の誤解を指摘したでしょう!)まだ、反復的なソリューションほど(おそらく)読み取り可能ではありません。

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

命令型と同じくらい読みやすく、効率が近い純粋に機能的なソリューションはありますか?

確かに、表現の変更は役立ちますが、これは演習によって与えられました。

Pythonだけでなく、HaskellまたはLISPにすることもできます。

10
user1358

@delnanが指摘しているように、Hornerの方法はおそらくより計算効率が良いですが、指数解法では、これをPythonでかなり読みやすくしています:

def eval_poly(poly, x):
    return sum( [a * x**i for i,a in enumerate(poly)] )
13
aelfric5578

多くの関数型言語にはmapiの実装があり、マップを介してインデックスを作成できます。それを合計と組み合わせると、F#に次のようになります。

let compute coefficients x = 
    coefficients 
        |> Seq.mapi (fun i c -> c * Math.Pow(x, (float)i))
        |> Seq.sum
7
Steven Evers

justに(固定された)タプルがある場合、なぜこれを行わないのか(Haskellで):

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

代わりに係数のリストがある場合は、以下を使用できます。

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

またはあなたが持っていたように削減して:

evalPolyList' coefs x = foldl' (\sum (c, p) -> sum + c*x^p) 0 $ Zip coefs [0..]
4
paul

定義した問題のスコープとコードの関係がわからないので、(記述した命令コードに基づいて)問題のスコープを無視してコードの機能のバージョンを提供します。

かなり読みやすいhaskell(このアプローチは、リストの構造化があり、純粋で読みやすい、あらゆるFP言語に簡単に変換できます):

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

そのようなhaskellの素朴なシンプルなアプローチは、FPに慣れていない人々へのより簡潔なアプローチよりもクリーンな場合があります。

まだ完全に純粋な、より明確な命令的アプローチは次のとおりです。

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)

2番目のアプローチのボーナスは、STモナドにあり、非常にうまく機能します。

確かですが、Haskellerが実際に実装する可能性が最も高いのは、上記の別の回答で述べたzipwithです。 zipWithは非常に典型的なアプローチであり、Pythonは、関数とマップ可能なインデクサーを組み合わせるというジッピングアプローチを模倣できると思います。

4
Jimmy Hoffa

関数アルゴリズムの可読性を向上させるために使用できる一般的な一連のステップがあります。

  • 1行にすべてを詰め込むのではなく、中間結果に名前を付けます。
  • 特に詳細なラムダ構文を使用する言語では、ラムダの代わりに名前付き関数を使用します。長いラムダ式よりもevaluateTermのようなものを読む方がはるかに簡単です。 canラムダを使用するからといって、必ずしもshouldであるとは限りません。
  • 現在名前が付けられている関数の1つがかなり頻繁に出現するもののように見える場合、それはおそらく標準ライブラリにすでに存在しています。見回す。私のpythonは少し錆びていますが、基本的にはenumerateまたはzipWithを再発明したようです。
  • 多くの場合、名前が付けられた関数と中間結果を見ると、何が起こっているのかを推論してそれを単純化することが簡単になります。
  • 命令型forループの方が読みやすいように見える場合は、理解のための可能性が高いでしょう。
3
Karl Bielefeldt