数式(文字列)から必要な要素を取り出す方法

数式を数値部分、演算子、関数などに分けるには、どんな方法があるか考えてみました。

 数式の処理や、その値の計算をさせるアプリケーションを開発する場合は、文字列で表された式を、その要素(数値や演算子、関数、括弧やコンマなど)に分解したり、文字列で表されている数値を数値型(doubleやfloatなどの浮動小数点型あるいはint などの整数型)に変換したり、その逆変換が必要になってきます。またそもそも入力された式や数値が正しい形になっているのかというチェックも必要になります。
 ここではそれらを、どのような方法でおこなえばいいのか、またどんな問題点があるのかを考えてみたいと思います。

 まず式の分解について考えます。例えば、1 + 2*sin(π) /5 のような式なら1、2、*、sin(、π、)、/、5といった部分に分解する必要があります。分解の方法として二つの方法がすぐ思いつきます。ひとつは式を一文字づつ順に読んでいき判定するもの、もう一つは正規表現を使う方法です。どう考えても正規表現を使う方が簡単なので、これについて考えます。

 この方法で必要なのは、数値、演算子などに一致する文字列のパターンを作ることです。数値については、整数、小数点以下を持つ浮動小数点数、-12.345e-12 のような指数形式(ここでのeは指数関数ではなく、1e2 =1×102という10の冪乗の意味です、念のため)また数値の前についている負記号(演算子でなく、例えば、式の先頭に現れるもの)すべてにマッチするパターンが必要です。

 それらを満たすパターンとして考えたものが、

		String numPtn="\\-?[0-9]+(\\.[0-9]*)?([Ee]-?[0-9]*)?";//数値マッチ
です。このパターン作成の考え方を付図「数値と正規表現パターン」にまとめておきました。また演算子、関数(例としてsin、cos、tan、powを扱う場合)さらに括弧・閉じ括弧のパターンは、
		String opratorPtn ="[\\+\\*\\-/^]";//演算子マッチ
		String funcPtn = "(sin|cos|tan|pow)\\(";//関数
		String parethPtn ="(\\(|\\)|,)";//括弧とコンマ
となります。上の関数パターンは、最後に括弧がついてないとマッチしません。例えば1 + sinだとマッチしません。また上のようなマッチパターンを定義する場合、空白などを入れてしまうと意図しない動きをするので空白を入れないように注意が必要です。

 これらを使って式を分解するプログラムをjavaを使って書いたものが以下のようになります。引数のform_strに分解したい式を入れます。なおここに入れる式文字列には不要な空白が入らないよう前処理をした方がよいでしょう。ただし逆ポーランド記法の式などの場合は、数値間を分けるのに空白を使うので、そういった必要な空白は削除しないようにする配慮が必要です。

	public static ArrayList<String> Resolve_Formula2(String form_str){
		String numPtn="\\-?[0-9]+(\\.[0-9]*)?([Ee]-?[0-9]*)?";//数値マッチ
		String opratorPtn ="[\\+\\*\\-/^]";//演算子マッチ
		String funcPtn = "(sin|cos|tan|pow)\\(";//関数
		String parethPtn ="(\\(|\\)|,)";//括弧とコンマ

		Pattern src_Ptn = Pattern.compile(numPtn +"|"+opratorPtn + "|"+funcPtn +"|"+ parethPtn );
		Matcher the_match = src_Ptn.matcher(form_str);
		ArrayList<String> dvid_fct = new ArrayList();
		while(the_match.find()){
			dvid_fct.add(the_match.group());
		}

		return dvid_fct;
	}

 これを使って式分解を行った結果と上のメソッドを実行させたプログラムを付図「式分解の例」に載せました。

 さてこの例を見てもらうとわかりますが、このパターンによる分解にはいくつか問題があります。まず数値部分でEまたはeの後の数字がなかったり他の文字が入っていても問題なく取り出されてしまいます。また001などのように数値の頭に0が連続しているものも、そのまま取り出されます。出来ればこれらは、間違った入力として処理したいのですが、これらをうまく処理するパターンを作ることが出来ませんでした。したがって分解したあと、数値要素のエラーチェックを行う必要があります。例えば、check_trgをチェックする文字列とすると

		String chk_match ="(\\.|[0-9])";
		Pattern src_Ptn =Pattern.compile(chk_match);
		String endlett = check_trg.substring(check_trg.length()-1 );
		if(!src_Ptn.matcher(endlett).find()){
			//エラー処理
		}
という具合です。また入力した式が、1.2aa3 という形の場合、先のメソッドは1.2と3に分解しますが、これは数式として成り立っていません。ですから数値要素が連続してでたらエラーとする処置も必要です。このように式分解したあとの要素が式として正しい形になっているかの確認が必要となってきます。そこまで正規表現によるパターンマッチのみで行う事は困難です。

 またResolve_Formula2 で

	Pattern src_Ptn = Pattern.compile(numPtn +"|"+opratorPtn + "|"+funcPtn +"|"+ parethPtn );
のところでcompileメソッドの引数に、調べるパターンを与えていますが、複数パターンを与える場合、その順番に注意しなければならない事があります。
 一例として数値計算でなく数式処理をおこなう場合を考えてみます。この場合は式に文字係数(x2 + a2 -1 でのxやa2の事)が入ってきますので、これらを取り出すためパターンとして\w+(単語構成文字が1回以上出現するパターン)が必要になります。これを先のメソッド内で
	String albPtn ="\\w+";
	Pattern src_Ptn = Pattern.compile(numPtn +"|"+opratorPtn + "|" +albPtn+ "|"+funcPtn +"|"+ parethPtn );
のように関数パターンより先によぶとsinやcosが関数としてではなくアルファベットの並びとして取り出されてしまい、sin + ( (括弧は単独の括弧と判断される)となります。

一方

	Pattern src_Ptn = Pattern.compile(numPtn +"|"+opratorPtn + "|"+funcPtn +"|"+ parethPtn + "|" +albPtn);
この順番の場合は、sinやcos等は関数と判断されます。

 以上は最初から数式が与えられている場合ですが、電卓のキー入力のように一文字・数字毎に入力する場合は、一入力ごとにチェックするという方法が考えられます。もちろんこの場合も入力が終わった段階でパターンマッチするという方法も取れますが、入力毎に確かめれば、例えば001という入力をさせたくないなどという場合はやりやすいでしょう。

 筆者はチェック方法を付図の状態遷移図のようなものを書いて考えています。この付図の例は数値を入力する場合を表していて、数字や小数点など入力したあと次に来る事が許される物を矢印で結んでいます。これらについて詳しくはまた別の機会に説明したいと思います。

Additional Images




Comments

Add your comment

Book a free consultation
user-symbol

Stay in touch

Get Practical Tips For Business and Developers.
Learn about PieceX Community Needs for Source Code Projects.
Be the First to Know PieceX Newest Free Community Code Projects.