我需要接受来自用户的数学表达式(包括一个或多个未知数),并将值替换为未知数以获得结果。
我可以使用eval()来做到这一点,但除非有办法识别" safe"否则风险太大了。表达式。
如果我能提供帮助,我宁愿不写自己的解析器。
我搜索了一个现成的解析器,但我找到的唯一一个(https://www.ruby-toolbox.com/gems/expression_parser,似乎与http://lukaszwrobel.pl/blog/math-parser-part-4-tests中讨论的解析器相同)似乎仅限于&#34 ;四条规则" + - * /。我至少需要包括指数函数,日志函数和三角函数。
有什么建议吗?
更新:示例
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)
更新 - 预解析验证步骤
我想我会使用这个正则表达式检查字符串中是否有正确的标记:
/((((cos\(|sin\()|(tan\(|acos\())|((asin\(|atan\()|(exp\(|log\())|ln\()(([+-\/*^\(\)A-Z]|\d)+))*([+-\/*^\(\)A-Z]|\d)+/
但是我仍然会在实际评估过程中实现解析方法。
感谢您的帮助!
答案 0 :(得分:1)
你可以查看Dentaku宝石 - 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} )
答案 1 :(得分: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.
答案 2 :(得分: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
异常)。