-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from go-board/feat/cmp
[cmp] add cmp package 1. add cmp.Ordering interface 2. add PartialOrd/Ord/PartialEq/Eq interface 3. add help functions
- Loading branch information
Showing
3 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
} |