LISPでの再帰プログラミング-式ソルバー

数式ソルバーはそれ自体非常に興味深いトレーニングであり、特定の瞬間にこのトレーニングは別のタスクで非常に役立ちます-新しい数式を設計し、自動的にチェックします(エラー、座標リストによる値の誤計算)...





私はすぐに予約します-コードは'87のサンプルのLISP用に作成されたものであり、新鮮なCommon LISPはカテゴリを理解していませんが、残念です...しかし-LISPコードから同じステップを実際の問題C#またはDelphi LISPでは、より甘いパフォーマンスが私の心と目に与えられます)



コードの難しいタスクに対して、キーボードから数式を入力することには多くの利点があります。 それを実装する方法は、おそらく学校の鉄の椅子からプログラマーが考えた。 タスクは素晴らしく、不可能に思えますが、主なことは開始することです。 はじめに>>



1.数式はどのように書かれますか?



最初のステップの通常の記録は、不必要に複雑です。 ポーランド形式の録音-それを使用します。 また、数値の演算は、最も複雑な数式を指定できるリストの形式で記述されます。



2+3->(+ 2 3)

2-3->(- 2 3)

2*3->(* 2 3)

2/3->(/ 2 3)

2+3+5->(+ 2 3 5)

2+3*5->(+ 2 (* 3 5))

(2+3)*5->(* (+ 2 3) 5)









2.どのように機能しますか? (単純な数式)





LISPの再帰精神でソルバーを記述します。ヘッドのリストから最初の要素を取得し、ヘッドの値に従ってテールを処理します。 ソルバーは毎回、式(a)、前のアクションの結果(b)、記号©、およびこれが前もって予測される場合は未知のパラメーターの値(xx)を受け取ります。



(defun solver (abc xx)

((null a) b)

((equal (searchx a) NIL) (solver (presolver a NIL xx) T c xx))

((eq (atom a) T) a)

((equal c NIL) (solver (cdr (cdr a)) (car (cdr a)) (car a) xx))

((equal c ps) (solver (cdr a) (+ b (car a)) c xx))

((equal c ms) (solver (cdr a) (- b (car a)) c xx))

((equal c mu) (solver (cdr a) (* b (car a)) c xx))

((equal c di) (solver (cdr a) (/ b (car a)) c xx))

)









式で固有名#(+ x 5))で示される不明なパラメーターは、対応する関数値で置き換える必要があります。 そしてソルバーに。 それをプリソルバーと呼び、入力での式、結果(ソルバーから呼び出す場合-NIL)、および要素 'x'を置換するための不明の値を指定します(LISPはデータ型を区別せず、主を賞賛するため、ここで特にこれを強調します)



(defun presolver (ab xx)

((null a) (revers b))

((eq (car a) x) (presolver (cdr a) (cons xx b) xx))

((eq (equal (car a) x) NIL) (presolver (cdr a) (cons (car a) b) xx))

)









そして-未知の存在を確認する必要があります。 presolver-aの結果はデフォルトで逆方向になるため、未知の検索関数が生まれ、searchxになると同時に、逆リスト関数が逆になります。



(defun searchx (a)

((null a) T)

((equal (car a) x) NIL)

(searchx (cdr a))

)









(defun revers (ab)

((null a) b)

(revers (cdr a) (cons (car a) b))

)









実行チェック



(solver '(ps x 2 3) NIL NIL 5)







-ura-works。 しかし-ネストされたリストのないリスト、アトム要素のある単純な式でのみ。



3.どのように動作します-2? (複雑な数式)





単純なソルバーを興味深い方法で複雑なソルバーに変える方法については長い間考えることができますが、より合理的な方法で進みます-作業部分には触れませんが、solver2プロジェクト-複雑な数式のソルバーに取り組み始めます。



(defun solver2 (a xx)

((eq (iseasy a) T)

(solver a NIL NIL xx))

((eq (atom (car a)) T)

(solver (cons (car a) (solver2 (cdr a) xx)) NIL NIL xx))

((eq (iseasy (car a)) T)

(cons (solver (car a) NIL NIL xx) (solver2 (cdr a) xx)))

((eq (iseasy (car a)) NIL)

(cons (solver2 (car a) xx) (solver2 (cdr a) xx)))

)









各複雑な数式には、少なくとも1つのリストアイテムが含まれます。 しかし、新しい関数が少なくとも単純な計算で機能するように、互換性の問題から始めましょう。 これを行うには、入力式を単純にするためにチェックする必要があり、単純な場合はソルバーに送信しますが、難しい場合はもう少し考えてください。



単一の入力パラメーター、式自体を持つ関数iseasyは、リストが単純であるか完全に役立つかどうかを決定します。 Iseasyは非原子要素を認識し、NIL応答でドロップアウトします



(defun iseasy (a)

((null a) T)

((equal (atom (car a)) Nil) NIL)

(iseasy (cdr a))

)









さらに、すべてがシンプルであると同時に、エレガントに理解できない。 関数の正確性は明らかですが、アルゴリズムは明らかではありません。 彼はそのようなものです:

-最初に、solver2は単純化のために式全体をチェックします。 数式全体が単純な場合、ソルバーで解決できるため、解決する必要があります。

-次にチェックが行われます-「ヘッドが式 'a'のアトムではないのですか?」ヘッドがアトムである場合、それは脆弱です。次に、このアトムとテールからソルバーコンストラクターを送信します。

-最初の2つのチェックがパスしなかった場合、式の何かが間違いである(コードを書いてから2年後)、簡単にするために式の頭をチェックする必要があります(単純ではありませんが、アトムではありません!-リストを意味します) )、ソルバー2の機能を開始した構造を、ソルバー2を再検討した式の解決されたヘッドとテールから送信します。 たとえば、「画面上」...おそらくこれはある種のバグレポートでしょうか?

-最初の3つのチェックに合格しなかった場合(問題は、式が単純ではなく、この式の先頭にアトムがない(つまり、±/ * /:ではない)記号であり、アトムのリストでさえない場合、「画面に」と表示されます»ソルバー2の頭と尾を元に戻し、リビジョンソルバー2も使用した設計



起動...

(solver2 '(mu (ps x 2 3) (ms x 5)) 10)





マーベル



4.なぜ機能するのですか? (結論)





明らかに、上記のコードのsolver2は単純な式を読み取ることができます。 しかし、それを起動することにより、どのように複雑と見なされますか? 再帰プログラミングのビジョンは非常に欺くものです。



各正しい式は、操作の原子から始まります。 読者のLISPのアクションに従ってください:solver2は正しい複雑な数式の解決を開始し、この数式の先頭(符号)からソルバーに構造を送信します。 テールはソルバー2に来て、当然、ヘッドにオペレーションアトムを含みません。 したがって、ソルバー2をさらに簡素化するために当然送信されます...



彼の本質は何ですか、彼は何をしているのですか? 一般的に、これ以上簡単なものはありません。リストを取得して数値に置き換えます。最終的に、任意の長さの式は、10倍の入れ子であっても、先頭に記号があり、末尾が1つだけの単純なリストになります原子、およびすべての原子は数字になります。 美しい、そして絶対に理解できないコード、関数を書く最初のステップでは理解できない)...どのRPと愛のために。



All Articles