I am trying to solve the boolean expression optimization problem for a simple rule engine.
Here is what I am trying to do, lets say if I have following 5 conditions (where a,b,c,d,e,f are complex boolean expressions)
if (a AND b AND c AND d AND e AND f) then do-1
if (a AND b AND c AND d AND e) then do-2
if (a AND b AND c AND d) then do-3
if (a AND b AND c) then do-4
if (a AND b) then do-5
In case when I execute these conditions in a linear order I will
- evaluate "a" for 5 times
- evaluate "b" for 5 times
- evaluate "c" for 4 times
- evaluate "d" for 3 times
- evaluate "e" for 2 times
- evaluate "f" for 1 time
I would like to make a expression execution tree from this so that each expression (a,b,c,d,e,f) is evaluated minimum number of times.
The perfect solution will be that each expression is evaluated only once. I think this can be achieved with a tree creation where all these conditions are part of a tree.
The tree may look like this
if(a AND b) then do-5 {
if(c) then do-4 {
if(d) then do-3 {
if(e) then do-2 {
if(f) then do-1
}
}
}
}
My questions is - How can I make this tree from the set of boolean expressions listed above?
Related Questions:
Algorithm for evaluating nested logical expression
Converting an expression to conjunctive normal form with a twist
Is it useful in C# to apply DeMorgan's theorem to manually optimize boolean expressions in conditional statements (e.g. if conditions)
You could approach it this way:
var booleans = [a, b, c, d, e, f];
var i = 0;
while(booleans[i]) i++;
switch(i) {
case 0:
// do something
break;
case 1:
// do something
break;
...
}
I'm pretty sure there is a way to merge the while loop with the switch operator to get something even more optimized from this.
Related
I was trying to check online if there is any equivalent expression to 1. (A AND B) OR C, by removing the brackets and rearranging the operands and boolean operators in the above expression.
Like 2. (A OR B) AND C = A AND C OR B AND C.
If I try to solve the first expression with the same logic as above, it doesn't seem logically equivalent I. E. A OR C AND B OR C . I want to remove the brackets from the expression. That's my main aim
What do you mean by "remove the brackets"?
When you write
(A OR B) AND C = A AND C OR B AND C
what does mean "A AND C OR B AND C"?
Does it means
A AND (C OR B) AND C
or
(A AND C) OR (B AND C)
Probably the latter, but it is only because you see implicit brackets around the AND expressions.
One generally considers that "AND" has a higher priority (precedence in programming languages) than "OR". Like "×" has a higher priority than "+".
and
a×b+c
is generally not ambiguous in terms of interpretation.
And it is true that "×" can be distributed over a sum, while the opposite is not true.
But there is no such thing in boolean algebra, and "AND" and "OR" have similar properties.
And they have the same distributivity.
So if your question is
"can we distribute an "OR" over an "AND" expression?",
the answer is yes.
(A AND B) OR C = (A OR C) AND (B OR C)
(just consider what happens when C=0 and when C=1 to verify that it is true)
If your question is
"does such an expression can be expressed without parenthesis with the rule that AND expressions are implicitely surrounded by parenthesis?",
the answer is also yes. Just write
A AND B OR C
with the precedence rule, it is interpreted as
(A AND B) OR C
You can also write
A.B+C
to make clearer that "AND" is generally considered as equivalent to "×" and "OR" equivalent to "+" in terms of precedence.
And in programming languages like C, when one writes
A & B | C
it is clearly interpreted as
(A & B) | C
and the same is true for
A && B || C
So with the "implicit parenthesis around AND expressions" rule, you can write
A AND B OR C = (A OR C) AND (B OR C)
(A OR B) AND C = A AND C OR B AND C
Note that in either case, you need parenthesis in one side of the equality.
I was going through the laws that govern Monoids and one of the two laws state that the append operation must be associative. For function composition this means that for all functions X->X given there are 3 functions f,g and h (f∘g)∘h=f∘(g∘h)
In scalaz I see that there is a type called EndoMonoid and it uses compose for appends which is different from the way normal function compositions work
val f : Int => Int = x => x*x
val g : Int => Int = y => y + 1
val e = f.endo |+| g.endo
val d = g.endo |+| f.endo
e run 10
Int = 121
d run 10
Int = 101
As can be seen from the above results that the functions don't satisfy the associative property. Does this mean that not all functions of type X -> X is a monoid?
What you claim cannot be seen from your example.
Your example only proves that function composition is not commutative. But function composition never was supposed to be commutative: if it were commutative, all math and programming would catastrophically collapse to counting the number of occurrences of basic operations (that is, if "counting" itself would somehow survive that... I'm not sure whether it's possible).
To demonstrate an example of associativity, you need a third function h: Int => Int, and then you have to compare
(f.endo |+| g.endo) |+| h.endo
vs.
f.endo |+| (g.endo |+| h.endo)
exactly as the rule (that you yourself have just quoted!) states.
Every set of endomorphisms is always a monoid, because categories are essentially just "monoids with many objects", whereas monoids are just "categories with a single object". If you take any category and then look at the endomorphisms at a single object, you automatically, by definition, obtain a monoid. That's in particular true for the canonical ambient "category" of ordinary functions. [Here should come the usual disclaimer that it's not really a category, and that in every real programming language nothing is really true]
I have written this code
def getParallelList[T](list : List[T]) : ParSeq[T] = {
val parList = list.par
parList.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(10))
parList
}
for {
a <- getList1
b <- getList2
c = b.calculateSomething
d <- getParallelList(getList3)
} { ... }
I want to know if this is a good (or best) way to make the for loop execute in parallel? Or should I explicitly code in futures inside of the loop.
I tested this and it seemed to work... but I am not sure if this is the best way ... also I am worried that what happens to the values of a,b,c for different threads of d. If one thread finishes earlier? does it change the value of a, b, c for others?
If getList3 is referentially transparent, i.e. it is going to return the same value every time it's called, it's better idea to calculate it once, since invoking .par on a list has to turn it to a ParVector, which takes O(n) (as List is a linked list and can't be immediately converted to a Vector structure). Here is example:
val list3 = getParallelList(getList3)
for {
a <- getList1
b <- getList2
c = b.calculateSomething
d <- list3
} { ... }
In the for comprehension, the values for (a, b, c) will remain the same during processing of d values.
For best performance, you might consider making getList1 or getList2 parallel, depending on how evenly work is split for a/b/c values.
This question refers to code generation with the Isabelle/HOL theorem prover.
When I export code for the distinct function on lists
export_code distinct in Scala file -
I get the following code
def member[A : HOL.equal](x0: List[A], y: A): Boolean = (x0, y) match {
case (Nil, y) => false
case (x :: xs, y) => HOL.eq[A](x, y) || member[A](xs, y)
}
def distinct[A : HOL.equal](x0: List[A]): Boolean = x0 match {
case Nil => true
case x :: xs => ! (member[A](xs, x)) && distinct[A](xs)
}
This code has quadratic runtime. Is there a faster version available? I think of something like importing "~~/src/HOL/Library/Code_Char" for strings at the beginning of my theory and efficient code generation for lists is set up.
A better implementation for distinct would be to sort the list in O(n log n) and iterate over the list once. But I guess one can do better?
Anyway, is there a faster implementation for distinct and maybe other functions from Main available?
I do not know of any faster implementation in Isabelle2013's library, but you can easily do it yourself as follows:
Implement a function distinct_sorted that determines distinctness on sorted lists.
Prove that distinct_sorted indeed implements distinct on sorted lists
Prove a lemma that implements distinct via distinct_list and sorting, and declare it as the new code equation for distinct.
In summary, this looks as follows:
context linorder begin
fun distinct_sorted :: "'a list => bool" where
"distinct_sorted [] = True"
| "distinct_sorted [x] = True"
| "distinct_sorted (x#y#xs) = (x ~= y & distinct_sorted (y#xs))"
lemma distinct_sorted: "sorted xs ==> distinct_sorted xs = distinct xs"
by(induct xs rule: distinct_sorted.induct)(auto simp add: sorted_Cons)
end
lemma distinct_sort [code]: "distinct xs = distinct_sorted (sort xs)"
by(simp add: distinct_sorted)
Next, you need an efficient sorting algorithm. By default, sort uses insertion sort. If you import Multiset from HOL/Library, sort will be implemented by quicksort. If you import Efficient Mergesort from the Archive of Formal Proofs, you get merge sort.
While this can improve efficiency, there's also a snag: After the above declarations, you can execute distinct only on lists whose elements are instances of the type class linorder. As this refinement happens only inside the code generator, your definitions and theorems in Isabelle are not affected.
For example, to apply distinct to a list of lists in any code equation, you first have to define a linear order on lists: List_lexord in HOL/Library does so by picking the lexicographic order, but this requires a linear order on the elements. If you want to use string, which abbreviates char list, Char_ord defines the usual order on char. If you map characters to the character type of the target language with Code_Char, you also need the adaptation theory Code_Char_ord for the combination with Char_ord.
In Python
def cross(A, B):
"Cross product of elements in A and elements in B."
return [a+b for a in A for b in B]
returns an one-dimensional array if you call it with two arrays (or strings).
But in CoffeeScript
cross = (A, B) -> (a+b for a in A for b in B)
returns a two-dimensional array.
Do you think it's by design in CoffeeScript or is it a bug?
How do I flatten arrays in CoffeScript?
First I would say say that 2 array comprehensions in line is not a very maintainable pattern. So lets break it down a little.
cross = (A, B) ->
for a in A
for b in B
a+b
alert JSON.stringify(cross [1,2], [3,4])
What's happening here is that the inner creates a closure, which has its own comprehension collector. So it runs all the b's, then returns the results as an array which gets pushed onto the parent comprehension result collector. You are sort of expecting a return value from an inner loop, which is a bit funky.
Instead I would simply collect the results myself.
cross = (A, B) ->
results = []
for a in A
for b in B
results.push a + b
results
alert JSON.stringify(cross [1,2], [3,4])
Or if you still wanted to do some crazy comprehension magic:
cross = (A, B) ->
results = []
results = results.concat a+b for b in B for a in A
results
alert JSON.stringify(cross [1,2], [3,4])
Whether this is a bug in CS or not is a bit debatable, I suppose. But I would argue it's good practice to do more explicit comprehension result handling when dealing with nested iterators.
https://github.com/jashkenas/coffee-script/issues/1191