Class: Reporter::Formula
- Inherits:
-
Object
- Object
- Reporter::Formula
- Defined in:
- lib/reporter/formula.rb
Overview
Formula parser and calculator
- Author
-
Matthijs Groen
This class has two main functions:
-
to parse formula into ready-to-use-arrays
-
use those arrays to perform calculations
Parsing formula
my_formula = Formula.new(“100% – (MAX(score – 5, 0) * 10%)”) => Formula my_formula_data = Formula.make(“100% – (MAX(score – 5, 0) * 10%)”) => Array
The array format used for formula data is [:operator, [parameter, parameter]] the parameters can also be arrays: e.g. sub-calculations
The text formula can be build with the following elements:
operators:
- -
-
subtract. subtracts the right side from the left side argument.
- *
-
multiply. multiplies the left side with the right side argument.
- /
-
divide. divides the left side with the ride side argument.
- +
-
add. adds the right side to the left side argument.
- ^
-
power. multiplies the left side by the power of the right side.
functions:
functions have the format of name(parameters) the parameters of the function will be pre calculated before the code of the function is executed. supported functions:
- max
-
selects the biggest value from the provided values
- min
-
selects the smallest value from the provided values
- sum
-
creates a sum of all the provided values
- avg
-
creates an average of all the provided values
- select
-
selects the value with the index of the first parameter
- empty
-
returns 1 if the given string is empty, 0 otherwise
parenthesis:
parentesis can be used to group calculation parts
variables:
terms that start with a alfabetic character and contain only alfanumeric characters and underscores can be used as variables. A hash with variables should be supplied when the calculation is performed
numeric values:
numeric values like integers, floats and percentages are also allowed. Percentages will be converted to floats. 3% and 66% will be converted to resp. 100% / 3 and 200% / 3
Performing calculations
my_formula.call(:score => 7.0) => 0.8 (using the above formula example) Formula.calculate(my_formula_data, :score => 3.0) => 1.0 (using the above formula example)
Constant Summary collapse
- OPERATORS =
Known operators
"-*/+^"
Instance Attribute Summary collapse
-
#calculation ⇒ Object
readonly
Returns the value of attribute calculation.
Class Method Summary collapse
- .calculate(calculation, input) ⇒ Object
- .calculation_to_s(calculation, input, solve = false) ⇒ Object
-
.make(code) ⇒ Object
Parses the given formula as text and returns the formula in nested array form.
- .term_list(calculation, input = {}) ⇒ Object
Instance Method Summary collapse
-
#call(input) ⇒ Object
executes the formula with a hash of given calculation terms.
-
#initialize(code) ⇒ Formula
constructor
parse the given code formula in an array using the format calculation = [operation, [parameter, parameter]] a parameter can ofcourse be in turn another calculation.
- #solve(input) ⇒ Object
- #term_list ⇒ Object
- #to_string(input) ⇒ Object
Constructor Details
#initialize(code) ⇒ Formula
parse the given code formula in an array using the format calculation = [operation, [parameter, parameter]] a parameter can ofcourse be in turn another calculation
59 60 61 62 |
# File 'lib/reporter/formula.rb', line 59 def initialize(code) @calculation = Reporter::Formula.make code #puts "#{@calculation.inspect}" end |
Instance Attribute Details
#calculation ⇒ Object (readonly)
Returns the value of attribute calculation.
64 65 66 |
# File 'lib/reporter/formula.rb', line 64 def calculation @calculation end |
Class Method Details
.calculate(calculation, input) ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/reporter/formula.rb', line 168 def self.calculate(calculation, input) operation, parameters = *calculation parameters = parameters.collect do |parameter| parameter.is_a?(Array) ? calculate(parameter, input) : parameter end return nil if (parameters[0].nil? or parameters[1].nil?) and [:add, :subtract, :times, :divide, :power].include? operation case operation when :add then parameters[0] + parameters[1] when :subtract then parameters[0] - parameters[1] when :times then parameters[0] * parameters[1] when :divide then parameters[1] == 0 ? nil : parameters[0].to_f / parameters[1].to_f when :power then parameters[0] ** parameters[1] # functions: when :max then parameters.compact.max when :min then parameters.compact.min when :sum then begin result = 0.0 parameters.each { |value| result += value || 0.0 } result end when :select then begin index = parameters.shift index.is_a?(Numeric) ? parameters[index - 1] : nil end when :avg then begin items = parameters.compact result = 0.0 items.each { |value| result += value } result / items.length end when :empty then begin result = parameters.collect { |item| item.to_s.strip == "" ? 1 : 0 } result.include?(0) ? 0 : 1 end # variables when :term then begin raise "Can't find term: #{parameters[0]}. Has keys: #{input.keys.collect(&:to_s).sort.inspect}" unless input.has_key? parameters[0] input[parameters[0]] end when :negative_term then begin raise "Can't find term: #{parameters[0]}. Has keys: #{input.keys.sort.inspect}" unless input.has_key? parameters[0] val = input[parameters[0]] return nil unless val - val end when :literal parameters[0] when :text parameters[0] # no-op when nil, :percentage then parameters[0].to_f end end |
.calculation_to_s(calculation, input, solve = false) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/reporter/formula.rb', line 101 def self.calculation_to_s(calculation, input, solve = false) operation, parameters = * calculation string_parameters = parameters.collect do |parameter| parameter.is_a?(Array) ? "#{calculation_to_s(parameter, input, solve)}" : parameter end case operation when :add, :subtract, :times, :divide, :power then "(#{string_parameters[0]} #{{:add => "+", :subtract => "-", :times => "*", :divide => "/", :power => "^"}[operation]} #{string_parameters[1]})" # functions: when :max, :min, :sum, :select, :avg, :empty then if solve result = calculate(calculation, input) "#{operation}(#{string_parameters * ","})[#{result}]" else "#{operation}(#{string_parameters * ","})" end # variables when :text then "\"#{string_parameters[0]}\"" when :term then "#{string_parameters[0]}[#{input[string_parameters[0]] ? input[string_parameters[0]] : "nil"}]" when :negative_term then "-#{string_parameters[0]}[#{input[string_parameters[0]] ? input[string_parameters[0]] : "nil"}]" when :literal then begin "nil" if string_parameters[0].nil? end # no-op when nil then string_parameters[0].to_s when :percentage then "#{string_parameters[0] * 100.0}%" else "!unsupported(#{operation}}" end end |
.make(code) ⇒ Object
Parses the given formula as text and returns the formula in nested array form.
67 68 69 70 71 72 73 74 75 |
# File 'lib/reporter/formula.rb', line 67 def self.make(code) #puts "parsing: #{code}" begin parse_operation(code) rescue StandardError => e puts "Error in formula: #{code}: #{e}" raise end end |
.term_list(calculation, input = {}) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/reporter/formula.rb', line 152 def self.term_list(calculation, input = {}) operation, parameters = *calculation parameters = parameters.collect do |parameter| parameter.is_a?(Array) ? term_list(parameter, input) : parameter end case operation # variables when :term then input[parameters[0]] = :term when :negative_term then input[parameters[0]] = :term end end |
Instance Method Details
#call(input) ⇒ Object
executes the formula with a hash of given calculation terms
78 79 80 81 82 83 84 85 |
# File 'lib/reporter/formula.rb', line 78 def call(input) begin Reporter::Formula.calculate(@calculation, input) rescue StandardError => e Rails.logger.error "Error executing formula: #{Reporter::Formula.calculation_to_s(@calculation, input)} : #{e.}" raise end end |
#solve(input) ⇒ Object
97 98 99 |
# File 'lib/reporter/formula.rb', line 97 def solve(input) Reporter::Formula.calculation_to_s(@calculation, input, true) end |
#term_list ⇒ Object
87 88 89 90 91 |
# File 'lib/reporter/formula.rb', line 87 def term_list terms = {} Reporter::Formula.term_list @calculation, terms terms.keys end |
#to_string(input) ⇒ Object
93 94 95 |
# File 'lib/reporter/formula.rb', line 93 def to_string(input) Reporter::Formula.calculation_to_s(@calculation, input) end |