From 9c875707afec561567b99299aa108ae4dcf20f3a Mon Sep 17 00:00:00 2001 From: lixiaohui Date: Sun, 15 May 2022 18:33:23 +0800 Subject: [PATCH] 1. add cmp.Ordering interface 2. add PartialOrd/Ord/PartialEq/Eq interface 3. add help functions --- cmp/cmp.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ cmp/cmp_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++ cmp/impl.go | 72 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 cmp/cmp.go create mode 100644 cmp/cmp_test.go create mode 100644 cmp/impl.go diff --git a/cmp/cmp.go b/cmp/cmp.go new file mode 100644 index 0000000..998b5be --- /dev/null +++ b/cmp/cmp.go @@ -0,0 +1,85 @@ +package cmp + +import ( + "fmt" + + "github.com/go-board/std/cond" + "github.com/go-board/std/optional" +) + +// private is a guard for Ordering safety. +// when Ordering using this, it can't be implemented out this package. +type private struct{} + +// Ordering is a type that represents the ordering of two values. +type Ordering interface { + ordering(private) + IsEq() bool + IsNe() bool + IsLt() bool + IsLe() bool + IsGt() bool + IsGe() bool + fmt.Stringer +} + +const ( + // Less is an Ordering where a compared value is less than another + Less = order(-1) + + // Equal is an Ordering where a compared value is equal to another + Equal = order(0) + + // Greater is an Ordering where a compared value is greater than another + Greater = order(1) +) + +type order int // -1, 0, 1 + +func (o order) ordering(private) {} +func (o order) IsEq() bool { return o == 0 } +func (o order) IsNe() bool { return o != 0 } +func (o order) IsLt() bool { return o < 0 } +func (o order) IsLe() bool { return o <= 0 } +func (o order) IsGe() bool { return o >= 0 } +func (o order) IsGt() bool { return o > 0 } +func (o order) String() string { + return cond.Ternary(o == 0, "Equal", cond.Ternary(o < 0, "Less", "Greater")) +} + +// PartialEq is a type that represents a partial equality comparison. +// see: [PartialEq](https://en.wikipedia.org/wiki/Partial_equivalence_relation) +type PartialEq[A any] interface { + Eq(A) bool + Ne(A) bool +} + +// Eq is a type that represents an equality comparison. +// see: [Eq](https://en.wikipedia.org/wiki/Equivalence_relation) +type Eq[A any] interface { + PartialEq[A] +} + +// PartialOrd is a type that represents a partially ordered value. +// see: [PartialOrd](https://en.wikipedia.org/wiki/Partially_ordered_set) +type PartialOrd[A any] interface { + PartialCmp(A) optional.Optional[Ordering] + Lt(A) bool + Le(A) bool + Gt(A) bool + Ge(A) bool +} + +// Ord is a type that represents an ordered value. +// see: [Ord](https://en.wikipedia.org/wiki/Total_order) +type Ord[A any] interface { + Eq[A] + PartialOrd[A] + Cmp(A) Ordering +} + +// OrdFunc is a function that returns an Ordering. +type OrdFunc[A any] func(lhs A, rhs A) Ordering + +// EqFunc is a function that returns a bool. +type EqFunc[A any] func(lhs A, rhs A) bool diff --git a/cmp/cmp_test.go b/cmp/cmp_test.go new file mode 100644 index 0000000..321d856 --- /dev/null +++ b/cmp/cmp_test.go @@ -0,0 +1,80 @@ +package cmp_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/go-board/std/cmp" + "github.com/go-board/std/slices" +) + +func TestOrdering(t *testing.T) { + c := qt.New(t) + c.Run("cmp", func(c *qt.C) { + c.Assert(cmp.Order(cmp.Less, cmp.Less), qt.Equals, cmp.Equal) + c.Assert(cmp.Order(cmp.Less, cmp.Equal), qt.Equals, cmp.Less) + c.Assert(cmp.Order(cmp.Less, cmp.Greater), qt.Equals, cmp.Less) + c.Assert(cmp.Order(cmp.Equal, cmp.Less), qt.Equals, cmp.Greater) + c.Assert(cmp.Order(cmp.Equal, cmp.Equal), qt.Equals, cmp.Equal) + c.Assert(cmp.Order(cmp.Equal, cmp.Greater), qt.Equals, cmp.Less) + c.Assert(cmp.Order(cmp.Greater, cmp.Less), qt.Equals, cmp.Greater) + c.Assert(cmp.Order(cmp.Greater, cmp.Equal), qt.Equals, cmp.Greater) + c.Assert(cmp.Order(cmp.Greater, cmp.Greater), qt.Equals, cmp.Equal) + }) + + c.Run("string", func(c *qt.C) { + c.Assert(cmp.Less.String(), qt.Equals, "Less") + c.Assert(cmp.Equal.String(), qt.Equals, "Equal") + c.Assert(cmp.Greater.String(), qt.Equals, "Greater") + }) +} + +type user struct { + Name string + Age int + Tags []string +} + +func (u user) Eq(rhs user) bool { + return u.Name == rhs.Name && u.Age == rhs.Age && slices.Equal(u.Tags, rhs.Tags) +} + +func (u user) Ne(rhs user) bool { + return !u.Eq(rhs) +} + +func TestCmp(t *testing.T) { + c := qt.New(t) + c.Run("max", func(c *qt.C) { + c.Run("ordered", func(c *qt.C) { + c.Assert(cmp.MaxOrdered(1, 2), qt.Equals, 2) + c.Assert(cmp.MaxOrdered(2, 1), qt.Equals, 2) + c.Assert(cmp.MaxOrdered(2, 2), qt.Equals, 2) + }) + c.Run("by", func(c *qt.C) { + userCmp := func(lhs, rhs user) cmp.Ordering { + if lhs.Name < rhs.Name { + return cmp.Less + } else if lhs.Name > rhs.Name { + return cmp.Greater + } else { + return cmp.Equal + } + } + lhs := user{Name: "a", Age: 1, Tags: []string{"a"}} + rhs := user{Name: "b", Age: 2, Tags: []string{"b"}} + c.Assert(cmp.MaxBy(userCmp, lhs, rhs), qt.DeepEquals, rhs) + c.Assert(cmp.MaxBy(userCmp, rhs, lhs), qt.DeepEquals, rhs) + }) + }) +} + +func TestEq(t *testing.T) { + c := qt.New(t) + c.Run("user", func(c *qt.C) { + lhs := user{Name: "a", Age: 1, Tags: []string{"a"}} + rhs := user{Name: "a", Age: 1, Tags: []string{"a"}} + c.Assert(lhs.Eq(rhs), qt.IsTrue) + c.Assert(lhs.Ne(rhs), qt.IsFalse) + }) +} diff --git a/cmp/impl.go b/cmp/impl.go new file mode 100644 index 0000000..46e7e92 --- /dev/null +++ b/cmp/impl.go @@ -0,0 +1,72 @@ +package cmp + +import ( + "github.com/go-board/std/cond" + "github.com/go-board/std/core" +) + +// MaxBy calculates the maximum value of two values by a given function. +func MaxBy[A any, F func(A, A) Ordering](compare F, lhs, rhs A) A { + return cond.Ternary(compare(lhs, rhs).IsGt(), lhs, rhs) +} + +// MaxByKey calculates the maximum value of two values by a key function. +func MaxByKey[A any, F func(A) K, K Ord[K]](f F, lhs, rhs A) A { + keyLhs, keyRhs := f(lhs), f(rhs) + return cond.Ternary(keyLhs.Cmp(keyRhs).IsGe(), lhs, rhs) +} + +// Max calculates the maximum value of two values. +func Max[A Ord[A]](lhs A, rhs A) A { + return MaxBy(func(lhs, rhs A) Ordering { return lhs.Cmp(rhs) }, lhs, rhs) +} + +// MaxOrdered calculates the maximum value of two ordered values. +func MaxOrdered[A core.Ordered](lhs, rhs A) A { + return cond.Ternary(lhs > rhs, lhs, rhs) +} + +// MinBy calculates the minimum value of two values by a given function. +func MinBy[A any, F func(A, A) Ordering](compare F, lhs, rhs A) A { + return cond.Ternary(compare(lhs, rhs).IsLt(), lhs, rhs) +} + +// MinByKey calculates the minimum value of two values by a key function. +func MinByKey[A any, F func(A) K, K Ord[K]](f F, lhs, rhs A) A { + keyLhs, keyRhs := f(lhs), f(rhs) + return cond.Ternary(keyLhs.Cmp(keyRhs).IsLt(), lhs, rhs) +} + +// Min calculates the minimum value of two values. +func Min[A Ord[A]](lhs, rhs A) A { + return MinBy(func(lhs, rhs A) Ordering { return lhs.Cmp(rhs) }, lhs, rhs) +} + +// MinOrdered calculates the minimum value of two ordered values. +func MinOrdered[A core.Ordered](lhs, rhs A) A { + return cond.Ternary(lhs < rhs, lhs, rhs) +} + +// EqByKey calculates the equality of two values by a key function. +func EqByKey[A any, F func(A) K, K Eq[K]](f F, lhs, rhs A) bool { + lhsKey, rhsKey := f(lhs), f(rhs) + return lhsKey.Eq(rhsKey) +} + +// CmpByKey calculates the comparison of two values by a key function. +func CmpByKey[A any, F func(A) K, K Ord[K]](f F, lhs, rhs A) Ordering { + lhsKey, rhsKey := f(lhs), f(rhs) + return lhsKey.Cmp(rhsKey) +} + +// Order is a function that returns an Ordering of two Ordering. +// result table is: +// | lhs\rhs | Less | Equal | Greater | +// |---------|---------|---------|---------+ +// | Less | Equal | Less | Less | +// | Equal | Greater | Equal | Less | +// | Greater | Greater | Greater | Equal | +func Order(lhs, rhs Ordering) Ordering { + lhsInner, rhsInner := lhs.(order), rhs.(order) + return cond.Ternary(lhsInner == rhsInner, Equal, cond.Ternary(lhsInner < rhsInner, Less, Greater)) +}