Ruby: 文字列式を解析、置換、評価する
概要
友人の心理調査プロジェクトのために、簡単な Ruby on Rails 調査アプリケーションを作成しています。 したがって、アンケートがあり、各アンケートには多数の質問があり、各質問には参加者が選択できるオプションが 1 つあります。何も刺激的なことはありません。
興味深い点の 1 つは、各回答選択肢にスコア値が関連付けられていることです。 したがって、各調査について、これらの値に基づいて合計スコアを計算する必要があります。
私のアイデアは、ハードコーディングされた計算の代わりに、ユーザーがアンケートの合計スコアを計算する式を追加できるようにすることです。式の例:
"Q1 + Q2 + Q3"
"(Q1 + Q2 + Q3) / 3"
"(10 - Q1) + Q2 + (Q3 * 2)"
基本的な数学だけです (わかりやすくするために追加の括弧を付けています)。基本的な数学を理解している人なら誰でも、複雑な構文を使用せずに数式を入力できるように、数式を非常にシンプルに保つことが目的です。
私のアイデアは、任意の式を使用して、Q1、Q2 などのプレースホルダーを、参加者が選択した内容に基づいたスコア値に置き換えることです。次に、新しく形成された文字列を eval() します。このようなもの:
f = "(Q1 + Q2 + Q3) / 2" # some crazy formula for this survey
values = {:Q1 => 1, :Q2 => 2, :Q3 => 2} # values for substitution
result = f.gsub(/(Q\d+)/) {|m| values[$1.to_sym] } # string to be eval()-ed
eval(result)
そこで私の質問は次のとおりです。
ありがとう!
解決策
OK、これで完全に安全になりました。私は誓います!
通常は式変数のクローンを作成しますが、この場合は敵対的なユーザーが心配なので、その場で変数をクリーンアップしました。
class Evaluator
def self.formula(formula, values)
# remove anything but Q's, numbers, ()'s, decimal points, and basic math operators
formula.gsub!(/((?![qQ0-9\s\.\-\+\*\/\(\)]).)*/,'').upcase!
begin
formula.gsub!(/Q\d+/) { |match|
(
values[match.to_sym] &&
values[match.to_sym].class.ancestors.include?(Numeric) ?
values[match.to_sym].to_s :
'0'
)+'.0'
}
instance_eval(formula)
rescue Exception => e
e.inspect
end
end
end
f = '(q1 + (q2 / 3) + q3 + (q4 * 2))' # some crazy formula for this survey
values = {:Q2 => 1, :Q4 => 2} # values for substitution
puts "formula: #{f} = #{Evaluator.formula(f,values)}"
=> formula: (0.0 + (1.0 / 3) + 0.0 + (2.0 * 2)) = 4.333333333333333
f = '(Q1 + (Q2 / 3) + Q3 + (Q4 * 2)) / 2' # some crazy formula for this survey
values = {:Q1 => 1, :Q3 => 2} # values for substitution
puts "formula: #{f} = #{Evaluator.formula(f,values)}"
=> formula: (1.0 + (0.0 / 3) + 2.0 + (0.0 * 2)) / 2 = 1.5
f = '(Q1 + (Q2 / 3) + Q3 + (Q4 * 2)) / 2' # some crazy formula for this survey
values = {:Q1 => 'delete your hard drive', :Q3 => 2} # values for substitution
puts "formula: #{f} = #{Evaluator.formula(f,values)}"
=> formula: (0.0 + (0.0 / 3) + 2.0 + (0.0 * 2)) / 2 = 1.0
f = 'system("ruby -v")' # some crazy formula for this survey
values = {:Q1 => 'delete your hard drive', :Q3 => 2} # values for substitution
puts "formula: #{f} = #{Evaluator.formula(f,values)}"
=> formula: ( -) = #<SyntaxError: (eval):1: syntax error, unexpected ')'>