From 2d45c1302ca56ac8e912e88e3b91b849c0c4450d Mon Sep 17 00:00:00 2001 From: Bob Glickstein Date: Sun, 13 Aug 2023 12:05:56 -0700 Subject: [PATCH] v3: Track changes in Go stdlib (#8) * Adapt to changes in exp/slices. * Implement in terms of new slices, maps, and cmp packages; require Go 1.21. * Remove x/exp dependency. * Require Go 1.21 in CI. --- .github/workflows/go.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- go.mod | 7 +- go.sum | 2 - iter/example_test.go | 2 +- iter/n.go | 2 +- maps/dropin.go | 21 +--- maps/dropin_test.go | 2 +- maps/maps.go | 29 ++++- maps/maps_test.go | 2 +- parallel/example_test.go | 2 +- parallel/parallel.go | 2 +- parallel/parallel_test.go | 2 +- set/example_test.go | 2 +- set/set.go | 4 +- slices/combinatorics.go | 2 +- slices/combinatorics_test.go | 2 +- slices/dropin.go | 157 +++++++++++++++------------- slices/dropin_test.go | 53 ++++------ slices/example_test.go | 2 +- slices/reverse.go | 8 -- slices/slices.go | 33 +++++- 22 files changed, 183 insertions(+), 157 deletions(-) delete mode 100644 slices/reverse.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 009004b..9202232 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18 + go-version: 1.21 - name: Unit tests run: go test -v -coverprofile=cover.out ./... diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index c35dd89..096cc03 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: '1.18' + go-version: '1.21' - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 diff --git a/go.mod b/go.mod index c39af09..1d664f9 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,8 @@ -module github.com/bobg/go-generics/v2 +module github.com/bobg/go-generics/v3 -go 1.18 +go 1.21 require ( github.com/mattn/go-sqlite3 v1.14.12 - golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) - -retract v2.0.0 diff --git a/go.sum b/go.sum index 0675c09..fc0f3e1 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= -golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/iter/example_test.go b/iter/example_test.go index 61c8ca4..da4bbae 100644 --- a/iter/example_test.go +++ b/iter/example_test.go @@ -3,7 +3,7 @@ package iter_test import ( "fmt" - "github.com/bobg/go-generics/v2/iter" + "github.com/bobg/go-generics/v3/iter" ) func ExampleAccum() { diff --git a/iter/n.go b/iter/n.go index b70f9a2..db47db7 100644 --- a/iter/n.go +++ b/iter/n.go @@ -1,6 +1,6 @@ package iter -import "github.com/bobg/go-generics/v2/internal" +import "github.com/bobg/go-generics/v3/internal" // FirstN produces an iterator containing the first n elements of the input // (or all of the input, if there are fewer than n elements). diff --git a/maps/dropin.go b/maps/dropin.go index 3090d48..232cc81 100644 --- a/maps/dropin.go +++ b/maps/dropin.go @@ -1,20 +1,8 @@ package maps -import "golang.org/x/exp/maps" +import "maps" -// This file contains entrypoints for each of the functions in in golang.org/x/exp/maps. - -// Keys returns the keys of the map m. -// The keys will be in an indeterminate order. -func Keys[M ~map[K]V, K comparable, V any](m M) []K { - return maps.Keys(m) -} - -// Values returns the values of the map m. -// The values will be in an indeterminate order. -func Values[M ~map[K]V, K comparable, V any](m M) []V { - return maps.Values(m) -} +// This file contains entrypoints for each of the functions in the standard Go maps package. // Equal reports whether two maps contain the same key/value pairs. // Values are compared using ==. @@ -28,11 +16,6 @@ func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M return maps.EqualFunc(m1, m2, eq) } -// Clear removes all entries from m, leaving it empty. -func Clear[M ~map[K]V, K comparable, V any](m M) { - maps.Clear(m) -} - // Clone returns a copy of m. This is a shallow clone: // the new keys and values are set using ordinary assignment. func Clone[M ~map[K]V, K comparable, V any](m M) M { diff --git a/maps/dropin_test.go b/maps/dropin_test.go index 1297e69..61c4cc9 100644 --- a/maps/dropin_test.go +++ b/maps/dropin_test.go @@ -12,7 +12,7 @@ import ( "strconv" "testing" - "github.com/bobg/go-generics/v2/slices" + "github.com/bobg/go-generics/v3/slices" ) var m1 = map[int]int{1: 2, 2: 4, 4: 8, 8: 16} diff --git a/maps/maps.go b/maps/maps.go index f2de7d5..342dc74 100644 --- a/maps/maps.go +++ b/maps/maps.go @@ -8,7 +8,7 @@ // in Go 1.21 (https://go.dev/doc/go1.21#maps). package maps -import "github.com/bobg/go-generics/v2/iter" +import "github.com/bobg/go-generics/v3/iter" // Each calls a function on each key-value pair in the given map. func Each[M ~map[K]V, K comparable, V any](m M, f func(K, V)) { @@ -63,3 +63,30 @@ func InvertMulti[M ~map[K]V, K, V comparable](m M) map[V][]K { } return result } + +// Keys returns the keys of the map m. +// The keys will be in an indeterminate order. +func Keys[M ~map[K]V, K comparable, V any](m M) []K { + result := make([]K, 0, len(m)) + for k := range m { + result = append(result, k) + } + return result +} + +// Values returns the values of the map m. +// The values will be in an indeterminate order. +func Values[M ~map[K]V, K comparable, V any](m M) []V { + result := make([]V, 0, len(m)) + for _, v := range m { + result = append(result, v) + } + return result +} + +// Clear removes all entries from m, leaving it empty. +func Clear[M ~map[K]V, K comparable, V any](m M) { + for k := range m { + delete(m, k) + } +} diff --git a/maps/maps_test.go b/maps/maps_test.go index ec61872..24b7853 100644 --- a/maps/maps_test.go +++ b/maps/maps_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/bobg/go-generics/v2/iter" + "github.com/bobg/go-generics/v3/iter" ) var ( diff --git a/parallel/example_test.go b/parallel/example_test.go index 1f19e16..ae4fc83 100644 --- a/parallel/example_test.go +++ b/parallel/example_test.go @@ -5,7 +5,7 @@ import ( "fmt" "sync" - "github.com/bobg/go-generics/v2/parallel" + "github.com/bobg/go-generics/v3/parallel" ) func ExampleConsumers() { diff --git a/parallel/parallel.go b/parallel/parallel.go index 69d164f..9097889 100644 --- a/parallel/parallel.go +++ b/parallel/parallel.go @@ -8,7 +8,7 @@ import ( "golang.org/x/sync/errgroup" - "github.com/bobg/go-generics/v2/iter" + "github.com/bobg/go-generics/v3/iter" ) // Error is an error type for wrapping errors returned from worker goroutines. diff --git a/parallel/parallel_test.go b/parallel/parallel_test.go index f60dd84..91bc4be 100644 --- a/parallel/parallel_test.go +++ b/parallel/parallel_test.go @@ -6,7 +6,7 @@ import ( "sync" "testing" - "github.com/bobg/go-generics/v2/set" + "github.com/bobg/go-generics/v3/set" ) func TestValues(t *testing.T) { diff --git a/set/example_test.go b/set/example_test.go index 1a64c45..8242571 100644 --- a/set/example_test.go +++ b/set/example_test.go @@ -3,7 +3,7 @@ package set_test import ( "fmt" - "github.com/bobg/go-generics/v2/set" + "github.com/bobg/go-generics/v3/set" ) func ExampleDiff() { diff --git a/set/set.go b/set/set.go index 9b8871d..12ce2fb 100644 --- a/set/set.go +++ b/set/set.go @@ -2,8 +2,8 @@ package set import ( - "github.com/bobg/go-generics/v2/iter" - "github.com/bobg/go-generics/v2/maps" + "github.com/bobg/go-generics/v3/iter" + "github.com/bobg/go-generics/v3/maps" ) // Of is a set of elements of type T. diff --git a/slices/combinatorics.go b/slices/combinatorics.go index 004aee2..6f25e38 100644 --- a/slices/combinatorics.go +++ b/slices/combinatorics.go @@ -1,7 +1,7 @@ package slices import ( - "github.com/bobg/go-generics/v2/iter" + "github.com/bobg/go-generics/v3/iter" ) // Permutations produces an iterator over all permutations of s. diff --git a/slices/combinatorics_test.go b/slices/combinatorics_test.go index 28bcd48..1b6631d 100644 --- a/slices/combinatorics_test.go +++ b/slices/combinatorics_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/bobg/go-generics/v2/iter" + "github.com/bobg/go-generics/v3/iter" ) func TestPermutations(t *testing.T) { diff --git a/slices/dropin.go b/slices/dropin.go index 0c3ae24..cee2ca7 100644 --- a/slices/dropin.go +++ b/slices/dropin.go @@ -1,11 +1,11 @@ package slices import ( - "golang.org/x/exp/constraints" - "golang.org/x/exp/slices" + "cmp" + "slices" ) -// This file contains entrypoints for each of the functions in in golang.org/x/exp/slices +// This file contains entrypoints for each of the functions in the standard Go slices package // (except those, like Insert, extended by other functions in this package). // Equal reports whether two slices are equal: the same length and all @@ -13,89 +13,70 @@ import ( // Otherwise, the elements are compared in increasing index order, and the // comparison stops at the first unequal pair. // Floating point NaNs are not considered equal. -func Equal[E comparable](s1, s2 []E) bool { +func Equal[S ~[]E, E comparable](s1, s2 S) bool { return slices.Equal(s1, s2) } -// EqualFunc reports whether two slices are equal using a comparison +// EqualFunc reports whether two slices are equal using an equality // function on each pair of elements. If the lengths are different, // EqualFunc returns false. Otherwise, the elements are compared in // increasing index order, and the comparison stops at the first index // for which eq returns false. -func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool { +func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool { return slices.EqualFunc(s1, s2, eq) } -// Compare compares the elements of s1 and s2. -// The elements are compared sequentially, starting at index 0, +// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair +// of elements. The elements are compared sequentially, starting at index 0, // until one element is not equal to the other. // The result of comparing the first non-matching elements is returned. // If both slices are equal until one of them ends, the shorter slice is // considered less than the longer one. // The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. -// Comparisons involving floating point NaNs are ignored. -func Compare[E constraints.Ordered](s1, s2 []E) int { +func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int { return slices.Compare(s1, s2) } -// CompareFunc is like Compare but uses a comparison function -// on each pair of elements. The elements are compared in increasing -// index order, and the comparisons stop after the first time cmp -// returns non-zero. +// CompareFunc is like [Compare] but uses a custom comparison function on each +// pair of elements. // The result is the first non-zero result of cmp; if cmp always // returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), // and +1 if len(s1) > len(s2). -func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { +func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { return slices.CompareFunc(s1, s2, cmp) } // Index returns the index of the first occurrence of v in s, // or -1 if not present. -func Index[E comparable](s []E, v E) int { +func Index[S ~[]E, E comparable](s S, v E) int { return slices.Index(s, v) } // IndexFunc returns the first index i satisfying f(s[i]), // or -1 if none do. -func IndexFunc[E any](s []E, f func(E) bool) int { +func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { return slices.IndexFunc(s, f) } // Contains reports whether v is present in s. -func Contains[E comparable](s []E, v E) bool { +func Contains[S ~[]E, E comparable](s S, v E) bool { return slices.Contains(s, v) } // ContainsFunc reports whether at least one // element e of s satisfies f(e). -func ContainsFunc[E any](s []E, f func(E) bool) bool { +func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool { return slices.ContainsFunc(s, f) } -// Delete removes the elements s[i:j] from s, returning the modified slice. -// Delete panics if s[i:j] is not a valid slice of s. -// Delete modifies the contents of the slice s; it does not create a new slice. -// Delete is O(len(s)-j), so if many items must be deleted, it is better to -// make a single call deleting them all together than to delete one at a time. -// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those -// elements contain pointers you might consider zeroing those elements so that -// objects they reference can be garbage collected. -// -// If i < 0 it counts from the end of s. -// If j <= 0 it counts from the end of s. -// (This is a change from the behavior of "golang.org/x/exp/slices".Delete.) -func Delete[S ~[]E, E any](s S, i, j int) S { - return RemoveTo(s, i, j) -} - -// Replace replaces the elements s[i:j] by the given v, and returns the -// modified slice. Replace panics if s[i:j] is not a valid slice of s. -// -// If i < 0 it counts from the end of s. -// If j <= 0 it counts from the end of s. -// (This is a change from the behavior of "golang.org/x/exp/slices".Replace.) -func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { - return ReplaceTo(s, i, j, v...) +// DeleteFunc removes any elements from s for which del returns true, +// returning the modified slice. +// When DeleteFunc removes m elements, it might not modify the elements +// s[len(s)-m:len(s)]. If those elements contain pointers you might consider +// zeroing those elements so that objects they reference can be garbage +// collected. +func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { + return slices.DeleteFunc(s, del) } // Clone returns a copy of the slice. @@ -106,7 +87,8 @@ func Clone[S ~[]E, E any](s S) S { // Compact replaces consecutive runs of equal elements with a single copy. // This is like the uniq command found on Unix. -// Compact modifies the contents of the slice s; it does not create a new slice. +// Compact modifies the contents of the slice s and returns the modified slice, +// which may have a smaller length. // When Compact discards m elements in total, it might not modify the elements // s[len(s)-m:len(s)]. If those elements contain pointers you might consider // zeroing those elements so that objects they reference can be garbage collected. @@ -114,7 +96,8 @@ func Compact[S ~[]E, E comparable](s S) S { return slices.Compact(s) } -// CompactFunc is like Compact but uses a comparison function. +// CompactFunc is like [Compact] but uses an equality function to compare elements. +// For runs of elements that compare equal, CompactFunc keeps the first one. func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { return slices.CompactFunc(s, eq) } @@ -132,54 +115,88 @@ func Clip[S ~[]E, E any](s S) S { return slices.Clip(s) } +// Reverse reverses the elements of the slice in place. +func Reverse[S ~[]E, E any](s S) { + slices.Reverse(s) +} + // Sort sorts a slice of any ordered type in ascending order. -// Sort may fail to sort correctly when sorting slices of floating-point -// numbers containing Not-a-number (NaN) values. -// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))}) -// instead if the input may contain NaNs. -func Sort[E constraints.Ordered](x []E) { +// When sorting floating-point numbers, NaNs are ordered before other values. +func Sort[S ~[]E, E cmp.Ordered](x S) { slices.Sort(x) } -// SortFunc sorts the slice x in ascending order as determined by the less function. -// This sort is not guaranteed to be stable. +// SortFunc sorts the slice x in ascending order as determined by the cmp +// function. This sort is not guaranteed to be stable. +// cmp(a, b) should return a negative number when a < b, a positive number when +// a > b and zero when a == b. // -// SortFunc requires that less is a strict weak ordering. +// SortFunc requires that cmp is a strict weak ordering. // See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. -func SortFunc[E any](x []E, less func(a, b E) bool) { - slices.SortFunc(x, less) +func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { + slices.SortFunc(x, cmp) } // SortStableFunc sorts the slice x while keeping the original order of equal -// elements, using less to compare elements. -func SortStableFunc[E any](x []E, less func(a, b E) bool) { - slices.SortStableFunc(x, less) +// elements, using cmp to compare elements in the same way as [SortFunc]. +func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { + slices.SortStableFunc(x, cmp) } // IsSorted reports whether x is sorted in ascending order. -func IsSorted[E constraints.Ordered](x []E) bool { +func IsSorted[S ~[]E, E cmp.Ordered](x S) bool { return slices.IsSorted(x) } -// IsSortedFunc reports whether x is sorted in ascending order, with less as the -// comparison function. -func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool { - return slices.IsSortedFunc(x, less) +// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the +// comparison function as defined by [SortFunc]. +func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool { + return slices.IsSortedFunc(x, cmp) +} + +// Min returns the minimal value in x. It panics if x is empty. +// For floating-point numbers, Min propagates NaNs (any NaN value in x +// forces the output to be NaN). +func Min[S ~[]E, E cmp.Ordered](x S) E { + return slices.Min(x) +} + +// MinFunc returns the minimal value in x, using cmp to compare elements. +// It panics if x is empty. If there is more than one minimal element +// according to the cmp function, MinFunc returns the first one. +func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { + return slices.MinFunc(x, cmp) +} + +// Max returns the maximal value in x. It panics if x is empty. +// For floating-point E, Max propagates NaNs (any NaN value in x +// forces the output to be NaN). +func Max[S ~[]E, E cmp.Ordered](x S) E { + return slices.Max(x) +} + +// MaxFunc returns the maximal value in x, using cmp to compare elements. +// It panics if x is empty. If there is more than one maximal element +// according to the cmp function, MaxFunc returns the first one. +func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { + return slices.MaxFunc(x, cmp) } // BinarySearch searches for target in a sorted slice and returns the position // where target is found, or the position where target would appear in the // sort order; it also returns a bool saying whether the target is really found // in the slice. The slice must be sorted in increasing order. -func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) { +func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) { return slices.BinarySearch(x, target) } -// BinarySearchFunc works like BinarySearch, but uses a custom comparison -// function. The slice must be sorted in increasing order, where "increasing" is -// defined by cmp. cmp(a, b) is expected to return an integer comparing the two -// parameters: 0 if a == b, a negative number if a < b and a positive number if -// a > b. -func BinarySearchFunc[E, T any](x []E, target T, cmp func(E, T) int) (int, bool) { +// BinarySearchFunc works like [BinarySearch], but uses a custom comparison +// function. The slice must be sorted in increasing order, where "increasing" +// is defined by cmp. cmp should return 0 if the slice element matches +// the target, a negative number if the slice element precedes the target, +// or a positive number if the slice element follows the target. +// cmp must implement the same ordering as the slice, such that if +// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice. +func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) { return slices.BinarySearchFunc(x, target, cmp) } diff --git a/slices/dropin_test.go b/slices/dropin_test.go index 9fc300c..ea30e55 100644 --- a/slices/dropin_test.go +++ b/slices/dropin_test.go @@ -7,14 +7,13 @@ package slices import ( + "cmp" "math" "math/rand" "sort" "strconv" "strings" "testing" - - "golang.org/x/exp/constraints" ) var raceEnabled bool @@ -89,7 +88,7 @@ func equalNaN[T comparable](v1, v2 T) bool { } // offByOne returns true if integers v1 and v2 differ by 1. -func offByOne[E constraints.Integer](v1, v2 E) bool { +func offByOne(v1, v2 int) bool { return v1 == v2+1 || v1 == v2-1 } @@ -110,10 +109,10 @@ func TestEqualFunc(t *testing.T) { s1 := []int{1, 2, 3} s2 := []int{2, 3, 4} - if EqualFunc(s1, s1, offByOne[int]) { + if EqualFunc(s1, s1, offByOne) { t.Errorf("EqualFunc(%v, %v, offByOne) = true, want false", s1, s1) } - if !EqualFunc(s1, s2, offByOne[int]) { + if !EqualFunc(s1, s2, offByOne) { t.Errorf("EqualFunc(%v, %v, offByOne) = false, want true", s1, s2) } @@ -179,12 +178,12 @@ var compareFloatTests = []struct { { []float64{1, math.NaN(), 3}, []float64{1, 2, math.NaN()}, - 0, + -1, }, { []float64{1, math.NaN(), 3, 4}, []float64{1, 2, math.NaN()}, - +1, + -1, }, } @@ -227,16 +226,6 @@ func equalToCmp[T comparable](eq func(T, T) bool) func(T, T) int { } } -func cmp[T constraints.Ordered](v1, v2 T) int { - if v1 < v2 { - return -1 - } else if v1 > v2 { - return 1 - } else { - return 0 - } -} - func TestCompareFunc(t *testing.T) { intWant := func(want bool) string { if want { @@ -256,19 +245,19 @@ func TestCompareFunc(t *testing.T) { } for _, test := range compareIntTests { - if got := CompareFunc(test.s1, test.s2, cmp[int]); got != test.want { - t.Errorf("CompareFunc(%v, %v, cmp[int]) = %d, want %d", test.s1, test.s2, got, test.want) + if got := CompareFunc(test.s1, test.s2, cmp.Compare); got != test.want { + t.Errorf("CompareFunc(%v, %v, cmp.Compare) = %d, want %d", test.s1, test.s2, got, test.want) } } for _, test := range compareFloatTests { - if got := CompareFunc(test.s1, test.s2, cmp[float64]); got != test.want { - t.Errorf("CompareFunc(%v, %v, cmp[float64]) = %d, want %d", test.s1, test.s2, got, test.want) + if got := CompareFunc(test.s1, test.s2, cmp.Compare); got != test.want { + t.Errorf("CompareFunc(%v, %v, cmp.Compare) = %d, want %d", test.s1, test.s2, got, test.want) } } s1 := []int{1, 2, 3} s2 := []int{2, 3, 4} - if got := CompareFunc(s1, s2, equalToCmp(offByOne[int])); got != 0 { + if got := CompareFunc(s1, s2, equalToCmp(offByOne)); got != 0 { t.Errorf("CompareFunc(%v, %v, offByOne) = %d, want 0", s1, s2, got) } @@ -724,7 +713,7 @@ func TestSortIntSlice(t *testing.T) { func TestSortFuncIntSlice(t *testing.T) { data := Clone(ints[:]) - SortFunc(data, func(a, b int) bool { return a < b }) + SortFunc(data, func(a, b int) int { return a - b }) if !IsSorted(data) { t.Errorf("sorted %v", ints) t.Errorf(" got %v", data) @@ -790,8 +779,8 @@ type intPair struct { type intPairs []intPair // Pairs compare on a only. -func intPairLess(x, y intPair) bool { - return x.a < y.a +func intPairCmp(x, y intPair) int { + return x.a - y.a } // Record initial order in B. @@ -829,12 +818,12 @@ func TestStability(t *testing.T) { for i := 0; i < len(data); i++ { data[i].a = rand.Intn(m) } - if IsSortedFunc(data, intPairLess) { + if IsSortedFunc(data, intPairCmp) { t.Fatalf("terrible rand.rand") } data.initB() - SortStableFunc(data, intPairLess) - if !IsSortedFunc(data, intPairLess) { + SortStableFunc(data, intPairCmp) + if !IsSortedFunc(data, intPairCmp) { t.Errorf("Stable didn't sort %d ints", n) } if !data.inOrder() { @@ -843,8 +832,8 @@ func TestStability(t *testing.T) { // already sorted data.initB() - SortStableFunc(data, intPairLess) - if !IsSortedFunc(data, intPairLess) { + SortStableFunc(data, intPairCmp) + if !IsSortedFunc(data, intPairCmp) { t.Errorf("Stable shuffled sorted %d ints (order)", n) } if !data.inOrder() { @@ -856,8 +845,8 @@ func TestStability(t *testing.T) { data[i].a = len(data) - i } data.initB() - SortStableFunc(data, intPairLess) - if !IsSortedFunc(data, intPairLess) { + SortStableFunc(data, intPairCmp) + if !IsSortedFunc(data, intPairCmp) { t.Errorf("Stable didn't sort %d ints", n) } if !data.inOrder() { diff --git a/slices/example_test.go b/slices/example_test.go index 8608f72..d1ae7a7 100644 --- a/slices/example_test.go +++ b/slices/example_test.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "github.com/bobg/go-generics/v2/slices" + "github.com/bobg/go-generics/v3/slices" ) func ExampleGet() { diff --git a/slices/reverse.go b/slices/reverse.go deleted file mode 100644 index 882b8b9..0000000 --- a/slices/reverse.go +++ /dev/null @@ -1,8 +0,0 @@ -package slices - -// Reverse reverses a slice in place. -func Reverse[S ~[]T, T any](s S) { - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] - } -} diff --git a/slices/slices.go b/slices/slices.go index e493de1..14b6463 100644 --- a/slices/slices.go +++ b/slices/slices.go @@ -17,12 +17,10 @@ package slices import ( + "slices" "sort" - // TODO: import slices from stdlib after Go 1.21. - "golang.org/x/exp/slices" - - "github.com/bobg/go-generics/v2/internal" + "github.com/bobg/go-generics/v3/internal" ) // Get gets the idx'th element of s. @@ -56,7 +54,7 @@ func Append[S ~[]T, T any](s S, vals ...T) S { // After the insert, the first new value has position idx. // // If idx < 0, it counts from the end of s. -// (This is a change from the behavior of "golang.org/x/exp/slices".Insert.) +// (This is a change from the behavior of Go's standard slices.Insert.) // // The input slice is modified. // @@ -68,6 +66,31 @@ func Insert[S ~[]T, T any](s S, idx int, vals ...T) S { return slices.Insert(s, idx, vals...) } +// Delete removes the elements s[i:j] from s, returning the modified slice. +// Delete panics if s[i:j] is not a valid slice of s. +// Delete is O(len(s)-j), so if many items must be deleted, it is better to +// make a single call deleting them all together than to delete one at a time. +// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those +// elements contain pointers you might consider zeroing those elements so that +// objects they reference can be garbage collected. +// +// If i < 0 it counts from the end of s. +// If j <= 0 it counts from the end of s. +// (This is a change from the behavior of Go's standard slices.Delete.) +func Delete[S ~[]E, E any](s S, i, j int) S { + return RemoveTo(s, i, j) +} + +// Replace replaces the elements s[i:j] by the given v, and returns the +// modified slice. Replace panics if s[i:j] is not a valid slice of s. +// +// If i < 0 it counts from the end of s. +// If j <= 0 it counts from the end of s. +// (This is a change from the behavior of Go's standard slices.Replace.) +func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { + return ReplaceTo(s, i, j, v...) +} + // ReplaceN replaces the n values of s beginning at position idx with the given values. // After the replace, the first new value has position idx. //