Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions builders.go
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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}
}
4 changes: 2 additions & 2 deletions builders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
34 changes: 22 additions & 12 deletions grid/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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{}{}
}
Expand Down Expand Up @@ -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))
}
}
}
Expand All @@ -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...))
Expand Down
12 changes: 6 additions & 6 deletions grid/samples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
80 changes: 32 additions & 48 deletions primary.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,47 @@ 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
}

// 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
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -109,48 +106,35 @@ 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
//
// x = \sum t_i \arrow f \and \sum r_i
//
// 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

}
Loading