Techioz Blog

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 ')'>