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