I have implemented a naive evaluator of some functions. I have just entered the world of f#, so it will ask you there exists (and if yes, how to implement) a faster evaluator. I have to get a data structure that contains (variableName, (condition + newVariableName)). These two fields are strings. Below is a part of my version of the evaluator:
let env = new Dictionary<_,_>(HashIdentity.Structural)
//eval: expr -> Prop
let rec eval = function
//Val of string -- is a variable name or a string value
| Val e -> if e.Equals("TRUE") then True // True is a Prop type used for BDD Algorithm
elif e.Equals("FALSE") then False // False is a Prop type used for BDD Algorithm
else var e //var of string --- is a Prop type used for BDD Algorithm
| Int i -> var (i.ToString())
| Float e -> var (e.ToString())
| Const c -> var c //Const of string --- is a constant name (ex. "$foo" is a constant)
| Path (e, s) -> var ((eval e).ToString() + "." + s)
| Lookup(s, el) -> var s
| Integer(ex) -> eval ex
| FromTo (e, el) ->var ((eval e).ToString() + (eval el.Head).ToString() + (eval el.Tail.Head).ToString())
| Str(e) -> eval e
| Equality (v, e) -> let evalV = eval v
let evalE = eval e
match evalE with
| Var e ->
let sndKey = "Equality" + evalE.ToString()
if env.ContainsKey (evalV.ToString()) then
if env.[(evalV.ToString())].Equals(sndKey) then
var env.[(evalV.ToString())]
else ~~~ (var env.[(evalV.ToString())])
else
env.Add((evalV.ToString()), (evalV.ToString()) + sndKey)
var ((evalV.ToString()) + sndKey)
| _ as k -> if bddBuilder.Equiv k False then
let sndKey = "Equality" + "False"
if env.ContainsKey (evalV.ToString()) then
if env.[(evalV.ToString())].Equals(sndKey) then
var env.[(evalV.ToString())]
else ~~~ (var env.[(evalV.ToString())])
else
env.Add((evalV.ToString()), (evalV.ToString()) + sndKey)
var ((evalV.ToString()) + sndKey)
else let sndKey = "Equality" + "True"
if env.ContainsKey (evalV.ToString()) then
if env.[(evalV.ToString())].Equals(sndKey) then
var env.[(evalV.ToString())]
else ~~~ (var env.[(evalV.ToString())])
else
env.Add((evalV.ToString()), (evalV.ToString()) + sndKey)
var ((evalV.ToString()) + sndKey)
| Inequality (v, e) -> let evaluatedV = (eval v).ToString()
let evaluatedE = (eval e).ToString()
let sndKey = "Inequality" + evaluatedE
if env.ContainsKey (evaluatedV.ToString()) then
if env.[(evaluatedV.ToString())].Equals(sndKey) then
var env.[(evaluatedV.ToString())]
else ~~~ (var env.[(evaluatedV.ToString())])
else env.Add((evaluatedV.ToString()), (evaluatedV.ToString()) + sndKey)
var ((evaluatedV.ToString()) + sndKey)
| IfThenElse (e1, e2, e3) -> (eval e1 &&& eval e2) ||| ((~~~ (eval e1)) &&& eval e3)
| FindString(e1, e2) -> var ("FS" + (eval e1).ToString() + (eval e2).ToString())
| GreaterThan(e1, e2) -> let evaluatedV = (eval e1).ToString()
let evaluatedE = (eval e2).ToString()
let sndKey = "GreaterThan" + evaluatedE
if env.ContainsKey (evaluatedV.ToString()) then
if env.[(evaluatedV.ToString())].Equals(sndKey) then
var env.[(evaluatedV.ToString())]
else ~~~ (var env.[(evaluatedV.ToString())])
else env.Add((evaluatedV.ToString()), (evaluatedV.ToString()) + sndKey)
var ((evaluatedV.ToString()) + sndKey)
| GreaterThanOrEqual(e1, e2) -> let evaluatedV = (eval e1).ToString()
let evaluatedE = (eval e2).ToString()
let sndKey = "GreaterThanOrEqual" + evaluatedE
if env.ContainsKey (evaluatedV.ToString()) then
if env.[(evaluatedV.ToString())].Equals(sndKey) then
var env.[(evaluatedV.ToString())]
else ~~~ (var env.[(evaluatedV.ToString())])
else env.Add((evaluatedV.ToString()), (evaluatedV.ToString()) + sndKey)
var ((evaluatedV.ToString()) + sndKey)
| Null(e) -> var ("Null" + (eval e).ToString())
| GetToken(e1, e2, e3) -> var ((eval e1).ToString() + (eval e2).ToString())
| Mod(e1, e2) -> var ((eval e1).ToString() + (eval e2).ToString())
| Match(e1, e2) -> let evaluatedV = (eval e1).ToString()
let evaluatedE = (eval e2).ToString()
let sndKey = "Match" + evaluatedE
if env.ContainsKey (evaluatedV.ToString()) then
if env.[(evaluatedV.ToString())].Equals(sndKey) then
var env.[(evaluatedV.ToString())]
else ~~~ (var env.[(evaluatedV.ToString())])
else env.Add((evaluatedV.ToString()), (evaluatedV.ToString()) + sndKey)
var ((evaluatedV.ToString()) + sndKey)
| LessThenOrEqual(e1, e2) -> let evaluatedV = (eval e1).ToString()
let evaluatedE = (eval e2).ToString()
let sndKey = "LessThen" + evaluatedE
if env.ContainsKey (evaluatedV.ToString()) then
if env.[(evaluatedV.ToString())].Equals(sndKey) then
var env.[(evaluatedV.ToString())]
else ~~~ (var env.[(evaluatedV.ToString())])
else env.Add((evaluatedV.ToString()), (evaluatedV.ToString()) + sndKey)
var ((evaluatedV.ToString()) + sndKey)
| LessThen(e1, e2) -> let evaluatedV = (eval e1).ToString()
let evaluatedE = (eval e2).ToString()
let sndKey = "LessThen" + evaluatedE
if env.ContainsKey (evaluatedV.ToString()) then
if env.[(evaluatedV.ToString())].Equals(sndKey) then
var env.[(evaluatedV.ToString())]
else ~~~ (var env.[(evaluatedV.ToString())])
else env.Add((evaluatedV.ToString()), (evaluatedV.ToString()) + sndKey)
var ((evaluatedV.ToString()) + sndKey)
| Length(e) -> var ("Len" + (eval e).ToString())
| Full(e) -> var ("Full" + (eval e).ToString())
| Minus(e1, e2) -> var ((eval e1).ToString() + (eval e2).ToString())
| Times(e1, e2) -> var ((eval e1).ToString() + (eval e2).ToString())
| Plus (e1, e2) -> var ((eval e1).ToString() + (eval e2).ToString())
| Duration(e) -> eval e
| Minutes(e) -> eval e
| Trim(e) -> var ("Tri" + (eval e).ToString())
| Reverse(e) -> var ("Rev" + (eval e).ToString())
| Ast.And (v1, v2) -> eval v1 &&& eval v2
| Ast.Or (v1, v2) -> eval v1 ||| eval v2
| Ast.Not(v1) -> ~~~ (eval v1)
| _ as a-> failwithf "Expression %A not found" a
Edit: This evaluator is used to verify boolean condition. In this version there is only one Dictionary and it is more performant, but how can I model situations such as the following:
The answer is: it depends.
It some scenarios it may be best to naively walk the tree evaluating as you go. This will probably be the best strategy when your expressions are small and evaluated once, or only a few times.
If you're expressions are large and maybe evaluated maybe times it maybe be best to try and do some optimization. Any optimization will have an associated cost that will need to be amortized, so this is why it is best to target large expression that will need to be executed several times. There are many types of optimization you could try, here are a few suggestions (a good compilers text book would no doubt have many more and better suggestions):
Any optimizations you implement should be carefully measured against the 'naïve' baseline, in some cases your optimization may not behave as expected and could result in a slower execution.
Anything other that quite simple optimizations will probably be quite tricky to implement, so good luck!
Perhaps the simplest optimization to your code that is likely to yield a significant gain is replacing your use of the (horrid) F# extension method for searching a Dictionary
with the .NET alternative that does not allocate. So this:
match env.TryGetValue evaluatedV with
| true, v1 ->
match v1.TryGetValue sndKey with
| true, v2 -> v2
| _ ->
v1.[sndKey] <- evaluetedV + sndKey
env.[evaluatedV] <- v1
evaluatedV + sndKey
| _ ->
if value.Count <> 0 then value.Clear()
value.[sndKey] <- evaluetedV + sndKey
env.[evaluatedV] <- value
evaluatedV + sndKey
becomes:
let mutable v1 = Unchecked.defaultof<_>
if env.TryGetValue(evaluatedV, &v1) then
let mutable v2 = Unchecked.defaultof<_>
if v1.TryGetValue(sndKey, &v2) then v2 else
v1.[sndKey] <- evaluetedV + sndKey
env.[evaluatedV] <- v1
evaluatedV + sndKey
else
if value.Count <> 0 then value.Clear()
value.[sndKey] <- evaluetedV + sndKey
env.[evaluatedV] <- value
evaluatedV + sndKey
However, those hash table lookups are probably killing your performance. There are a variety of techniques that can evade such problems but they are not F# specific:
value
or env
dictionaries.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.