中置数式を後置形式に解析しています。 [4.5, 3, 0.25, +, -]
のようなリストに保存して、解析後に処理できるようにしたいと考えています。もう一度文字列に格納することはできましたが、2回解析する必要がありました。問題は同じリストに演算子とオペランドを保存する方法がわからないことです。私は共通の親の2つの子供クラスを考えましたが、それらはまったく異なります。
これを達成するための最良の方法は何ですか?
問題は、演算子とオペランドを同じリストに格納する方法がわからないことです
コンポジットデザインパターン を考えます。
この記事では、数式の例を示します。
これにより、独自のツリーを構築できます。すべての要素をリストに格納することもできますが、doThis()
はツリーを使用してトラバースします。
少し紛らわしいのは、すべての操作がメソッドとして表示されることです。代わりに、複合ノードに操作を保存できます。 doThis()
の名前をcalculate()
に変更すると、root.calculate()
を呼び出して式全体を計算できます。これはどのノードでも実行できます。インターフェイスは次のようになります。
_<<interface>>
Component
---------
+calculate()
+getPrefixString()
+getInfixString()
+getPostfixString()
+addElement(Component component)
_
これらのいずれも、各要素にアクセスするときに要素を再解析する必要がないというわけではありません。これを回避する方法は次のとおりです。
これで、ツリーを構築する前に、すべての解析を前もって行うことができます。 doThis()
、またはcalculate()
は、名前を変更すると、どの演算子を多態的に使用するかを認識します。
これらはすべて同じタイプComponent
であるため、リスト、配列などに自由に格納できます。しかし、それらがリンクする方法は、まだツリーです。
中置記法に関する構造上の懸念を表明しました。 ビルダーパターン を検討してください。コンポジットの作成は得意です。私が提供したリンクは、まさにそのような場合のリファクタリングを通してあなたを連れて行きます。中置、前置、後置を処理するのに十分なパワーがあると思います。
数式をC++リストに格納する方法
考慮してください 木
ここでの基本的なことは、接頭表記、中置表記、および後置表記は、数式を提示する方法にすぎないということです。保存するとき、それらがどのように提示されたかを気にする必要はありません。ツリーについての気の利いたことは、これらの表記の同等の式を同一のツリーに変換できることです。また、ツリーをこれらの表記のいずれかに変換できます。
これは、毎回同じ方法でトラバースされた同じツリーです。違いは、ノードから値を取得するときです。違いはドットです。ドットに触れると、文字をピックアップ(またはダウン)します。ドットを回転させると、3つの異なる表記法で文字が得られます。
このために機能するc ++データ構造があります。それは btrees と呼ばれます
私はCandiedOrangeの答えがより良いアプローチだと思いますが、ツリーは式の操作を実行するのにはるかに便利なので、それを保存して後で評価するだけの場合は、スタックベースの評価とともにPostfixリストが適切で簡単です。
オペランドと演算子の間に共通点がないと言うとき、それは完全に真実ではありません。式を実行するには、それぞれに順番にアクセスし、操作するためのスタックを提供する必要があります。オペランドはスタックに値をプッシュし、演算子は値のペアをポップして計算を実行し、結果をプッシュします。どちらも同じインターフェースを持つことができます:それらはスタック操作です。
(私はあなたが本物C++ 11 または C++ 14 でコーディングしたいと思います- 標準コンテナ & tilities ライブラリ;以前のバージョンのC++を使用せず、必ずrecentコンパイラを使用してください: 2016年6月、 [〜#〜] gcc [〜#〜] version 5 または 6 または Clang/LLVMを使用 バージョン .7または3.8 、どちらも フリーソフトウェア )
解析した式の 抽象構文ツリー (AST)を表現したいと思うでしょう。 解析 の詳細をご覧ください。 ドラゴンブック の最初の章を読みたくなるかもしれません。または、単純な 再帰下降パーサー をコーディングします。または ANTLR または GNU bison のようないくつかのパーサージェネレーター(しばしば誤って compiler-compilers と呼ばれます)を使用します-またはいくつか yacc -)。ところであなたの問題は、bison
のドキュメント( infix calc )にある例の1つに似ています。
AST= C++listとして整理するのではなく、treeとして整理します。たとえば、共通の(抽象)スーパークラスclass expr
があり、整数のリテラル(class leaf_expr: public class expr
など)のclass int_expr: public leaf_expr
、変数(x
など)の12
などの具体的な最終サブクラスを持つリーフclass var_expr: public leaf_expr
の抽象サブクラスがある場合は、スーパークラスclass binary_expr : class_expr
があります。バイナリノードの場合 スマートポインター フィールド(おそらくstd::shared_ptr<expr>
...)を含み、合計サブノードの具体的な最終サブクラスclass sum_expr : public class binary_expr
など(たとえば、x+12
)。
ASTのPostfix変換を取得する必要がある場合は、Postfix式のanother表現を、おそらくPostfixコンポーネントへのスマートポインタのリストとして定義し、別のクラスを定義しますpostfixコンポーネントの階層:さまざまなサブクラスclass postfix
(class int_postfix
などのPostfix整数リテラルオペランドの場合)、12
(x
などのPostfix変数オペランドの場合)、class var_postfix
(class plus_postfix
などのPostfix演算子の場合)、いくつかの中間抽象を使用した抽象スーパークラス+
オペランドのclass operand_postfix : public postfix
のようなスーパークラスとclass operator_postfix : public postfix
、そしておそらくclass unary_operator_postfix : public operator_postfix
とclass binary_operator_postfix : public operator_postfix
など...)...そしてこれらのスマートポインタのリストを持っています(例:std::list<std::unique_ptr<postfix>>
...)。次に、ASTからpostfixリストへの変換を簡単にコーディングできます。
std::list<std::unique_ptr<postfix>> transform_ast_to_postfix
(std::shared_ptr<expr>);
問題は、演算子とオペランドを同じリストに格納する方法がわからないことです
一般的なスーパークラスへのスマートポインターのリストが表示されます。 std::list<std::unique_ptr<postfix>>
。 タグ付きユニオン を含むオブジェクトのリストを持つことを検討することもできます(ただし、あなたの場合はそうではありません)。 FWIW C++ 17 はあなたに std :: any
(AST=をメモリ内で表すexplicitlyを回避し、パーサーのアクションでpostfix
要素へのスマートポインターのリストを直接作成することができますが、 ASTの構築がより簡単でより一般的である)と信じている
pointers と smart pointers をよく理解していることがessential。生のポインターの代わりにスマートポインター(expr*
など)を使用すると、よりクリーンなコードを記述して メモリリーク を回避できます。 [〜#〜] raii [〜#〜] と、おそらく(C++ 11では) 5つのルール を理解する必要があります。 this の質問を参照してください。
Stroustrupの プログラミング-原則とC++を使用した実践 を読んでください。