diff --git a/function/predicate.go b/function/predicate.go index c503b43..362ac28 100644 --- a/function/predicate.go +++ b/function/predicate.go @@ -3,6 +3,9 @@ package function // And returns a composed predicate that represents the logical AND of a list of predicates. // It evaluates to true only if all predicates evaluate to true for the given value. func And[T any](predicates ...func(T) bool) func(T) bool { + if len(predicates) < 2 { + panic("programming error: predicates count must be at least 2") + } return func(value T) bool { for _, predicate := range predicates { if !predicate(value) { @@ -23,6 +26,9 @@ func Negate[T any](predicate func(T) bool) func(T) bool { // Or returns a composed predicate that represents the logical OR of a list of predicates. // It evaluates to true if at least one of the predicates evaluates to true for the given value. func Or[T any](predicates ...func(T) bool) func(T) bool { + if len(predicates) < 2 { + panic("programming error: predicates count must be at least 2") + } return func(value T) bool { for _, predicate := range predicates { if predicate(value) { @@ -36,6 +42,9 @@ func Or[T any](predicates ...func(T) bool) func(T) bool { // Nor returns a composed predicate that represents the logical NOR of a list of predicates. // It evaluates to true only if all predicates evaluate to false for the given value. func Nor[T any](predicates ...func(T) bool) func(T) bool { + if len(predicates) < 2 { + panic("programming error: predicates count must be at least 2") + } return func(value T) bool { for _, predicate := range predicates { if predicate(value) { @@ -45,3 +54,22 @@ func Nor[T any](predicates ...func(T) bool) func(T) bool { return true // Only returns true if all predicates evaluate to false } } + +// Xnor returns a composed predicate that represents the logical XNOR of a list of predicates. +// It evaluates to true only if all predicates evaluate to true or false for the given value. +func Xnor[T any](predicates ...func(T) bool) func(T) bool { + if len(predicates) < 2 { + panic("programming error: predicates count must be at least 2") + } + return func(value T) bool { + trueCount := 0 + for _, predicate := range predicates { + if predicate(value) { + trueCount++ + } + } + // XNOR is true if either all predicates are true or all are false + // This is the same as saying trueCount is 0 (all false) or trueCount is len(predicates) (all true) + return trueCount == 0 || trueCount == len(predicates) + } +} diff --git a/function/predicate_example_test.go b/function/predicate_example_test.go index 9a15ec8..b1e43ab 100644 --- a/function/predicate_example_test.go +++ b/function/predicate_example_test.go @@ -73,6 +73,22 @@ func ExampleNor() { // false } +func ExampleXnor() { + isEven := func(i int) bool { return i%2 == 0 } + isPositive := func(i int) bool { return i > 0 } + + match := Xnor(isEven, isPositive) + + fmt.Println(match(2)) + fmt.Println(match(-3)) + fmt.Println(match(3)) + + // Output: + // true + // true + // false +} + // func ExamplePredicatesMix() { // a := Or( // func(s string) bool { return strings.ContainsAny(s, "0123456789") }, diff --git a/function/predicate_test.go b/function/predicate_test.go index b0154d6..7e94d7c 100644 --- a/function/predicate_test.go +++ b/function/predicate_test.go @@ -74,6 +74,21 @@ func TestPredicatesNorPure(t *testing.T) { assert.ShouldBeFalse(match("0123456789")) } +func TestPredicatesXnorPure(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestPredicatesXnorPure") + + isEven := func(i int) bool { return i%2 == 0 } + isPositive := func(i int) bool { return i > 0 } + + match := Xnor(isEven, isPositive) + + assert.ShouldBeTrue(match(2)) + assert.ShouldBeTrue(match(-3)) + assert.ShouldBeFalse(match(3)) +} + func TestPredicatesMix(t *testing.T) { t.Parallel() @@ -95,4 +110,7 @@ func TestPredicatesMix(t *testing.T) { c = Nor(a, b) assert.ShouldBeFalse(c("hello!")) + + c = Xnor(a, b) + assert.ShouldBeTrue(c("hello!")) }