bound: rewrite tests to use tables and fix unsorted input handling for Union
MODIFY
bound/bound.go
bound/bound.go
package bound
-import "math"
+import (
+ "math"
+ "sort"
+)
// Bound represents [Min, Max] bounds.
type Bound struct {
Min, Max float64
}
-// IsValid returns whether the bound is valid
+// IsValid returns whether the bound is valid. A valid bound will have
+// the minimum less than or equal to the maximum.
func (b Bound) IsValid() bool {
return b.Min <= b.Max
}
-// Intersection returns the intersection of the input bounds if possible.
-// Otherwise a NaN Bound is returned.
+// Intersection returns the intersection of the input bounds. If the
+// intersection is empty an invalid Bound is returned.
func Intersection(bounds ...Bound) Bound {
if len(bounds) == 0 {
return Bound{Min: math.NaN(), Max: math.NaN()}
@@ -37,11 +41,16 @@ func Intersection(bounds ...Bound) Bound {
}
// Union returns the contiguous union of the input bounds if possible.
-// Otherwise a NaN Bound is returned.
+// Otherwise an invalid Bound is returned. If bounds is a slice of Bound
+// and is not sorted, the order of elements will be changed so that
+// they are ordered ascending by Min.
func Union(bounds ...Bound) Bound {
if len(bounds) == 0 {
return Bound{Min: math.NaN(), Max: math.NaN()}
}
+ if len(bounds) > 1 && !sort.IsSorted(byMin(bounds)) {
+ sort.Sort(byMin(bounds))
+ }
union := Bound{Min: bounds[0].Min, Max: bounds[0].Max}
for _, b := range bounds[1:] {
@@ -58,3 +67,9 @@ func Union(bounds ...Bound) Bound {
return union
}
+
+type byMin []Bound
+
+func (b byMin) Len() int { return len(b) }
+func (b byMin) Less(i, j int) bool { return b[i].Min < b[j].Max }
+func (b byMin) Swap(i, j int) { b[i], b[j] = b[j], b[i] }

MODIFY
bound/bound_test.go
bound/bound_test.go
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// TODO: Rewrite these tests as table-driven.
-
package bound
import (
"math"
"testing"
-)
-
-func TestIntersection(t *testing.T) {
- b0 := Bound{Min: 0.0, Max: 5.0}
- b1 := Bound{Min: 3.0, Max: 4.0}
- b2 := Bound{Min: -1.0, Max: 1.0}
- b3 := Bound{Min: 6.0, Max: 8.0}
-
- ret := Intersection(b0)
- expected := Bound{Min: 0.0, Max: 5.0}
- if ret != expected {
- t.Errorf("Intersection(b0) should be %v but got %v", expected, ret)
- }
-
- ret = Intersection(b0, b1)
- expected = Bound{Min: 3.0, Max: 4.0}
- if ret != expected {
- t.Errorf("Intersection(b0, b1) should be %v but got %v", expected, ret)
- }
- ret = Intersection(b0, b2)
- expected = Bound{Min: 0.0, Max: 1.0}
- if ret != expected {
- t.Errorf("Intersection(b0, b2) should be %v but got %v", expected, ret)
- }
-
- ret = Intersection(b0, b3)
- if ret.IsValid() {
- t.Error("Intersection(b0, b3) should not be valid")
- }
-
- ret = Intersection(b0, b1, b2)
- if ret.IsValid() {
- t.Error("Intersection(b0, b1, b2) should not be valid")
- }
+ "gonum.org/v1/gonum/floats"
+)
- ret = Intersection()
- if ret.IsValid() {
- t.Error("Intersection() with zero input length should not be valid")
- }
+var isValidTests = []struct {
+ b Bound
+ want bool
+}{
+ {b: Bound{Min: math.Inf(-1), Max: 5}, want: true},
+ {b: Bound{Min: 0, Max: 5}, want: true},
+ {b: Bound{Min: 0, Max: math.Inf(1)}, want: true},
+ {b: Bound{Min: 5, Max: 0}, want: false},
+ {b: Bound{Min: math.NaN(), Max: 5}, want: false},
+ {b: Bound{Min: 5, Max: math.NaN()}, want: false},
+ {b: Bound{Min: math.NaN(), Max: math.NaN()}, want: false},
}
func TestIsValid(t *testing.T) {
- b0 := Bound{Min: 0.0, Max: 5.0}
- if !b0.IsValid() {
- t.Error("b0 is valid")
+ for _, test := range isValidTests {
+ got := test.b.IsValid()
+ if got != test.want {
+ t.Errorf("unexpected validity of %+v: got:%t want:%t", test.b, got, test.want)
+ }
+ }
+}
+
+var intersectionTests = []struct {
+ bounds []Bound
+ want Bound
+}{
+ {
+ bounds: []Bound{{Min: 0, Max: 5}},
+ want: Bound{Min: 0, Max: 5},
+ },
+ {
+ bounds: []Bound{{Min: 0, Max: 5}, {Min: 3, Max: 4}},
+ want: Bound{Min: 3, Max: 4},
+ },
+ {
+ bounds: []Bound{{Min: 0, Max: 5}, {Min: -1, Max: 1}},
+ want: Bound{Min: 0, Max: 1},
+ },
+ {
+ bounds: []Bound{{Min: 0, Max: 5}, {Min: 6, Max: 8}},
+ want: Bound{Min: math.NaN(), Max: math.NaN()},
+ },
+ {
+ bounds: []Bound{{Min: 0, Max: 5}, {Min: 3, Max: 4}, {Min: -1, Max: 1}},
+ want: Bound{Min: math.NaN(), Max: math.NaN()},
+ },
+ {
+ bounds: []Bound{},
+ want: Bound{Min: math.NaN(), Max: math.NaN()},
+ },
+ {
+ bounds: nil,
+ want: Bound{Min: math.NaN(), Max: math.NaN()},
+ },
}
- b1 := Bound{Min: 5.0, Max: 0.0}
- if b1.IsValid() {
- t.Error("b1 is invalid")
- }
-
- b2 := Bound{Min: math.NaN(), Max: 5.0}
- if b2.IsValid() {
- t.Error("b2 is invalid")
- }
-
- b3 := Bound{Min: 5.0, Max: math.NaN()}
- if b3.IsValid() {
- t.Error("b3 is invalid")
- }
-
- b4 := Bound{Min: math.NaN(), Max: math.NaN()}
- if b4.IsValid() {
- t.Error("b4 is invalid")
- }
+func TestIntersection(t *testing.T) {
+ for _, test := range intersectionTests {
+ got := Intersection(test.bounds...)
+ if !same(got, test.want) {
+ t.Errorf("unexpected result from Intersection(%#v...): got:%+v want:%+v", test.bounds, got, test.want)
+ }
+ }
+}
+
+var unionTests = []struct {
+ bounds []Bound
+ want Bound
+}{
+ {
+ bounds: []Bound{{Min: 0, Max: 5}},
+ want: Bound{Min: 0, Max: 5},
+ },
+ {
+ bounds: []Bound{{Min: 0, Max: 5}, {Min: 3, Max: 4}},
+ want: Bound{Min: 0, Max: 5},
+ },
+ {
+ bounds: []Bound{{Min: 0, Max: 5}, {Min: -1, Max: 1}},
+ want: Bound{Min: -1, Max: 5},
+ },
+ {
+ bounds: []Bound{{Min: -1, Max: 1}, {Min: 1, Max: 2}},
+ want: Bound{Min: -1, Max: 2},
+ },
+ {
+ bounds: []Bound{{Min: -1, Max: 1}, {Min: 1.1, Max: 2}},
+ want: Bound{Min: math.NaN(), Max: math.NaN()},
+ },
+ {
+ bounds: []Bound{{Min: 0, Max: 1}, {Min: 2, Max: 3}, {Min: 0.5, Max: 2.5}},
+ want: Bound{Min: 0, Max: 3},
+ },
+ {
+ bounds: []Bound{},
+ want: Bound{Min: math.NaN(), Max: math.NaN()},
+ },
+ {
+ bounds: nil,
+ want: Bound{Min: math.NaN(), Max: math.NaN()},
+ },
}
func TestUnion(t *testing.T) {
- b0 := Bound{Min: 0.0, Max: 5.0}
- b1 := Bound{Min: 3.0, Max: 4.0}
- b2 := Bound{Min: -1.0, Max: 1.0}
- b3 := Bound{Min: 1.0, Max: 2.0}
- b4 := Bound{Min: 1.1, Max: 2.0}
-
- ret := Union(b0)
- expected := Bound{Min: 0.0, Max: 5.0}
- if ret != expected {
- t.Errorf("Intersection(b0) should be %v but got %v", expected, ret)
+ for _, test := range unionTests {
+ got := Union(test.bounds...)
+ if !same(got, test.want) {
+ t.Errorf("unexpected result from Union(%#v...): got:%+v want:%+v", test.bounds, got, test.want)
}
-
- ret = Union(b0, b1)
- expected = Bound{Min: 0.0, Max: 5.0}
- if ret != expected {
- t.Errorf("Intersection(b0, b1) should be %v but got %v", expected, ret)
- }
-
- ret = Union(b0, b2)
- expected = Bound{Min: -1.0, Max: 5.0}
- if ret != expected {
- t.Errorf("Intersection(b0, b2) should be %v but got %v", expected, ret)
}
-
- ret = Union(b2, b3)
- expected = Bound{Min: -1.0, Max: 2.0}
- if ret != expected {
- t.Errorf("Intersection(b0, b2) should be %v but got %v", expected, ret)
}
- ret = Union(b2, b4)
- if ret.IsValid() {
- t.Errorf("Intersection(b2, b4) should be be valid but got %v", ret)
- }
-
- ret = Union()
- if ret.IsValid() {
- t.Error("Intersection() with zero input length should not be valid")
- }
+func same(a, b Bound) bool {
+ return floats.Same([]float64{a.Min, a.Max}, []float64{b.Min, b.Max})
}

bound: rewrite tests to use tables and fix unsorted input handling for Union