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


10

私は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にすることもできます。


7
私の経験では、可変forループを使用しないという意味で純粋に機能的なコード(たとえば、ループを使用しないことも意味します)は、Pythonで目指すのが悪い目標です。変数を慎重に再バインドし、オブジェクトを変更しないことで、ほとんどすべての利点が得られ、コードが無限に読みやすくなります。数値オブジェクトは不変であり、2つのローカル名のみを再バインドするため、「命令型」ソリューションは、「厳密に純粋な」Pythonコードよりも関数型プログラミングの利点をよりよく実現します。

2
ところで、乗算法はホーナーの方法であり、累乗は非常に同じ乗算を必要とし、さらにいくつかを必要とするため、各ステップでの累乗よりも効率的です。

1
Pythonはlambda、匿名構文関数がより軽い言語と比較して、を使い始めると悪名高いものになります。その一部はおそらく「汚れた」外観に貢献しています。
KChaloux 2013

@KChalouxそれはまさに私が言っていたものです。関数型プログラミングのサポートは、Pythonの多くの点で後付けのようなものであり、一種のショーです。それでも、最初のバージョンでさえ恐ろしく読めないので、何が起こっているのか理解できません。
Evicatos 2013

私はあなたのコードに本当に混乱していますが、問題スコープには非常に明確な数学の方程式がありますが、その数学の方程式をそのまま使用しませんか?それは、任意の言語を与えられた関数にかなり簡単に変換されます...質問が単一の方程式を評価してその方程式を与える関数を求めているとき、何をマップまたは削減または反復したいかわからない-それは求めませんイテレーションはすべて...
ジミー・ホファ

回答:


13

@delnanが指摘しているように、Hornerの方法はおそらくより計算効率が良いですが、私はこれをPythonで非常に読みやすい指数関数の解法と呼びます。

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

17
角括弧を削除して、変数にもっと説明的な名前を与え、それも良いでしょう: sum(coeff * X**power for power, coeff in enumerate(poly))
Izkata

1
他の投稿された回答が非常に複雑であることは、一種の悲しみです。言語を有利に使用してください!
イズカタ2013

理解は、関数型プログラミングにfor-loopで「密輸」されているようなものです
user1358

7
@ user1358いいえ、それはmapとの合成のための構文糖ですfilter。特定の形状のforループと考えることもできますが、その形状のループは、前述のfuncitonalコンビネーターと同等です。

7

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

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

2
また、機能しない場合でも、機能を理解していれば、自分で作成するのはmap簡単です。
KChaloux 2013

4

あなたのコードがあなたが定義した問題のスコープとどのように関連するのか理解していないので、あなたが書いた命令のコードに基づいて、あなたのコードが問題のスコープを無視して私のバージョンを提供します。

かなり読みやすい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

(固定)タプルしかない場合は、(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..]

1
宿題ではありません!言うまでもなく、私はすでに3つのソリューションを実行しました。
user1358 2013

Pythonの半分の時間(この場合を含む)では、「タプル」は「不変リスト」を意味するため、任意の長さになります。

明らかに任意の長さ
user1358

1
Pythonのためではなく、多項式が任意の長さを意味するため、固定サイズは大きな課題ではない
user1358

1
@delnan面白いですね。私は常にtuple、追加または削除できない固定サイズの値のセットを意味します。値のセットはそれぞれ異なる可能性があります。異種入力を受け入れるリストを持つ動的言語がなぜそれらを必要とするのか、私は本当に理解していませんでした。
KChaloux 2013

3

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

  • 1行にすべてを詰め込むのではなく、中間結果に名前を付けます。
  • 特に詳細なラムダ構文を使用する言語では、ラムダの代わりに名前付き関数を使用します。evaluateTerm長いラムダ式のようなものを読む方がはるかに簡単です。ラムダ使用できるからといって、必ずしもそうである必要はありませ
  • 現在名前が付けられている関数の1つがかなり頻繁に出現するもののように見える場合、その関数はすでに標準ライブラリにある可能性があります。見回す。私のpythonは少し錆びですが、基本的には再発明のように見えますenumeratezipWith
  • 多くの場合、名前が付けられた関数と中間結果を見ると、何が起こっているのかを推論してそれを単純化することが簡単になります。
  • 命令型forループの方が読みやすいように見える場合は、理解のための可能性が高いでしょう。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.