README.md in dentaku-1.1.0 vs README.md in dentaku-1.2.0

- old
+ new

@@ -19,72 +19,84 @@ This is probably simplest to illustrate in code: ```ruby calculator = Dentaku::Calculator.new calculator.evaluate('10 * 2') -=> 20 +#=> 20 ``` Okay, not terribly exciting. But what if you want to have a reference to a variable, and evaluate it at run-time? Here's how that would look: ```ruby calculator.evaluate('kiwi + 5', kiwi: 2) -=> 7 +#=> 7 ``` You can also store the variable values in the calculator's memory and then evaluate expressions against those stored values: ```ruby calculator.store(peaches: 15) calculator.evaluate('peaches - 5') -=> 10 +#=> 10 calculator.evaluate('peaches >= 15') -=> true +#=> true ``` For maximum CS geekery, `bind` is an alias of `store`. Dentaku understands precedence order and using parentheses to group expressions to ensure proper evaluation: ```ruby calculator.evaluate('5 + 3 * 2') -=> 11 +#=> 11 calculator.evaluate('(5 + 3) * 2') -=> 16 +#=> 16 ``` +The `evalutate` method will return `nil` if there is an error in the formula. +If this is not the desired behavior, use `evaluate!`, which will raise an +exception. + +```ruby +calculator.evaluate('10 * x') +#=> nil +calculator.evaluate!('10 * x') +Dentaku::UnboundVariableError: Dentaku::UnboundVariableError +``` + A number of functions are also supported. Okay, the number is currently five, but more will be added soon. The current functions are -`if`, `not`, `round`, `rounddown`, and `roundup`, and they work like their counterparts in Excel: +`if`, `not`, `round`, `rounddown`, and `roundup`, and they work like their +counterparts in Excel: ```ruby calculator.evaluate('if (pears < 10, 10, 20)', pears: 5) -=> 10 +#=> 10 calculator.evaluate('if (pears < 10, 10, 20)', pears: 15) -=> 20 +#=> 20 ``` `round`, `rounddown`, and `roundup` can be called with or without the number of decimal places: ```ruby calculator.evaluate('round(8.2)') -=> 8 +#=> 8 calculator.evaluate('round(8.2759, 2)') -=> 8.28 +#=> 8.28 ``` `round` and `rounddown` round down, while `roundup` rounds up. If you're too lazy to be building calculator objects, there's a shortcut just for you: ```ruby Dentaku('plums * 1.5', plums: 2) -=> 3.0 +#=> 3.0 ``` BUILT-IN OPERATORS AND FUNCTIONS --------------------------------- @@ -93,11 +105,69 @@ Logic: `< > <= >= <> != = AND OR` Functions: `IF NOT ROUND ROUNDDOWN ROUNDUP` +RESOLVING DEPENDENCIES +---------------------- +If your formulas rely on one another, they may need to be resolved in a +particular order. For example: + +```ruby +calc = Dentaku::Calculator.new +calc.store(monthly_income: 50) +need_to_compute = { + income_taxes: "annual_income / 5", + annual_income: "monthly_income * 12" +} +``` + +In the example, `annual_income` needs to be computed (and stored) before +`income_taxes`. + +Dentaku provides two methods to help resolve formulas in order`: + +#### Calculator.dependencies +Pass a (string) expression to Dependencies and get back a list of variables (as +`:symbols`) that are required for the expression. `Dependencies` also takes +into account variables already (explicitly) stored into the calculator. + +```ruby +calc.dependencies("monthly_income * 12") +#=> [] +# (since monthly_income is in memory) + +calc.dependencies("annual_income / 5") +#=> [:annual_income] +``` + +#### Calculator.solve! +Have Dentaku figure out the order in which your formulas need to be evaluated. + +Pass in a hash of {eventual_variable_name: "expression"} to `solve!` and +have Dentaku figure out dependencies (using `TSort`) for you. + +Raises `TSort::Cyclic` when a valid expression order cannot be found. + +```ruby +calc = Dentaku::Calculator.new +calc.store(monthly_income: 50) +need_to_compute = { + income_taxes: "annual_income / 5", + annual_income: "monthly_income * 12" +} +calc.solve!(need_to_compute) +#=> {annual_income: 600, income_taxes: 120} + +calc.solve!( + make_money: "have_money", + have_money: "make_money" +} +#=> raises TSort::Cyclic +``` + EXTERNAL FUNCTIONS ------------------ I don't know everything, so I might not have implemented all the functions you need. Please implement your favorites and send a pull request! Okay, so maybe @@ -146,13 +216,13 @@ type: :numeric, signature: [:numeric, :numeric], body: ->(mantissa, exponent) { mantissa ** exponent } ) > c.evaluate('EXP(3,2)') -=> 9 +#=> 9 > c.evaluate('EXP(2,3)') -=> 8 +#=> 8 ``` Here's an example of adding the `max` function: ```ruby @@ -162,11 +232,11 @@ type: :numeric, signature: [:non_close_plus], body: ->(*args) { args.max } ) > c.evaluate 'MAX(5,3,9,6,2)' -=> 9 +#=> 9 ``` THANKS ------ @@ -180,9 +250,10 @@ * [thbar](https://github.com/thbar) / [BoxCar](https://www.boxcar.io) * [antonversal](https://github.com/antonversal) * [mvbrocato](https://github.com/mvbrocato) * [brixen](https://github.com/brixen) * [0xCCD](https://github.com/0xCCD) +* [AlexeyMK](https://github.com/AlexeyMK) LICENSE -------