【C#】逆ポーランドコンバータを改良する②

この記事はひとりアドベントカレンダー2020の3日目の記事だったものです。

昨日までの実装で、括弧が含まれた式も変換して計算できるようになりました。今日は、sincosなどのメジャーな数学系関数を処理できるようにしていきます。

実装

関数対応をするにあたって修正の必要があるのは、トークン解析部分と計算部分です。優先度に応じてスタックに詰めていく作業は以前までと特に変更しません。

トークン解析

トークン解析するにあたり、数学関数に対応した正規表現を実装しておきましょう。今回は対応する数学関数ごとに独立した正規表現になっていますが、まとめてしまっても問題ありません。演算するときにどれなのか判別できないので数学関数ごとに分けましょう。また、トークンの種類としてFunctionも追加しておきましょう

public class Token {
  public enum Kind {
    Number,
    Operator,
    Function
  }
 
  public int priority_;
  public Kind kind_;
  public string value_;
}
public class RPN {
  Regex regex_;
  Regex sin_;
  Regex cos_;
  Regex num_;
  
  /// summary   : コンストラクタ
  public RPN() {
    regex_ = new Regex(@"([+\-*/=()])");
    sin_ = new Regex(@"sin");
    cos_ = new Regex(@"cos");
    num_ = new Regex(@"(-?[0-9]?\.[0-9]?|[0-9]*)");
  }
.....

そしたら、解析パターンを追加します。この時、数学関数はどの演算子よりも優先されるべきなので優先度は最大に設定しておきます。

if(Regex.IsMatch(t, regex_.ToString())) {
  var priority = 0;
  if(t.Equals("(") | t.Equals(")")) {
    priority = 4;
  } else if(t.Equals("/")) {
    priority = 3;
  } else if(t.Equals("*")) {
    priority = 2;
  } else {
    priority = 1;
  }
  tokenized.Add(new Token() { kind_ = Token.Kind.Operator, value_ = t, priority_ = priority });
}else if(Regex.IsMatch(t, cos_.ToString()) |
         Regex.IsMatch(t, sin_.ToString())){
  var priority = 5;
  tokenized.Add(new Token() { kind_ = Token.Kind.Function, value_ = t, priority_ = priority });
} else {
  tokenized.Add(new Token() { kind_ = Token.Kind.Number, value_ = t, priority_ = 0 });
}

計算

if(t.kind_ == Token.Kind.Number) {
  stack.Add(double.Parse(t.value_));
}else if(t.kind_ == Token.Kind.Function){
  if(Regex.IsMatch(t.value_, cos_.ToString())){
    var arg = stack.Last();
    stack.RemoveAt(stack.Count() - 1);
    double amp;
    var ampStr = Regex.Match(t.value_, num_.ToString()).Value;
    if(ampStr.Equals("")) {
      amp = 1;
    } else if(ampStr.Equals(".")) {
      throw new Exception("この計算はできません.");
    } else {
      amp = double.Parse(ampStr);
    }
    stack.Add(amp * Math.Cos(arg));
  }
  if(Regex.IsMatch(t.value_, sin_.ToString())){
    var arg = stack.Last();
    stack.RemoveAt(stack.Count() - 1);
    double amp;
    var ampStr = Regex.Match(t.value_, num_.ToString()).Value;
    if(ampStr.Equals("")) {
      amp = 1;
    } else if(ampStr.Equals(".")) {
      throw new Exception("この計算はできません.");
    } else {
      amp = double.Parse(ampStr);
    }
    stack.Add(amp * Math.Sin(arg));
  }
}else {
  var lhs = 0.0;
  var rhs = 0.0;
  if(stack.Count() >= 2) {
    rhs = stack.Last();
    stack.RemoveAt(stack.Count() - 1);
    lhs = stack.Last();
    stack.RemoveAt(stack.Count() - 1);
  } else if(stack.Count() == 1 && t.value_.Equals("-")) {
    lhs = 0;
    rhs = stack.Last();
    stack.RemoveAt(stack.Count() - 1);
  } else {
    throw new Exception("この計算はできません.");
  }
 
 
  if(t.value_.Equals("+")) {
    stack.Add(lhs + rhs);
  } else if(t.value_.Equals("-")) {
    stack.Add(lhs - rhs);
  } else if(t.value_.Equals("*")) {
    stack.Add(lhs * rhs);
  } else if(t.value_.Equals("/")) {
    if(rhs == 0) {
      throw new Exception("この計算はできません.");
    }
    stack.Add(lhs / rhs);
  }
}

てことで、3日間で数学関数に対応した逆ポーランド変換器を作成することができました。

おすすめ

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です