Skip to content

Commit

Permalink
Merge pull request #3 from go-board/feat/cmp
Browse files Browse the repository at this point in the history
[cmp] add cmp package
1. add cmp.Ordering interface
2. add PartialOrd/Ord/PartialEq/Eq interface
3. add help functions
  • Loading branch information
leaxoy authored May 15, 2022
2 parents 9235e68 + 9c87570 commit 76fae82
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 0 deletions.
85 changes: 85 additions & 0 deletions cmp/cmp.go
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
80 changes: 80 additions & 0 deletions cmp/cmp_test.go
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)
})
}
72 changes: 72 additions & 0 deletions cmp/impl.go
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))
}

0 comments on commit 76fae82

Please sign in to comment.