2

I need to accept an mathematical expression (including one or more unknowns) from the user and substitute values in for the unknowns to get a result.

I could use eval() to do this, but it's far too risky unless there is a way to recognise "safe" expressions.

I'd rather not write my own parser if I can help it.

I searched for a ready-made parser but the only one I found ( https://www.ruby-toolbox.com/gems/expression_parser , which seems to be the same as the one discussed at http://lukaszwrobel.pl/blog/math-parser-part-4-tests) seems to be limited to the "four rules" +-*/. I need to include exponential, log and trig functions at the very least.

Any suggestions?

UPDATE: EXAMPLE

include Math

def exp(x)
 Math.exp(x)
end

def cos(x)
 Math.cos(x)
end

pi=Math::PI
t=2
string= '(3*exp(t/2)*cos(3*t-pi/2))'
puts eval(string)

UPDATE - a pre-parsing validation step

I think I will use this regex to check the string has the right kinds of tokens in it:

/((((cos\(|sin\()|(tan\(|acos\())|((asin\(|atan\()|(exp\(|log\())|ln\()(([+-\/*^\(\)A-Z]|\d)+))*([+-\/*^\(\)A-Z]|\d)+/

But I will still implement the parsing method during the actual evaluation.

Thanks for the help!

4

3 回答 3

1

您可以查看 Dentaku gem - https://github.com/rubysolo/dentaku

您可以使用它来执行用户给定的公式。

这是此 gem 的示例用法。

class FormulaExecutor
  def execute_my_formula(formula, params)
    calc = Dentaku::Calculator.new

    # Param 1 => formula to execute
    # Param 2 => Hash of local variables
    calc.evaluate(formula, params)
  end
end

FormulaExecutor.new.execute_my_formula( "length * breadth", {'length' => 11, 'breadth' => 120} )
于 2017-12-07T17:15:05.703 回答
0

首先假设 eval 不存在,除非您对评估的内容有非常严格的控制。即使您不解析,也可以将所有输入拆分为标记并检查每个标记是否是可接受的标记。

这是一种非常粗略的方法来检查输入除了有效标记之外什么都没有。许多重构/改进是可能的。

include Math

def exp(x)
 Math.exp(x)
end

def cos(x)
 Math.cos(x)
end

pi=Math::PI
t=2

a = %Q(3*exp(t/2)*cos(3*t-pi/2))  # input string

b = a.tr("/*)([0-9]-",'')  # remove all special single chars
b.gsub!(/(exp|cos|pi|t)/,'')  # remove all good tokens

eval(a) if b == ''  # eval if nothing other than good tokens.
于 2012-06-07T00:05:41.693 回答
0

如果 eval 可以工作,那么您可以使用 ruby​​ 解析器(例如gem install ruby_parser)解析表达式,然后递归地评估 S 表达式,忽略或引发非算术函数的错误。这可能需要一些工作,但听起来很有趣:

require 'ruby_parser'

def evaluate_arithmetic_expression(expr)
  parse_tree = RubyParser.new.parse(expr)  # Sexp < Array
  return evaluate_parse_tree(parse_tree)
end

def evaluate_parse_tree(parse_tree)
  case parse_tree[0]
  when :lit
    return parse_tree[1]
  when :call
    meth = parse_tree[2]
    if [:+, :*, :-, :/, :&, :|, :**].include? meth
      val = evaluate_parse_tree parse_tree[1]
      arglist = evaluate_parse_tree parse_tree[3]
      return val.send(meth, *arglist)
    else
      throw 'Unsafe'
    end
  when :arglist
    args = parse_tree[1..-1].map {|sexp| evaluate_parse_tree(sexp) }
    return args
  end
end

您应该能够很容易地增强它以包含cos,sin等内容。它适用于我尝试过的一些简单示例,并且包括免费检查格式是否正确(Racc::ParseError如果没有,解析会引发异常)。

于 2012-06-07T01:14:00.117 回答