diff --git a/builders.go b/builders.go index 52280b0..62ecb6c 100644 --- a/builders.go +++ b/builders.go @@ -1,18 +1,18 @@ package boolgebra // TermBuilder can be used to efficiently append ID ( or Not(ID)) into a big And -type TermBuilder struct { +type TermBuilder[T comparable] struct { isLitFalse bool - m minterm + m Term[T] } // And append a variable to the current term -func (t *TermBuilder) And(id string, val bool) { +func (t *TermBuilder[T]) And(id T, val bool) { if t.isLitFalse { return } // nothing to do if t.m == nil { - t.m = make(minterm) + t.m = make(Term[T]) } if prev, exists := t.m[id]; exists && prev != val { //attempt to do something like AND(x, !x) which is always false therefore the result will always be Lit(false) @@ -24,14 +24,14 @@ func (t *TermBuilder) And(id string, val bool) { } // IsFalse returns true if the term under construction is already degenerated to False -func (t TermBuilder) IsFalse() bool { return t.isLitFalse } +func (t TermBuilder[T]) IsFalse() bool { return t.isLitFalse } -func (t *TermBuilder) Build() Expr { +func (t *TermBuilder[T]) Build() Expr[T] { if t.isLitFalse { t.isLitFalse = false // reset it - return Lit(false) + return Lit[T](false) } res := t.m t.m = nil // destroy reference to m to avoid editing it anymore - return res + return Expr[T]{res} } diff --git a/builders_test.go b/builders_test.go index e4e2905..d26d8fc 100644 --- a/builders_test.go +++ b/builders_test.go @@ -11,7 +11,7 @@ import ( // This benchmark is to show that, in comparision with BenchmarkTermBuilder func BenchmarkAnd(b *testing.B) { - res := Lit(true) // neutral for And + res := Lit[string](true) // neutral for And // prepare 1000 different ids M := 1000 var ids []string @@ -36,7 +36,7 @@ func BenchmarkTermBuilder(b *testing.B) { for i := 0; i < M; i++ { ids = append(ids, "parameter_"+strconv.Itoa(i)) } - var t TermBuilder + var t TermBuilder[string] b.ResetTimer() for i := 0; i < b.N; i++ { t.And(ids[i%M], true) diff --git a/go.mod b/go.mod index 5e4e470..d3a3b7f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/etnz/boolgebra -go 1.12 +go 1.18 require ( github.com/etnz/permute v0.0.0-20160126210303-99613134c393 diff --git a/grid/rules.go b/grid/rules.go index 7dd7ea0..45fbff4 100644 --- a/grid/rules.go +++ b/grid/rules.go @@ -8,20 +8,30 @@ import ( ) // creates an ID that means property name = value -// just for test, not safe for any kind of injection -func P(name, value string) Expr { return ID(name + "=" + value) } +func P[T comparable](name, value T) Expr[Pair[T]] { + return ID(Pair[T]{Key: name, Val: value}) +} + +type Pair[T comparable] struct { + Val T + Key T +} + +func (p Pair[T]) String() string { + return fmt.Sprintf("%v=%v", p.Key, p.Val) +} // Values is a list all possible values without repetition // // a Group is a subset of Values, Groups is the set of all defined Group, N is card(Groups) // -// Let's define 'R' an transitive and symetric relation in Values noted `\forall x,y \in Values xRy` +// Let's define 'R' an transitive and symmetric relation in Values noted `\forall x,y \in Values xRy` // -// 1. `\forall g,h \in Groups² |g| = |h| \and g \inter h = \phi` -// 2. `\forall G \in Groups, \forall v \notin G \exists! w in G vRw` +// 1. `\forall g,h \in Groups² |g| = |h| \and g \inter h = \phi` +// 2. `\forall G \in Groups, \forall v \notin G \exists! w in G vRw` // // groups are defined by to position in the list -func Rules(N int, values ...string) Expr { +func Rules[T comparable](N int, values ...T) Expr[Pair[T]] { if len(values)%N != 0 { panic(fmt.Sprintf("inconsistent number of values %d with number of groups %d (not divisible)", len(values), N)) @@ -30,7 +40,7 @@ func Rules(N int, values ...string) Expr { M := len(values) / N { // the following code is in a a block 'cause I don't keep anyting from here the index is purely local, and temporary - index := make(map[string]struct{}) // index values (that shall be unique) + index := make(map[T]struct{}) // index values (that shall be unique) for _, v := range values { index[v] = struct{}{} } @@ -82,16 +92,16 @@ func Rules(N int, values ...string) Expr { } //rules := make([]map[string]bool, 0) // the game rules will be an Or() of all possible solutions - rules := Lit(false) + rules := Lit[Pair[T]](false) for ok := true; ok; ok = next() { // loop over all columns x all permutations, and break when done - var tb TermBuilder // build a big AND expression + var tb TermBuilder[Pair[T]] // build a big AND expression // for the given solution, scan all possible ID ( "Paul is 6yo") wether it is true or not for i, v := range values { for j, w := range values { if i != j { - tb.And(v+"="+w, whois(i) == whois(j)) + tb.And(Pair[T]{v, w}, whois(i) == whois(j)) } } } @@ -104,9 +114,9 @@ func Rules(N int, values ...string) Expr { return rules } -func Solve(nbproperties int, values []string, hints ...Expr) Expr { +func Solve[T comparable](nbproperties int, values []T, hints ...Expr[Pair[T]]) Expr[Pair[T]] { - rules := []Expr{} + rules := []Expr[Pair[T]]{} rules = append(rules, Rules(nbproperties, values...)) rules = append(rules, hints...) return Simplify(And(rules...)) diff --git a/grid/samples_test.go b/grid/samples_test.go index f26e060..d414786 100644 --- a/grid/samples_test.go +++ b/grid/samples_test.go @@ -47,11 +47,11 @@ func ExampleSimplify_logic3x1() { result := Simplify(And(rules, Hint1, Hint2, Hint3)) - if result.Terms() > 1 { - fmt.Printf("There are %d solutions, that's too many\n", result.Terms()) + if len(result) > 1 { + fmt.Printf("There are %d solutions, that's too many\n", len(result)) fmt.Println(Factor(result)) } else { - fmt.Printf("There is %d solution.\n", result.Terms()) + fmt.Printf("There is %d solution.\n", len(result)) } //Output: // There is 1 solution. @@ -95,13 +95,13 @@ func ExampleSimplify_logic4x1() { P(Philippe, R6), ) - if result.Terms() > 1 { - fmt.Printf("There are %d solutions, that's too many\n", result.Terms()) + if len(result) > 1 { + fmt.Printf("There are %d solutions, that's too many\n", len(result)) deduction, rem := Factor(result) fmt.Println(deduction) fmt.Println(rem) } else { - fmt.Printf("There is %d solution.\n", result.Terms()) + fmt.Printf("There is %d solution.\n", len(result)) } //Output: // There is 1 solution. diff --git a/primary.go b/primary.go index 6f5811b..0b780f1 100644 --- a/primary.go +++ b/primary.go @@ -4,37 +4,36 @@ package boolgebra // expression/minterm types // Lit returns an Expr equivalent to a boolean literal 'val' -func Lit(val bool) Expr { +func Lit[T comparable](val bool) Expr[T] { if val { - return minterm{} // true is by definition an empty minterm ( neutral for product) + return Expr[T]{Term[T]{}} // true is by definition an empty minterm ( neutral for product) } else { - return expression{} // false is an empty expression (neutral for sum) + return Expr[T]{} // false is an empty expression (neutral for sum) } } // ID returns an Expr equivalent to a single ID 'id' -func ID(id string) Expr { return minterm{id: true} } +func ID[T comparable](id T) Expr[T] { return Expr[T]{Term[T]{id: true}} } // Or return the conjunction of all the expression passed in parameter. // // By convention, if 'x' is empty it returns Lit(false). See https://en.wikipedia.org/wiki/Empty_sum -func Or(x ...Expr) Expr { +func Or[T comparable](x ...Expr[T]) Expr[T] { // start with the neutral of the Or i.e a false - res := make(expression, 0) + res := make(Expr[T], 0) // scan all terms, in all expr for _, exp := range x { - for i := 0; i < exp.Terms(); i++ { - t := exp.Term(i) - if t.Is(true) { - return t // + for _, t := range exp { + if t.isLiteral(true) { + return Expr[T]{Term[T]{}} } - if !t.Is(false) { // if this is the literal false, we can just skip it - res = append(res, t.(minterm)) + if !t.isLiteral(false) { // if this is the literal false, we can just skip it + res = append(res, t) } } } if len(res) == 1 { - return res[0] + return Expr[T]{res[0]} } return res } @@ -42,10 +41,10 @@ func Or(x ...Expr) Expr { // And returns the disjunction of all the expressions passed in parameters. // // By convention, if 'x' is empty it returns Lit(true). See https://en.wikipedia.org/wiki/Empty_product -func And(expressions ...Expr) Expr { +func And[T comparable](expressions ...Expr[T]) Expr[T] { if len(expressions) == 0 { - return Lit(true) // return the neutral of And operation by convention + return Lit[T](true) // return the neutral of And operation by convention } if len(expressions) == 1 { return expressions[0] // another common degenerated case @@ -62,26 +61,24 @@ func And(expressions ...Expr) Expr { // this is the only real case x, y := expressions[0], expressions[1] - if x.Is(false) || y.Is(false) { - return Lit(false) + if x.isLiteral(false) || y.isLiteral(false) { + return Lit[T](false) } - if x.Is(true) { + if x.isLiteral(true) { return y } - if y.Is(true) { + if y.isLiteral(true) { return x } // general case - z := make(expression, 0, x.Terms()*y.Terms()) + z := make(Expr[T], 0, len(x)*len(y)) // this is the big one: all terms from x multiplied by terms from y - for i := 0; i < x.Terms(); i++ { - m := x.Term(i).(minterm) + for _, m := range x { product: - for j := 0; j < y.Terms(); j++ { - n := y.Term(j).(minterm) + for _, n := range y { // compute the real m && n , this is basically a merge of all IDs // there is one special case: A & A' = false @@ -93,7 +90,7 @@ func And(expressions ...Expr) Expr { } // basic merge - o := make(minterm) + o := make(Term[T]) for k, v := range m { o[k] = v } @@ -109,17 +106,7 @@ func And(expressions ...Expr) Expr { } // Not returns the negation of 'x'. -func Not(x Expr) Expr { return x.Not() } - -// Simplify returns a simpler version of 'x' by applying simplification rules. -func Simplify(x Expr) Expr { - switch e := x.(type) { - case expression: - return reduce(e) - default: - return x // unchanged - } -} +func Not[T comparable](x Expr[T]) Expr[T] { return x.Not() } // Factor computes the greatest common factor between terms of x // @@ -127,30 +114,27 @@ func Simplify(x Expr) Expr { // // x is currently a sum of terms, this function returns f and rem so that // -// x = And(f, rem) -// f.Terms() ==1 : it's a minterm -// -func Factor(x Expr) (f, rem Expr) { - var res minterm - for i := 0; i < x.Terms(); i++ { - m := x.Term(i).(minterm) +// x = And(f, rem) +// f.Terms() ==1 : it's a minterm +func Factor[T comparable](x Expr[T]) (f, rem Expr[T]) { + var res Term[T] + for i, m := range x { if i == 0 { // special case for the first one, need to init the thing res = m } res = inter(res, m) if len(res) == 0 { - return expression{res}, x // empty one + return Expr[T]{res}, x // empty one } } // now for each minterm recompute the reminder - r := expression{} - for i := 0; i < x.Terms(); i++ { - m := x.Term(i).(minterm) + r := Expr[T]{} + for _, m := range x { r = append(r, div(m, res)) } - return expression{res}, r + return Expr[T]{res}, r } diff --git a/primary_test.go b/primary_test.go index 0f9945a..b44b289 100644 --- a/primary_test.go +++ b/primary_test.go @@ -13,19 +13,19 @@ func ExampleID() { // TesLit ensure that the basic true and false are working accordingly with Is(bool) func TestLit(t *testing.T) { - if !Lit(true).Is(true) { + if !Lit[string](true).isLiteral(true) { t.Error("Lit(true).Is(true) must be true") } - if !Lit(false).Is(false) { + if !Lit[string](false).isLiteral(false) { t.Error("Lit(false).Is(false) must be true") } } func ExampleLit() { - A := Lit(true) + A := Lit[string](true) fmt.Println(A) - B := Lit(false) + B := Lit[string](false) fmt.Println(B) //Output: // Lit(true) @@ -34,8 +34,8 @@ func ExampleLit() { func ExampleNot() { fmt.Println(Not(ID("A"))) - fmt.Println(Not(Lit(true))) - fmt.Println(Not(Lit(false))) + fmt.Println(Not(Lit[string](true))) + fmt.Println(Not(Lit[string](false))) //Output: // Not("A") // Lit(false) @@ -46,8 +46,8 @@ func ExampleAnd() { A := ID("A") B := ID("B") C := ID("C") - fmt.Println(And(A, Lit(true))) - fmt.Println(And(A, Lit(false))) + fmt.Println(And(A, Lit[string](true))) + fmt.Println(And(A, Lit[string](false))) fmt.Println(And(A, B, C)) fmt.Println(And(A, Not(B))) //Output: @@ -62,8 +62,8 @@ func ExampleOr() { A := ID("A") B := ID("B") - fmt.Println(Or(A, Lit(true))) - fmt.Println(Or(A, Lit(false))) + fmt.Println(Or(A, Lit[string](true))) + fmt.Println(Or(A, Lit[string](false))) fmt.Println(Or(A, Not(B))) //Output: // Lit(true) @@ -71,16 +71,16 @@ func ExampleOr() { // Or("A", Not("B")) } -func truthTester(t *testing.T, label string, z Expr, expected bool) { - if !z.Is(expected) { +func truthTester(t *testing.T, label string, z Expr[string], expected bool) { + if !z.isLiteral(expected) { t.Errorf("%s: expected %v got %v", label, expected, z) } } func Test_truthTables(t *testing.T) { - T := Lit(true) - F := Lit(false) + T := Lit[string](true) + F := Lit[string](false) //Not truthTester(t, "Not(F)", Not(F), true) diff --git a/quine-mccluskey.go b/quine-mccluskey.go index 0d0eb98..442a709 100644 --- a/quine-mccluskey.go +++ b/quine-mccluskey.go @@ -2,15 +2,15 @@ package boolgebra // Quine-McCluskey is an algorithm to simplify a sum of prod. -//reduce combine together all minterms of x into prime implicants -func reduce(x expression) expression { +// Simplify returns a simpler version of 'x' by applying simplification rules. +func Simplify[T comparable](x Expr[T]) Expr[T] { // to reduce we need to cluster minterms of x into number of non neg ID - var cluster [][]minterm // index minterm by their 1s. store them in a slice - var primes []minterm // also keep primes all together + var cluster [][]Term[T] // index minterm by their 1s. store them in a slice + var primes []Term[T] // also keep primes all together // fill the first cluster - cluster = make([][]minterm, 1+len(x.IDs())) + cluster = make([][]Term[T], 1+len(x.IDs())) for _, m := range x { ones := positives(m) cluster[ones] = append(cluster[ones], m) @@ -20,7 +20,7 @@ func reduce(x expression) expression { for !emptycluster { // the next cluster will become the current one soon, so we already set the bool to true, because we start with an empty one emptycluster = true // we start with an empty next one, let see if it get filled - next := make([][]minterm, len(cluster)) + next := make([][]Term[T], len(cluster)) // attempt all possible combinations. // a minterm with n Positive IDs, can only be combined with another one with n or n+1 ( or n-1 but combination is symetric so we don't care) @@ -94,12 +94,12 @@ func reduce(x expression) expression { cluster = next } //done, we now have all the prime implicant - return expression(primes) + return Expr[T](primes) } // appenunique behave like 'append' except for items in 'terms' that are present in 'set': they // are not appended in this case. -func appendunique(set []minterm, terms ...minterm) []minterm { +func appendunique[T comparable](set []Term[T], terms ...Term[T]) []Term[T] { termsloop: for _, m := range terms { for _, x := range set { @@ -118,11 +118,11 @@ termsloop: // x and y must be identical but on exactly one identifier. // // the combined is then then intersection of x and y. -func combine(x, y minterm) (c minterm, ok bool) { +func combine[T comparable](x, y Term[T]) (c Term[T], ok bool) { // alg: find out the one and only one difference between x,y // so scan for differences and count. - var d string // the identifier that is different (if diffs == 1)) - diffs := 0 // number of differences + var d T // the identifier that is different (if diffs == 1)) + diffs := 0 // number of differences for k, v := range x { w, exists := y[k] if !exists || v != w { @@ -154,7 +154,7 @@ func combine(x, y minterm) (c minterm, ok bool) { // build c accordingly then // x and y are guaranteed to be identical but on 'd' // so copy x but 'd' - c = make(minterm) + c = make(Term[T]) for k, v := range x { if k != d { c[k] = v @@ -165,7 +165,7 @@ func combine(x, y minterm) (c minterm, ok bool) { } // equals return true if and only if m and n are both minterm, then they are semantically equals -func equals(m, n minterm) bool { +func equals[T comparable](m, n Term[T]) bool { if len(m) != len(n) { return false } @@ -178,7 +178,7 @@ func equals(m, n minterm) bool { } // positives returns the number of positive identifiers -func positives(m minterm) int { +func positives[T comparable](m Term[T]) int { count := 0 for _, v := range m { if v { @@ -188,9 +188,9 @@ func positives(m minterm) int { return count } -//inter computes the intersection of x inter y -func inter(x, y minterm) minterm { - res := make(minterm) +// inter computes the intersection of x inter y +func inter[T comparable](x, y Term[T]) Term[T] { + res := make(Term[T]) for k, v := range x { if w, exists := y[k]; exists && v == w { res[k] = v @@ -200,10 +200,10 @@ func inter(x, y minterm) minterm { } -//div computes x/y i.e z so that And(z,y) = x +// div computes x/y i.e z so that And(z,y) = x // can be seen as x removed from items in y -func div(x, y minterm) minterm { - res := make(minterm) +func div[T comparable](x, y Term[T]) Term[T] { + res := make(Term[T]) for k, v := range x { if w, exists := y[k]; !exists || v != w { res[k] = v diff --git a/quine-mccluskey_test.go b/quine-mccluskey_test.go index db023a9..265e4a4 100644 --- a/quine-mccluskey_test.go +++ b/quine-mccluskey_test.go @@ -2,7 +2,7 @@ package boolgebra import "testing" -//TestReduce check that we get the prime correctly using the wikipedia examples +// TestReduce check that we get the prime correctly using the wikipedia examples func TestReduce(t *testing.T) { /* Number @@ -28,20 +28,20 @@ func TestReduce(t *testing.T) { m11 := m("ab'cd") m14 := m("abcd'") m15 := m("abcd") - x := expression{m4, m8, m9, m10, m12, m11, m14, m15} + x := Expr[string]{m4, m8, m9, m10, m12, m11, m14, m15} //primes are //m4_12 := m("bc'd'") //m8_9_10_11 := m("ab'") //m8_10_12_14 := m("ad'") //m10_11_14_15 := m("ac") - y := reduce(x) + y := Simplify(x) t.Logf("reduced = %v", y) } -//newminterm creates a new minterm using ' at the end of the string to find out that its a neg -func m(x string) minterm { - res := make(minterm) +// newminterm creates a new minterm using ' at the end of the string to find out that its a neg +func m(x string) Term[string] { + res := make(Term[string]) for i, id := range x { nextisquote := i+1 < len(x) && x[i+1] == '\'' @@ -54,7 +54,7 @@ func m(x string) minterm { // TestPosLen make sure that we count the number of true correctly func TestPositives(t *testing.T) { - x := minterm{"A": true, "B": true, "C": false, "E": true} + x := Term[string]{"A": true, "B": true, "C": false, "E": true} if positives(x) != 3 { t.Errorf("invalid minterm %v PosLen attribute got %v want 3", x, positives(x)) } @@ -63,13 +63,13 @@ func TestPositives(t *testing.T) { // TestMinterm_combine gold test some minterm combinations func TestCombine(t *testing.T) { - x := minterm{"A": true, "B": true, "C": false, "E": true} - var y, r, c minterm + x := Term[string]{"A": true, "B": true, "C": false, "E": true} + var y, r, c Term[string] var ok bool // 1,0 -> _ - y = minterm{"A": true, "B": true, "C": false, "E": false} - r = minterm{"A": true, "B": true, "C": false} + y = Term[string]{"A": true, "B": true, "C": false, "E": false} + r = Term[string]{"A": true, "B": true, "C": false} c, ok = combine(x, y) if !ok { t.Errorf("failed to combine(%v,%v)", x, y) @@ -78,8 +78,8 @@ func TestCombine(t *testing.T) { } // 1,_ -> _ - y = minterm{"A": true, "B": true, "C": false} - r = minterm{"A": true, "B": true, "C": false} + y = Term[string]{"A": true, "B": true, "C": false} + r = Term[string]{"A": true, "B": true, "C": false} c, ok = combine(x, y) if !ok { t.Errorf("failed to combine(%v,%v)", x, y) @@ -88,8 +88,8 @@ func TestCombine(t *testing.T) { } // 0,1 -> _ - y = minterm{"A": true, "B": true, "C": true, "E": true} - r = minterm{"A": true, "B": true, "E": true} + y = Term[string]{"A": true, "B": true, "C": true, "E": true} + r = Term[string]{"A": true, "B": true, "E": true} c, ok = combine(x, y) if !ok { t.Errorf("failed to combine(%v,%v)", x, y) @@ -98,8 +98,8 @@ func TestCombine(t *testing.T) { } // 0,_ -> _ - y = minterm{"A": true, "B": true, "E": true} - r = minterm{"A": true, "B": true, "E": true} + y = Term[string]{"A": true, "B": true, "E": true} + r = Term[string]{"A": true, "B": true, "E": true} c, ok = combine(x, y) if !ok { t.Errorf("failed to combine(%v,%v)", x, y) diff --git a/secondary.go b/secondary.go index 637757d..fc6f68d 100644 --- a/secondary.go +++ b/secondary.go @@ -6,7 +6,7 @@ package boolgebra // Impl returns the logical implication of A,B // // see https://en.wikipedia.org/wiki/Boolean_algebra#Secondary_operations -func Impl(A, B Expr) Expr { +func Impl[T comparable](A, B Expr[T]) Expr[T] { return Or(Not(A), B) } @@ -15,16 +15,16 @@ func Impl(A, B Expr) Expr { // It can also be the logical equivalence A <=> B. Both are in fact the same boolean function. // // see https://en.wikipedia.org/wiki/Boolean_algebra#Secondary_operations -func Eq(A, B Expr) Expr { return Or(And(Not(A), Not(B)), And(A, B)) } +func Eq[T comparable](A, B Expr[T]) Expr[T] { return Or(And(Not(A), Not(B)), And(A, B)) } // Xor returns the logical Xor // // see https://en.wikipedia.org/wiki/Boolean_algebra#Secondary_operations -func Xor(A, B Expr) Expr { return Or(And(A, Not(B)), And(Not(A), B)) } +func Xor[T comparable](A, B Expr[T]) Expr[T] { return Or(And(A, Not(B)), And(Not(A), B)) } // Neq returns the logical '!=' // // It is the same as Xor. // // see https://en.wikipedia.org/wiki/Boolean_algebra#Secondary_operations -func Neq(A, B Expr) Expr { return Xor(A, B) } +func Neq[T comparable](A, B Expr[T]) Expr[T] { return Xor(A, B) } diff --git a/secondary_test.go b/secondary_test.go index 2d48dbd..56bf263 100644 --- a/secondary_test.go +++ b/secondary_test.go @@ -4,8 +4,8 @@ import "testing" func Test_truthTables2(t *testing.T) { - T := Lit(true) - F := Lit(false) + T := Lit[string](true) + F := Lit[string](false) //XOr truthTester(t, "Xor(F,F)", Xor(F, F), false) diff --git a/smullyan/counting.go b/smullyan/counting.go index d20e69b..a5c8d6f 100644 --- a/smullyan/counting.go +++ b/smullyan/counting.go @@ -1,29 +1,31 @@ package smullyan -import "github.com/etnz/permute" -import . "github.com/etnz/boolgebra" +import ( + . "github.com/etnz/boolgebra" + "github.com/etnz/permute" +) //counting.go holds the function relative to counting, like Exactly or AtMost // Exactly retuns an expression that is true if and only if exactly 'i' terms are True. // // This is the Or() of and And() of all the i-subsets of terms -func Exactly(i int, terms ...Expr) Expr { - return quantified(i, identity, Not, terms...) +func Exactly[T comparable](i int, terms ...Expr[T]) Expr[T] { + return quantified(i, identity[T], Not[T], terms...) } // AtMost retuns an expression that is true if and only if at most 'i' terms are True. -func AtMost(i int, terms ...Expr) Expr { - return quantified(i, truth, Not, terms...) +func AtMost[T comparable](i int, terms ...Expr[T]) Expr[T] { + return quantified(i, truth[T], Not[T], terms...) } // AtLeast retuns an expression that is true if and only if at least 'i' terms are True. -func AtLeast(i int, terms ...Expr) Expr { - return quantified(i, identity, truth, terms...) +func AtLeast[T comparable](i int, terms ...Expr[T]) Expr[T] { + return quantified(i, identity[T], truth[T], terms...) } -func identity(x Expr) Expr { return x } -func truth(x Expr) Expr { return Lit(true) } +func identity[T comparable](x Expr[T]) Expr[T] { return x } +func truth[T comparable](x Expr[T]) Expr[T] { return Lit[T](true) } // quantifier returns Or ( And( f(p,p')) )where: // p is a subset of terms, p' is the complement ( remain terms) @@ -33,13 +35,13 @@ func truth(x Expr) Expr { return Lit(true) } // - for Atmost: we just need to build those with p' // // therefore we defined two simple function fp(Expr) and fp' accordingly -func quantified(i int, f, g func(x Expr) Expr, terms ...Expr) Expr { +func quantified[T comparable](i int, f, g func(x Expr[T]) Expr[T], terms ...Expr[T]) Expr[T] { - p := subid(i) // the slice of indices starting at identify, always - var ors []Expr // all ands to be Or()ed + p := subid(i) // the slice of indices starting at identify, always + var ors []Expr[T] // all ands to be Or()ed for ok := true; ok; ok = nextsubset(p, len(terms)) { - res := make([]Expr, 0, len(terms)) + res := make([]Expr[T], 0, len(terms)) c := complement(p, len(terms)) for _, i := range p { diff --git a/types.go b/types.go index d18d743..ad4d80e 100644 --- a/types.go +++ b/types.go @@ -1,41 +1,30 @@ package boolgebra import ( + "fmt" "sort" "strconv" "strings" ) type ( - // expression is boolean algebra expression as a sum of prod of minterms. + // Expr is boolean algebra Expr as a sum of prod of minterms. // As such it is a slice of minterms. It must be considered as a set // - // an empty expression is always false ( and this is the definition of false + // an empty Expr is always false ( and this is the definition of false // - expression []minterm + Expr[T comparable] []Term[T] - //minterm is a product of identifier or their negation. For instance - // mintern "AB'D" <=> "A or Not(B) or D" is coded as minterm{ "A":true, "B":false, "D":true} + //Term is a product of identifiers or their negation. For instance + // mintern "AB'D" <=> "A or Not(B) or D" is coded as Term{ "A":true, "B":false, "D":true} // - // it is conventional that https://en.wikipedia.org/wiki/Empty_product the empty minterm is 1 the neutral for prod ( and for and too) + // it is conventional that https://en.wikipedia.org/wiki/Empty_product the empty Term is 1 the neutral for prod ( and for and too) // - minterm map[string]bool - - // Expr is the interface that all elements of a boolean algebra share - Expr interface { - String() string - // return the negation of receiver - Not() Expr - // Is return true if the Expr is literally equals to value - Is(val bool) bool - Terms() int - Term(i int) Expr - IDs() (ids map[string]struct{}) - } + Term[T comparable] map[T]bool ) // String return the literal representation (using primary functions) of the current expression. -func (x expression) String() string { +func (x Expr[T]) String() string { if len(x) == 0 { return "Lit(false)" } @@ -54,51 +43,75 @@ func (x expression) String() string { return "Or(" + strings.Join(terms, ", ") + ")" } -//String return the literal representation (using primary functions) of the current minterm -func (m minterm) String() string { +// String return the literal representation (using primary functions) of the current minterm +func (m Term[T]) String() string { if len(m) == 0 { return "Lit(true)" } - var terms []string + terms := make([]T, 0, len(m)) for k := range m { terms = append(terms, k) } - sort.Strings(terms) + var sTerms []string + switch s := any(terms).(type) { + case []string: + sort.Strings(s) + sTerms = s + case []int: + sort.Ints(s) + default: + sTerms = make([]string, len(terms)) + for i := range terms { + sTerms[i] = fmt.Sprintf("%v", terms[i]) + } + + sort.Slice(sTerms, func(i, j int) bool { + return sTerms[i] < sTerms[j] + }) + } + if sTerms != nil { + sTerms = make([]string, len(terms)) + for i := range terms { + sTerms[i] = fmt.Sprintf("%v", terms[i]) + } + } + for i, t := range terms { + s := sTerms[i] if !m[t] { - terms[i] = "Not(" + strconv.Quote(t) + ")" + sTerms[i] = "Not(" + strconv.Quote(s) + ")" } else { - terms[i] = strconv.Quote(t) + sTerms[i] = strconv.Quote(s) } } if len(terms) == 1 { - return terms[0] + return sTerms[0] } else { - return "And(" + strings.Join(terms, ", ") + ")" + return "And(" + strings.Join(sTerms, ", ") + ")" } } // NOT -func (x expression) Not() Expr { - factors := make([]Expr, 0, len(x)) +func (x Expr[T]) Not() Expr[T] { + factors := make([]Expr[T], 0, len(x)) for _, e := range x { factors = append(factors, e.Not()) } return And(factors...) } -func (m minterm) Not() Expr { - res := make(expression, 0, len(m)) +func (m Term[T]) Not() Expr[T] { + res := make(Expr[T], 0, len(m)) for k, v := range m { - res = append(res, minterm{string(k): !v}) + res = append(res, Term[T]{k: !v}) } return res } -//Is return true if this expression is equals to val -func (x expression) Is(val bool) bool { +// Is return true if this expression is equals to val +func (x Expr[T]) isLiteral(val bool) bool { if val { return len(x) == 1 && len(x[0]) == 0 } else { @@ -106,36 +119,14 @@ func (x expression) Is(val bool) bool { } } -//Is return true if this expression is equals to val -func (m minterm) Is(val bool) bool { +// Is return true if this expression is equals to val +func (m Term[T]) isLiteral(val bool) bool { return val && len(m) == 0 } -//Terms retuns the number of terms in this expression -func (x expression) Terms() int { return len(x) } - -//Terms retuns the number of terms in this expression -func (m minterm) Terms() int { return 1 } - -//Term retuns the ith terms. Panic if out of bounds ( negative, or >= Terms()) -func (x expression) Term(i int) Expr { - if i < 0 || i >= x.Terms() { - panic("Term is not defined for this index value") - } - return x[i] -} - -//Term retuns the ith terms. Panic if out of bounds ( negative, or >= Terms()) -func (m minterm) Term(i int) Expr { - if i != 0 { - panic("Term is not defined for this index value") - } - return m -} - // IDs return the set of ID in this expression -func (x expression) IDs() (ids map[string]struct{}) { - ids = make(map[string]struct{}) +func (x Expr[T]) IDs() (ids map[T]struct{}) { + ids = make(map[T]struct{}) for _, m := range x { for k := range m { ids[k] = struct{}{} @@ -145,8 +136,8 @@ func (x expression) IDs() (ids map[string]struct{}) { } // IDs return the set of ID in this expression -func (m minterm) IDs() (ids map[string]struct{}) { - ids = make(map[string]struct{}) +func (m Term[T]) IDs() (ids map[T]struct{}) { + ids = make(map[T]struct{}) for k := range m { ids[k] = struct{}{} }