Skip to content
Merged
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
33 changes: 29 additions & 4 deletions proto/tableaupb/wellknown_util.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package tableaupb

import "slices"

// NewFraction creates a new fraction.
func NewFraction(num, den int32) *Fraction {
return &Fraction{Num: num, Den: den}
Expand Down Expand Up @@ -29,13 +31,18 @@ func NewIntegerComparator(sign Comparator_Sign, num int32) *Comparator {
}

// Compare returns true if the given fraction matches the given comparator.
//
// Deprecated: Use [Fraction.Match] instead.
func Compare(left *Fraction, cmp *Comparator) bool {
return left.Cmp(cmp)
return left.Match(cmp)
}

func (f *Fraction) Cmp(cmp *Comparator) bool {
// Match reports whether the fraction satisfies the comparator's
// relational condition (==, !=, <, <=, >, >=). Comparison is done by
// cross-multiplication to stay in exact integer arithmetic for performance
// and accuracy.
func (f *Fraction) Match(cmp *Comparator) bool {
other := cmp.GetValue()
// cross-multiply to compare
lval := int64(f.GetNum()) * int64(other.GetDen())
rval := int64(other.GetNum()) * int64(f.GetDen())
switch cmp.GetSign() {
Expand All @@ -52,6 +59,24 @@ func (f *Fraction) Cmp(cmp *Comparator) bool {
case Comparator_SIGN_GREATER_OR_EQUAL:
return lval >= rval
default:
panic("invalid compare operator")
// Unknown sign is treated as no-match; proto enums are open-ended (forward-compat), not a bug.
return false
}
}

// MatchAny reports whether the fraction matches any of the given comparators (OR logic).
// It returns false if no comparators are provided.
func (f *Fraction) MatchAny(cmps ...*Comparator) bool {
return slices.ContainsFunc(cmps, f.Match)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please align MatchAny with MatchAll by using an explicit loop instead of slices.ContainsFunc. This keeps the two helpers stylistically consistent and lets us drop the now-unused slices import.

Suggested change
return slices.ContainsFunc(cmps, f.Match)
func (f *Fraction) MatchAny(cmps ...*Comparator) bool {
for _, cmp := range cmps {
if f.Match(cmp) {
return true
}
}
return false
}

Also remove import "slices".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// MatchAll reports whether the fraction matches all of the given comparators (AND logic).
// It returns true if no comparators are provided (vacuous truth).
func (f *Fraction) MatchAll(cmps ...*Comparator) bool {
for _, cmp := range cmps {
if !f.Match(cmp) {
return false
}
}
return true
}
99 changes: 96 additions & 3 deletions proto/tableaupb/wellknown_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"testing"
)

func TestCompare(t *testing.T) {
func TestMatch(t *testing.T) {
type args struct {
left *Fraction
cmp *Comparator
Expand Down Expand Up @@ -71,11 +71,104 @@ func TestCompare(t *testing.T) {
},
want: true,
},
{
name: "unknown sign",
args: args{
left: NewFraction(1, 2),
cmp: NewComparator(Comparator_Sign(42), 1, 2), // unrecognized sign → no match
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.args.left.Match(tt.args.cmp); got != tt.want {
t.Errorf("Match() = %v, want %v", got, tt.want)
}
})
}
}

func TestMatchAny(t *testing.T) {
left := NewFraction(1, 2)
tests := []struct {
name string
cmps []*Comparator
want bool
}{
{
name: "empty",
cmps: nil,
want: false,
},
{
name: "any match (first)",
cmps: []*Comparator{
NewComparator(Comparator_SIGN_EQUAL, 1, 2), // 1/2 == 1/2 → true
NewComparator(Comparator_SIGN_LESS, 1, 4), // 1/2 < 1/4 → false
},
want: true,
},
{
name: "any match (last)",
cmps: []*Comparator{
NewComparator(Comparator_SIGN_LESS, 1, 4), // 1/2 < 1/4 → false
NewComparator(Comparator_SIGN_GREATER, 1, 4), // 1/2 > 1/4 → true
},
want: true,
},
{
name: "no match",
cmps: []*Comparator{
NewComparator(Comparator_SIGN_LESS, 1, 4), // 1/2 < 1/4 → false
NewComparator(Comparator_SIGN_GREATER_OR_EQUAL, 3, 4), // 1/2 >= 3/4 → false
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := left.MatchAny(tt.cmps...); got != tt.want {
t.Errorf("MatchAny() = %v, want %v", got, tt.want)
}
})
}
}

func TestMatchAll(t *testing.T) {
left := NewFraction(1, 2)
tests := []struct {
name string
cmps []*Comparator
want bool
}{
{
name: "empty (vacuous truth)",
cmps: nil,
want: true,
},
{
name: "all match",
cmps: []*Comparator{
NewComparator(Comparator_SIGN_EQUAL, 2, 4), // 1/2 == 2/4 → true
NewComparator(Comparator_SIGN_GREATER, 1, 4), // 1/2 > 1/4 → true
NewComparator(Comparator_SIGN_LESS_OR_EQUAL, 3, 4), // 1/2 <= 3/4 → true
},
want: true,
},
{
name: "one fails",
cmps: []*Comparator{
NewComparator(Comparator_SIGN_EQUAL, 2, 4), // 1/2 == 2/4 → true
NewComparator(Comparator_SIGN_LESS, 1, 4), // 1/2 < 1/4 → false
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Compare(tt.args.left, tt.args.cmp); got != tt.want {
t.Errorf("Compare() = %v, want %v", got, tt.want)
if got := left.MatchAll(tt.cmps...); got != tt.want {
t.Errorf("MatchAll() = %v, want %v", got, tt.want)
}
})
}
Expand Down