C++でネストした三項式を解析する:スタックを使ったパーサーの実装
任意の深さでネストされた三項式(三項演算子による条件式)を表す文字列が与えられたとき、その式を解析して最終的な評価結果を求めるのが本問題です。入力となる式は常に有効であり、構成要素は数字「0〜9」、「?」、「:」、「T」、「F」のみです(TとFはそれぞれTrue・Falseを表します)。この問題には以下のような性質があります。
- 与えられる文字列の長さは10000以下である。
- 各数値は必ず1桁である。
- 条件式は右から左に向かってグループ化される。
- 条件部分は必ずTまたはFであり、数値が条件になることはない。
- 式の評価結果は、必ず0〜9のいずれかの数字、T、またはFのいずれかになる。
動作例
たとえば入力が「F?1:T?4:5」の場合、出力は「4」となります。これは、まず最も右側の式「T?4:5」が解析されて4が返され、その結果として全体の式が「F?1:4」に書き換わり、条件Fは偽であるため最終的に4が出力されるからです。
アルゴリズムの考え方
この問題は、文字列を右から左へ走査しながらスタックを活用することで、シンプルかつ効率的に解くことができます。具体的な手順は以下の通りです。
- ret := 空文字列、n := 文字列sの長さとし、空のスタックstを用意する。
- i を n − 1 から 0 まで逆順にループする。
- x := s[i] とする。
- スタックが空でなく、かつスタックの先頭が「?」である場合は、次のように処理する。
- 先頭の「?」をポップして取り除く。
- first := スタックの先頭要素とし、続けて2つの要素をポップする(真の場合の値と「:」を除去)。
- second := スタックの先頭要素とし、それをポップする。
- x が「T」であれば first を、そうでなければ second をスタックにプッシュする。
- 上記以外の場合は、x をそのままスタックにプッシュする。
- スタックが空になるまで、先頭の文字を ret の末尾に加えながらポップを繰り返す。
- 最後に ret を反転して返す。
C++による実装例
理解を深めるために、以下の実装コードを見てみましょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
string parseTernary(string s) {
string ret = "";
int n = s.size();
stack <char> st;
for(int i = n - 1; i >= 0; i--){
char x = s[i];
if(!st.empty() && st.top() == '?'){
st.pop();
char first = st.top();
st.pop();
st.pop();
char second = st.top();
st.pop();
if(x == 'T'){
st.push(first);
}
else st.push(second);
}
else{
st.push(x);
}
}
while(!st.empty()){
ret += st.top();
st.pop();
}
reverse(ret.begin(), ret.end());
return ret;
}
};
main(){
Solution ob;
cout << (ob.parseTernary("F?1:T?4:5"));
}
入力
"F?1:T?4:5"
出力
4
-
C++で学ぶ式ツリー(Expression Tree)の基本と具体例
式ツリーとは何か式ツリー(Expression Tree)とは、二分木の一種であり、木の各ノードが「演算子」または「オペランド(被演算子)」のいずれかで構成される特殊なデータ構造です。数式を木構造として表現することで、コンパイラや電卓アプリなどが数式を効率的に解析・評価できるようになります。ノードの役割式ツリーにおける各ノードは、次のように役割が分かれています。葉ノード(リーフノード):オペランド(数値や変数)を表します。非葉ノード(内部ノード):演算子(+、-、*、/ など)を表します。つまり、計算の対象となる値は必ず葉に配置され、それらをどのように処理するかを示す演算子が親ノードとして上に
-
C++で式ツリー(Expression Tree)を評価する方法|再帰を使った実装例
本記事では、+、-、*、/ といった二項演算子から構成される式ツリー(Expression Tree)を評価し、その計算結果を返す問題について解説します。 式ツリーとは 式ツリーは二分木の一種であり、各ノードには演算子またはオペランド(被演算数)が格納されます。ノードの役割は次のように分けられます。 葉ノード:演算の対象となる値(オペランド)を保持します。 非葉ノード(内部ノード):実行すべき演算を表す二項演算子を保持します。 式ツリーを中順走査(in-order traversal)すると、元の中置記法の数式が復元できるのが特徴です。 例題で理解しよう 入力:次のような式ツリーが与えられ