Rubyで逆ポーランド記法
勉強のためにrubyで逆ポーランド記法で計算するクラスを作ってみた。
#!/usr/bin/ruby # -*- coding: utf-8 -*- class Calc attr_writer :siki def initialze @siki = "" end def rtnval val hs = Hash.new hs["("] = 5 hs["*"] = 3 hs["×"] = 3 hs["/"] = 3 hs["÷"] = 3 hs["+"] = 2 hs["+"] = 2 hs["-"] = 2 hs["−"] = 2 hs[")"] = 1 hs["BTM"] = 0 rtn = hs[val] if rtn.nil? rtn = 4 end return rtn end def do_calc a = @siki.scan(/\w+|[^\s\w]+/) stc1 = [] stc2 = [] stc1 << "BTM" a.each do |s| v1 = rtnval(s) v2 = rtnval(stc1[stc1.size-1]) if(v2.to_i >= v1.to_i && stc1[stc1.size-1] != "(" ) stc2 << stc1.pop if(s == ")") stc2 << stc1.pop stc1.pop else stc1 << s end elsif(v1.to_i > v2.to_i && s != "(" ) stc1 << s else stc1 << s end end stc1.size.times do |r| stc2 << stc1.pop end stc2.size.times do |str| i = stc2.shift case(i) when("+" || "+") stc1 << stc1.pop.to_i + stc1.pop.to_i when("-" || "−") stc1 << stc1.pop.to_i - stc1.pop.to_i when("*" || "×") stc1 << stc1.pop.to_i * stc1.pop.to_i when("/" || "÷") stc1 << stc1.pop.to_i / stc1.pop.to_i when("BTM") break else stc1 << i end end stc1[0] end end
一応ちゃんと動くかチェック
c = Calc.new c.siki = "2 + 5 / 5" c.do_calc # => 3 c.siki ="(1+1) * 3 * (1+4)" c.do_calc # => 30
ちゃんと()つきでも動いた
※もっとうまい書き方があればコメントしていただけるとありがたいです
追記
ちゃんとテストしたらだめだめだったので直しました。
以下修正版
#!/usr/bin/ruby # -*- coding: utf-8 -*- class Calc #値の取り出し順序を判断するメソッド def rtnval val hs = { "(" => 5, "*" => 3, "×" => 3, "/" => 3, "÷" => 3, "+" => 2, "+" => 2, "-" => 2, "−" => 2, ")" => 1, "BTM" => 0 } rtn = hs[val] rtn = 4 unless rtn return rtn end #通常の式を逆ポーランド記法に変換して計算するメソッド def do_it siki arr = siki.scan(/\d+|[^\s\w]/) stc = [] #逆ポーランド記法変換時に使用する一時スタック rev_prd = [] #逆ポーランド記法の式を格納するスタック stc << "BTM" #stackの底を判断するために識別用変数を入れておく #引数で渡された式を逆ポーランド記法に変換 #1.式から取り出した項目の取り出し順がスタックの一番上の取り出し順と同じか小さい かつ # 式から取り出した項目が(でない限りはスタックの一番上の項目を逆ポーランド記法のスタックに格納 #2.式から取り出した項目の取り出し順がスタックの一番上の取り出し順より大きい場合はスタックに取り出した項目を格納 #3.式から取り出した項目が)かつスタックの一番上の項目が(の場合はスタックから(を取り除く arr.each do |crt| crt_val = rtnval(crt) stc.size.times do |val| stc_val = rtnval(stc[stc.size-1]) if(stc_val.to_i >= crt_val.to_i && stc[stc.size-1] != "(" ) rev_prd << stc.pop elsif(crt_val.to_i > stc_val.to_i ) stc << crt break elsif(stc_val.to_i == 5 && crt_val.to_i == 1) stc.pop break else stc << crt break end end end #スタックに残った値を逆ポーランド記法のスタックにPUSH stc.size.times do |param| rev_prd << stc.pop end # 逆ポーランド記法の式を計算する rev_prd.size.times do |str| i = rev_prd.shift case(i) when("+" || "+") stc << stc.pop.to_i + stc.pop.to_i when("-" || "−") stc << stc.pop.to_i - stc.pop.to_i when("*" || "×") stc << stc.pop.to_i * stc.pop.to_i when("/" || "÷") stc << stc.pop.to_i / stc.pop.to_i when("BTM") break else stc << i end end stc[0] # 計算結果 end end