web-dev-qa-db-ja.com

Javaで算術式を解析し、それからツリーを構築する

算術式を指定してカスタムツリーを作成するには、いくつかのヘルプが必要でした。たとえば、次の算術式を入力するとします。

(5+2)*7

結果ツリーは次のようになります。

    *
   / \
  +   7
 / \
5   2

PlusOp、LeafIntなど、さまざまなタイプのノードを表すカスタムクラスがいくつかあります。式を評価する必要はありません。ツリーを作成するだけなので、後で他の機能を実行できます。さらに、負の演算子「-」は子を1つしか持つことができません。「5-2」を表すには、5 +(-2)として入力する必要があります。

各タイプの演算子が正しいnoを持っていることを確認するには、式の検証が必要になります。引数/子の場合、各開き括弧には閉じ括弧が付きます。

また、入力ストリングをトークンのスタックに変換するコードが既に作成されていることを友人が既に言及している必要があります(これが役立つ場合)。

助けていただければ幸いです。ありがとう:)

(文法を書いてantlr/JavaCCなどを使用して構文解析ツリーを作成できることを読みましたが、これらのツールや文法の記述に慣れていないので、それがあなたのソリューションであるなら、私は感謝します役立つチュートリアルやリンクを提供できます。)

38
ChocolateBear

"ANTLRの5分間の紹介" には算術文法の例が含まれています。特にantlrはオープンソース(BSDライセンス)であるため、チェックアウトする価値があります。

9
Cameron Skinner

これはある種の宿題であり、あなたは自分でそれをしたいと思っています。

私はこれを一度しました、あなたはスタックが必要です

したがって、この例で行うことは次のとおりです。

何をすべきかを解析しますか?スタックは
のように見えます(スタックにプッシュします(
 5プッシュ5(、5 
 +プッシュ+(、5、+ 
 2プッシュ2(、5 、+、2 
)まで評価(7 
 *プッシュ* 7、* 
 7プッシュ7 +7、*、7 
 eof評価はトップ49 

「5」や「+」などの記号は、単に文字列または単純なオブジェクトとして保存できます。または、値を設定せずに+を+()オブジェクトとして保存し、評価時に設定できます。

これには優先順位も必要だと思うので、その仕組みを説明します。

5 + 2 * 7の場合

5をプッシュする必要がある)、ファイルの終わり、または優先順位が低いか等しい演算子に遭遇すると、スタックの計算を開始します(またはファイルの先頭)。

スタックには5 + 2 * 7が含まれるようになったため、評価するときは最初に2 * 7をポップし、結果の*(2,7)ノードをスタックにプッシュしてから、もう一度スタックの上位3つを評価します( 5 + * node)ので、ツリーが正しく表示されます。

それが他の方法で注文された場合:5 * 2 + 7、 "5 * 2"でスタックに到達するまでプッシュし、それから低い優先順位+に到達します。 5 * 2を* nodeに評価してプッシュし、さらに+と3をプッシュして* node + 7を取得し、その時点で評価します。

これは、+ /-を押すと1を、*または/を押すと2を、「^」を表す3を格納する「現在の優先順位が最も高い」変数があることを意味します。これにより、変数をテストして、次の演算子の優先順位が<=現在の優先順位であるかどうかを確認できます。

「)」が優先度4と見なされる場合、一致する「(」を削除することを除き、他の演算子として扱うことができますが、優先度が低い場合は削除されません。

49
Bill K

Bill K.の回答に返信したかったのですが、コメントを追加する評判がありません(実際にこの回答が属するのはそのためです)。これは、Bill K.の答えに対する補遺と考えることができます。彼は少し不完全だったからです。不足している考慮事項は operator associativity ;です。すなわち、次のような式を解析する方法:

49 / 7 / 7

分割が左結合か右結合かに応じて、答えは次のとおりです。

49 / (7 / 7) => 49 / 1 => 49

または

(49 / 7) / 7 => 7 / 7 => 1

通常、除算と減算は左結合(つまり、上記のケース2)とみなされますが、べき乗は右結合と見なされます。したがって、一連の演算子を同じ優先順位で実行した場合、それらが左結合の場合は順番に、右結合の場合は逆順に解析する必要があります。これは単にスタックにプッシュするかポップするかを決定するので、与えられたアルゴリズムを過度に複雑にすることはなく、連続する演算子が同じ優先順位である場合のケースを追加します(つまり、左の連想であればスタックを評価し、右の連想ならばスタックにプッシュします) 。

13
Ray Weidner

あなたのためのいくつかのオプション:

  1. 既存の式パーサーを再利用します。構文とセマンティクスに柔軟性がある場合、それは機能します。私がお勧めするのは、Java(最初はJSPおよびJSFファイルで使用するために)に組み込まれている統一式言語です。

  2. 独自のパーサーをゼロから作成します。演算子の優先順位などを考慮するパーサーを記述するための明確な方法があります。それがどのように行われるかを正確に記述することは、この答えの範囲外です。この方法を使用する場合は、コンパイラー設計に関する優れた本を見つけてください。言語解析理論については、最初の数章で説明します。通常、式の解析は例の1つです。

  3. JavaCCまたはANTLRを使用して、レクサーとパーサーを生成します。私はJavaCCを好みますが、それぞれが好みです。 「javaccサンプル」または「antlrサンプル」をGoogleで検索するだけです。たくさんあります。

2〜3の間では、新しい技術を習得する必要がある場合でも、3を強くお勧めします。パーサージェネレーターが作成されたのには理由があります。

また、(解析例外で失敗するだけでなく)不正な形式の入力を処理できるパーサーを作成することは、有効な入力のみを受け入れるパーサーを記述するよりもはるかに複雑であることに注意してください。基本的に、さまざまな一般的な構文エラーを説明する文法を作成する必要があります。

更新:JavaCCを使用して作成した式言語パーサーの例を次に示します。構文は、統一された式言語に大まかに基づいています。それはあなたが何に直面しているのかについてかなり良い考えを与えるはずです。

org.Eclipse.sapphire/plugins/org.Eclipse.sapphire.modeling/src/org/Eclipse/sapphire/modeling/el/parser/internal/ExpressionLanguageParser.jjのコンテンツ

与えられた式(5 + 2)* 7は中置として取ることができます

Infix  :     (5+2)*7
Prefix :     *+527

上記から、ツリーの事前順序と順序の変更がわかります...これからツリーを簡単に構築できます。おかげで、

1
knils