計算
計算
こんにちは。
ファイルから読み込んで、命令を実行するというものを作りたいですね。
計算式をどうやって再現すればいいのか解らなくて困っているんです。
1+1とかなら簡単に解りますが、1+1*2*(1+4)のような
括弧や掛け算の優先順位とかがあると複雑になります。
・1行単位で扱っている。
・命令 > 変数名 > コメントの順に識別している。
・区切りは 半角空白とコンマだけ
・変数名の次は、数値又は文字列となっている。
・今の所は変数に代入しかできない。
・同じ変数名で型が違っても代入が可能。
・命令の引数は、変数かそのまま利用するのか、エラーかしか判別しない。
・コメントは区切りのない文字列及び数値の事です。
・命令や変数名の前にコメントを書くと変数扱いされてしまう。(意図しない結果になる)
・左上から右下へ順番通りに進む。
・変数名は日本語、英文字どちらでも良い(数値のみでも可能 しかし作動は不明)
現状はこんな感じです。
これ自体のソースは短いんですけど、
多重継承していて大量のファイルになってしまうんですよ。
簡単に考えるなら命令を作くれば良いだけですけど…。
計算式の方が見栄えが良いし分かり易いですよね。
考え方を教えて下さると嬉しいです。
ファイルから読み込んで、命令を実行するというものを作りたいですね。
計算式をどうやって再現すればいいのか解らなくて困っているんです。
1+1とかなら簡単に解りますが、1+1*2*(1+4)のような
括弧や掛け算の優先順位とかがあると複雑になります。
・1行単位で扱っている。
・命令 > 変数名 > コメントの順に識別している。
・区切りは 半角空白とコンマだけ
・変数名の次は、数値又は文字列となっている。
・今の所は変数に代入しかできない。
・同じ変数名で型が違っても代入が可能。
・命令の引数は、変数かそのまま利用するのか、エラーかしか判別しない。
・コメントは区切りのない文字列及び数値の事です。
・命令や変数名の前にコメントを書くと変数扱いされてしまう。(意図しない結果になる)
・左上から右下へ順番通りに進む。
・変数名は日本語、英文字どちらでも良い(数値のみでも可能 しかし作動は不明)
現状はこんな感じです。
これ自体のソースは短いんですけど、
多重継承していて大量のファイルになってしまうんですよ。
簡単に考えるなら命令を作くれば良いだけですけど…。
計算式の方が見栄えが良いし分かり易いですよね。
考え方を教えて下さると嬉しいです。
Re:計算
四則演算の優先順位を満たし、カッコによる優先順位の変更に対応するには、
「数式の木(演算木ともいう)」について考える必要がありそうです。
例題として挙げられた1+1*2*(1+4)という式から数式の木を構築すると
この木を「帰りがけ順」で走査すると、
コンピュータで扱いやすくなります(「スタック」というデータ構造とマッチしている)。
また、逆ポーランド記法は数式を日本語読みすることと相性がよいです。
今回の例は
1に「1に2を掛けたものに、1に4を足したものを掛けたもの」を足す
と読めます。
任意の数式から、その数式に対応する「数式の木」を構築できれば、
後はしめたものです。
ただ、その「数式の木」を構築することがなかなか難しそうです。
いくつかのキーワードをちりばめました。
これらをもとに調べてみてはいかがでしょうか。
行ないたいこととずれている、もしくはすでにご存じの内容でしたら、
きれいさっぱり読み捨ててください。
「数式の木(演算木ともいう)」について考える必要がありそうです。
例題として挙げられた1+1*2*(1+4)という式から数式の木を構築すると
+ / \ 1 * / \ * + / \ / \ 1 2 1 4という結果を得ます。
この木を「帰りがけ順」で走査すると、
1 1 2 * 1 4 + * +となります。この書き方を「逆ポーランド記法」と呼び、
コンピュータで扱いやすくなります(「スタック」というデータ構造とマッチしている)。
また、逆ポーランド記法は数式を日本語読みすることと相性がよいです。
今回の例は
1に「1に2を掛けたものに、1に4を足したものを掛けたもの」を足す
と読めます。
任意の数式から、その数式に対応する「数式の木」を構築できれば、
後はしめたものです。
ただ、その「数式の木」を構築することがなかなか難しそうです。
いくつかのキーワードをちりばめました。
これらをもとに調べてみてはいかがでしょうか。
行ないたいこととずれている、もしくはすでにご存じの内容でしたら、
きれいさっぱり読み捨ててください。
Re:計算
>符号と数字の識別は何処でやっているのか解らないのですが
is_opがオペレーションと数値を判別しています。
isdigit()で数値であることを認識したら node構造体の is_opを偽にしています。
それ以外で且つ ')'でなければ is_opを真にしています。
>改善したら早くなるとか、別の方が良いとかありましたら
本題からは少々外れてしまいますが、幾つか聞かせて下さい。
・ クラス Train/TrainExなどは STLの list/stackに置き換えられると思うのですが、使わない理由は何かあるのでしょうか。
・ これは課題とか学習目的のものでしょうか。それともゲームとかを製作していて、この手のスクリプトを実装しようとしているのでしょうか。
is_opがオペレーションと数値を判別しています。
isdigit()で数値であることを認識したら node構造体の is_opを偽にしています。
それ以外で且つ ')'でなければ is_opを真にしています。
>改善したら早くなるとか、別の方が良いとかありましたら
本題からは少々外れてしまいますが、幾つか聞かせて下さい。
・ クラス Train/TrainExなどは STLの list/stackに置き換えられると思うのですが、使わない理由は何かあるのでしょうか。
・ これは課題とか学習目的のものでしょうか。それともゲームとかを製作していて、この手のスクリプトを実装しようとしているのでしょうか。
Re:計算
Justyさん
考えることは誰でも一緒ですね。
符号か数値かのフラグだったんですね。
木の右、左の類かと思っていました。
リストを作ったので、苦労した時間を大切にしたいので利用しているだけです。
STLを使わない理由は特にないですが、
無料のVC++2005にはSTLが無かったような記憶があるのでその名残です。
知っていれば応用の幅が広がるという面では学習目的です。
ゲームを作りたいと言うのが本音です。
単にスクリプトの機能をつけるのであれば、そう言ったものを利用すれば良いだけのことです。
即戦力にはなりますが、己の力とはなりません。
知識がないと出来なかったり、想像が付かないんですよ。
これをどうやって再現しているんだろうかとか
気になってたけど、失敗して出来ない。
それでも諦められないと言う執着心があって、
目標となっているわけです。
ゲーム制作はその過程も楽しまなければ良いものは出来ないと思っています。
知識を増やすというのは私にとって喜びなのです。
出来なかったことが出来た時も凄く嬉しいのです。
考えることは誰でも一緒ですね。
符号か数値かのフラグだったんですね。
木の右、左の類かと思っていました。
リストを作ったので、苦労した時間を大切にしたいので利用しているだけです。
STLを使わない理由は特にないですが、
無料のVC++2005にはSTLが無かったような記憶があるのでその名残です。
知っていれば応用の幅が広がるという面では学習目的です。
ゲームを作りたいと言うのが本音です。
単にスクリプトの機能をつけるのであれば、そう言ったものを利用すれば良いだけのことです。
即戦力にはなりますが、己の力とはなりません。
知識がないと出来なかったり、想像が付かないんですよ。
これをどうやって再現しているんだろうかとか
気になってたけど、失敗して出来ない。
それでも諦められないと言う執着心があって、
目標となっているわけです。
ゲーム制作はその過程も楽しまなければ良いものは出来ないと思っています。
知識を増やすというのは私にとって喜びなのです。
出来なかったことが出来た時も凄く嬉しいのです。
Re:計算
@meiginさん
>木の右、左の類かと思っていました
私のサンプルでは明示的に木の概念は出てこないので、右も左もなかったりします。
>リストを作ったので、苦労した時間を大切にしたいので利用しているだけです
なるほど、わかりました。
>無料のVC++2005にはSTLが無かったような記憶があるのでその名残です。
たしかあったはずでず。
無いのは Windows専用のライブラリである MFC/ATLとかです。
さすがに C++を謳ったコンパイラで STLがないってのは・・・。
>知っていれば応用の幅が広がるという面では学習目的です。
なるほど。だとしたら出来合いのものは使わない方がいいですね。
>それでも諦められないと言う執着心があって、目標となっているわけです
これは重要であり、素晴らしいことだと思います。
>出来なかったことが出来た時も凄く嬉しいのです
とてもよくわかります。
>間違っていたら教えて下さい。
コンパイルが通らない(SetLeftNest()/SetRightNest()内に変数 nがない)というのはありますが、それを除いてもいろいろと挙動が変です。
ツリーの左と右に分けようとしているのはわかるのですが、SetRightNest()などで mIndex->mpRightとかに既に別の要素が入っているのに newして上書きしたりしているので、前にセットした情報が消えてしまったりしています。
このソースから手を入れるのはちょっと時間がかかりそうだったので、先のサンプルと meiginさんが添付されたものを融合した物を作ってみました。
逆ポーランド化されていく仮定が順次表示されるようになっています。
>木の右、左の類かと思っていました
私のサンプルでは明示的に木の概念は出てこないので、右も左もなかったりします。
>リストを作ったので、苦労した時間を大切にしたいので利用しているだけです
なるほど、わかりました。
>無料のVC++2005にはSTLが無かったような記憶があるのでその名残です。
たしかあったはずでず。
無いのは Windows専用のライブラリである MFC/ATLとかです。
さすがに C++を謳ったコンパイラで STLがないってのは・・・。
>知っていれば応用の幅が広がるという面では学習目的です。
なるほど。だとしたら出来合いのものは使わない方がいいですね。
>それでも諦められないと言う執着心があって、目標となっているわけです
これは重要であり、素晴らしいことだと思います。
>出来なかったことが出来た時も凄く嬉しいのです
とてもよくわかります。
>間違っていたら教えて下さい。
コンパイルが通らない(SetLeftNest()/SetRightNest()内に変数 nがない)というのはありますが、それを除いてもいろいろと挙動が変です。
ツリーの左と右に分けようとしているのはわかるのですが、SetRightNest()などで mIndex->mpRightとかに既に別の要素が入っているのに newして上書きしたりしているので、前にセットした情報が消えてしまったりしています。
このソースから手を入れるのはちょっと時間がかかりそうだったので、先のサンプルと meiginさんが添付されたものを融合した物を作ってみました。
逆ポーランド化されていく仮定が順次表示されるようになっています。
Re:計算
>-3とかの数値がこのままでは扱えません
なんかプログラマとして負けた気分になってきたので、なんとなく完全対応してみました。
今回は中身をがらっと変えて、BNF式から書き起こしてみました。
"|"はまたは、*はその前が0回以上繰り返す、という意味です。
で、この式に当てはめてそのままプログラミングしたのがこの添付のソースです。
今回はループではなく再帰で、且つ計算可能ならその場で計算していきます。
ClTest_ki::parse_op()が計算の実行テンプレート関数で、実行内容を関数オブジェクトで指定します。
この延長線上で考えていけば、比較・評価もやりやすいかもしれません。
例えば、
なんかプログラマとして負けた気分になってきたので、なんとなく完全対応してみました。
今回は中身をがらっと変えて、BNF式から書き起こしてみました。
[color=#d0d0ff" face="monospace]factor ::= integer | '(' expression ')' | '+' factor | '-' factor
term2 ::= factor (('^' factor))*
term1 ::= term2 (('*' term2) | ('/' term2))*
expression ::= term1 (('+' term1) | ('-' term1))*[/color]
expressionが解析開始で、" "(空白)はそれに続く、という意味で、"|"はまたは、*はその前が0回以上繰り返す、という意味です。
で、この式に当てはめてそのままプログラミングしたのがこの添付のソースです。
今回はループではなく再帰で、且つ計算可能ならその場で計算していきます。
ClTest_ki::parse_op()が計算の実行テンプレート関数で、実行内容を関数オブジェクトで指定します。
この延長線上で考えていけば、比較・評価もやりやすいかもしれません。
例えば、
[color=#d0d0ff" face="monospace]expression2 ::= expression !(compare_op expression)
compare_op ::= "==" | "!=" | "<" | ">" | ">=" | "<";[/color]
な感じで。Re:計算
>試しに%を追加してみたら、エラー……
std::modulus<>の実装はほんとに "%"演算子を使ってるだけですからね・・・。
代わりに 自作の関数オブジェクトを作って double型で %演算子と同じ動きをするのを作れば、対応はできるはずです。
>parse_integerを改造すれば、変数に対応出来そうですね
はい、そうですね。
integerの中で対応してもいいですし、別関数にしてもいいですが、そのあたりを修正すれば変数にも対応できます。
>比較、!が追加出来ました
お見事です!
いくつか試しましたが、問題ないようです。
>失敗したものをほって前に進めないのでまともに動くように直そうと思っています
そうですね。
頑張って下さい。
# 自分のコードを改めて見直して。
・parse_factor()の変数 sign_negは使われていないので不要でした。
・power_functor()自体がテンプレートになっていますが、doubleしか扱わないので
double専用の関数にしてしまった方がいいかもしれません。
std::modulus<>の実装はほんとに "%"演算子を使ってるだけですからね・・・。
代わりに 自作の関数オブジェクトを作って double型で %演算子と同じ動きをするのを作れば、対応はできるはずです。
>parse_integerを改造すれば、変数に対応出来そうですね
はい、そうですね。
integerの中で対応してもいいですし、別関数にしてもいいですが、そのあたりを修正すれば変数にも対応できます。
>比較、!が追加出来ました
お見事です!
いくつか試しましたが、問題ないようです。
>失敗したものをほって前に進めないのでまともに動くように直そうと思っています
そうですね。
頑張って下さい。
# 自分のコードを改めて見直して。
・parse_factor()の変数 sign_negは使われていないので不要でした。
・power_functor()自体がテンプレートになっていますが、doubleしか扱わないので
double専用の関数にしてしまった方がいいかもしれません。
Re:計算
Justyさん
ありがとう御座います。
ダブル専用でも問題ないですね。
イントにすると大問題でわり算の結果が変わってしまいますね。
%は使い道が無いので非推薦にしておきます。
試行錯誤の末に出来ました。
図に書いたとおりにプログラムを組んだら全く上手く行かず。
その図が間違っていたみたいで、並び替えの末に完成しました。
なんでそれが正しく動くのか解らないんですよ。
まず足し算から初めて、掛け算を加え、()に対応。
又間違っていたら大変だなと思いつつ添付しておきます。
変なのが入っていますが、test.cppがメインです。
実行するとテキストファイルを読み込んで計算します。
今の状態だと並び替えた数値や演算子が表示されます。
()は木に入れないんですね。
上に登る三つ又になっています。
ファイル読み込みのクラスは継承を外したのでぐちゃぐちゃになってしまいました。
これは関係ないものですので無視して下さい。
ありがとう御座います。
ダブル専用でも問題ないですね。
イントにすると大問題でわり算の結果が変わってしまいますね。
%は使い道が無いので非推薦にしておきます。
試行錯誤の末に出来ました。
図に書いたとおりにプログラムを組んだら全く上手く行かず。
その図が間違っていたみたいで、並び替えの末に完成しました。
なんでそれが正しく動くのか解らないんですよ。
まず足し算から初めて、掛け算を加え、()に対応。
又間違っていたら大変だなと思いつつ添付しておきます。
変なのが入っていますが、test.cppがメインです。
実行するとテキストファイルを読み込んで計算します。
今の状態だと並び替えた数値や演算子が表示されます。
()は木に入れないんですね。
上に登る三つ又になっています。
ファイル読み込みのクラスは継承を外したのでぐちゃぐちゃになってしまいました。
これは関係ないものですので無視して下さい。
Re:計算
今回のプログラムでは影響はないとは思いますが、St.hの TrainExはいくつか問題を抱えているようです。
[color=#d0d0ff" face="monospace]#include <iostream>
#include <crtdbg.h>
#include "St.h"
namespace {
void disp_trainex_container(St::TrainEx<int> &c){
using namespace std;
cout << "te : ";
for(int n = 0; n < c.Size(); ++n)
cout << c[n] << " ";
cout << endl;
}
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF); // メモリリークのチェック
{
St::TrainEx<int> te1, te2;
for(int n=0; n<8; ++n)
te1.Push(n);
disp_trainex_container(te1); // 0~7が順に表示される
te1.Swep(2, 5);
disp_trainex_container(te1); //! 7が2つある!
te1.Pop(1);
disp_trainex_container(te1); //! 数値が変わった?
te2 = te1;
} //! 落ちる
return 0; //! リークする
}[/color]
//! の部分で挙動が想定(?)と違う挙動をするようです。Re:計算
Justyさん
毎度毎度、申し訳ないです。
TrainExは、たびたび意図しない結果を出すのでその都度修正を行っています。
エラーの原因は、デストラクタで解放しているのでそれで発生していました。
同じアドレスになって、コピーではなく全く同じものを指していました。
te1を変更するとte2の値も変化してしまいます。
=を設定しましたのでエラーは無くなったと思います。
入れ替えは元となったクラスにそう書いてあったので信用してそのままにしていました。
一度もその関数を使わなかったので解りませんでした。
中身だけを入れ替えるように直しました。
読み込み管理は色々と問題があるので作り直す予定です。
計算は繋がっていても出来るようにしました。
その他は必ず半角空白かコンマが必要です。
なので = は半角空白で間を空けて下さい。
三度目の正直になって欲しいです。
色々とご指摘、助かります。
毎度毎度、申し訳ないです。
TrainExは、たびたび意図しない結果を出すのでその都度修正を行っています。
エラーの原因は、デストラクタで解放しているのでそれで発生していました。
同じアドレスになって、コピーではなく全く同じものを指していました。
te1を変更するとte2の値も変化してしまいます。
=を設定しましたのでエラーは無くなったと思います。
入れ替えは元となったクラスにそう書いてあったので信用してそのままにしていました。
一度もその関数を使わなかったので解りませんでした。
中身だけを入れ替えるように直しました。
読み込み管理は色々と問題があるので作り直す予定です。
計算は繋がっていても出来るようにしました。
その他は必ず半角空白かコンマが必要です。
なので = は半角空白で間を空けて下さい。
三度目の正直になって欲しいです。
色々とご指摘、助かります。
Re:計算
@meiginさん
>TrainExは、たびたび意図しない結果を出すのでその都度修正
先に TrainExなどのコンテナの完成度をある程度上げておいた方がいいかもしれません。
そうしておけば、使っていて問題がおきたときに TrainEx側には問題ない、と確信できると思います。
>=を設定しましたので
代入オペレータを定義したのならついでに
・ 同じ変数同士の代入(a=a)への対応
・ *thisの 参照を戻す(a=b=cができるように)
・ コピーコンストラクタを定義
もしておくといいと思います。
結構 C++って奥が深く、面倒なんですよね・・・。
>計算は繋がっていても出来るようにしました
少し試してみました。かなり良い感じですね。
ちゃんと変数も認識していますし、あとは負の数への対応くらいでしょうか。
何にしても後少しです。頑張って下さい。
>TrainExは、たびたび意図しない結果を出すのでその都度修正
先に TrainExなどのコンテナの完成度をある程度上げておいた方がいいかもしれません。
そうしておけば、使っていて問題がおきたときに TrainEx側には問題ない、と確信できると思います。
>=を設定しましたので
代入オペレータを定義したのならついでに
・ 同じ変数同士の代入(a=a)への対応
・ *thisの 参照を戻す(a=b=cができるように)
・ コピーコンストラクタを定義
もしておくといいと思います。
結構 C++って奥が深く、面倒なんですよね・・・。
>計算は繋がっていても出来るようにしました
少し試してみました。かなり良い感じですね。
ちゃんと変数も認識していますし、あとは負の数への対応くらいでしょうか。
何にしても後少しです。頑張って下さい。