From 559de5da818e21eb80feec455abf8c7a626c4f0c Mon Sep 17 00:00:00 2001 From: Blake Mizerany Date: Tue, 15 Aug 2023 17:45:52 +0000 Subject: [PATCH] all: use slices mirror package This commit "vendors" and uses a copy of the slices package just before changeing the SlicesFunc signature to require an int return vs a bool. By vendoring, we avoid updating our code (for now) and requiring a forced upgrade to go1.21 for anyone not yet ready for that. Fixes #306 --- api/api.go | 2 +- api/api_test.go | 2 +- api/materialize/views_test.go | 2 +- cmd/tier/accounts.go | 2 +- cmd/tier/cline/cline.go | 2 +- cmd/tier/tier.go | 2 +- cmd/tier/tier_test.go | 2 +- control/client_test.go | 2 +- control/schedule.go | 2 +- control/schedule_test.go | 2 +- go.mod | 3 + go.sum | 5 + mirror/staticcheck.conf | 1 + mirror/x/exp/slices/slices.go | 282 +++++++ mirror/x/exp/slices/slices_race_test.go | 10 + mirror/x/exp/slices/slices_test.go | 862 +++++++++++++++++++++ mirror/x/exp/slices/sort.go | 128 +++ mirror/x/exp/slices/sort_benchmark_test.go | 238 ++++++ mirror/x/exp/slices/sort_test.go | 294 +++++++ mirror/x/exp/slices/zsortfunc.go | 479 ++++++++++++ mirror/x/exp/slices/zsortordered.go | 481 ++++++++++++ refs/refs.go | 2 +- refs/refs_test.go | 2 +- 23 files changed, 2795 insertions(+), 12 deletions(-) create mode 100644 mirror/staticcheck.conf create mode 100644 mirror/x/exp/slices/slices.go create mode 100644 mirror/x/exp/slices/slices_race_test.go create mode 100644 mirror/x/exp/slices/slices_test.go create mode 100644 mirror/x/exp/slices/sort.go create mode 100644 mirror/x/exp/slices/sort_benchmark_test.go create mode 100644 mirror/x/exp/slices/sort_test.go create mode 100644 mirror/x/exp/slices/zsortfunc.go create mode 100644 mirror/x/exp/slices/zsortordered.go diff --git a/api/api.go b/api/api.go index 88d9887..359cd57 100644 --- a/api/api.go +++ b/api/api.go @@ -9,11 +9,11 @@ import ( "strings" "time" - "golang.org/x/exp/slices" "tier.run/api/apitypes" "tier.run/api/materialize" "tier.run/client/tier" "tier.run/control" + "tier.run/mirror/x/exp/slices" "tier.run/refs" "tier.run/stripe" "tier.run/trweb" diff --git a/api/api_test.go b/api/api_test.go index ccaa89e..d6eb111 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -10,11 +10,11 @@ import ( "time" "github.com/kr/pretty" - "golang.org/x/exp/slices" "kr.dev/diff" "tier.run/api/apitypes" "tier.run/client/tier" "tier.run/control" + "tier.run/mirror/x/exp/slices" "tier.run/refs" "tier.run/stripe" "tier.run/stripe/stroke" diff --git a/api/materialize/views_test.go b/api/materialize/views_test.go index 6389797..3d88428 100644 --- a/api/materialize/views_test.go +++ b/api/materialize/views_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/tailscale/hujson" - "golang.org/x/exp/slices" "kr.dev/diff" "tier.run/client/tier" "tier.run/control" + "tier.run/mirror/x/exp/slices" "tier.run/refs" ) diff --git a/cmd/tier/accounts.go b/cmd/tier/accounts.go index 459bf99..b4d8775 100644 --- a/cmd/tier/accounts.go +++ b/cmd/tier/accounts.go @@ -10,7 +10,7 @@ import ( "path/filepath" "time" - "golang.org/x/exp/slices" + "tier.run/mirror/x/exp/slices" "tier.run/stripe" ) diff --git a/cmd/tier/cline/cline.go b/cmd/tier/cline/cline.go index e1e88e9..307933d 100644 --- a/cmd/tier/cline/cline.go +++ b/cmd/tier/cline/cline.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "golang.org/x/exp/slices" + "tier.run/mirror/x/exp/slices" ) var testTier string diff --git a/cmd/tier/tier.go b/cmd/tier/tier.go index ec23f09..c42b7d1 100644 --- a/cmd/tier/tier.go +++ b/cmd/tier/tier.go @@ -21,12 +21,12 @@ import ( "time" "go4.org/types" - "golang.org/x/exp/slices" "tier.run/api" "tier.run/api/apitypes" "tier.run/api/materialize" "tier.run/client/tier" "tier.run/control" + "tier.run/mirror/x/exp/slices" "tier.run/profile" "tier.run/stripe" "tier.run/version" diff --git a/cmd/tier/tier_test.go b/cmd/tier/tier_test.go index 6628d4d..0455819 100644 --- a/cmd/tier/tier_test.go +++ b/cmd/tier/tier_test.go @@ -12,9 +12,9 @@ import ( "testing" "time" - "golang.org/x/exp/slices" "kr.dev/diff" "tier.run/cmd/tier/cline" + "tier.run/mirror/x/exp/slices" "tier.run/profile" "tier.run/stripe" "tier.run/stripe/stroke" diff --git a/control/client_test.go b/control/client_test.go index 60ce70f..520ba58 100644 --- a/control/client_test.go +++ b/control/client_test.go @@ -6,8 +6,8 @@ import ( "sync" "testing" - "golang.org/x/exp/slices" "kr.dev/diff" + "tier.run/mirror/x/exp/slices" "tier.run/refs" "tier.run/stripe/stroke" ) diff --git a/control/schedule.go b/control/schedule.go index 26794de..79b5a7a 100644 --- a/control/schedule.go +++ b/control/schedule.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "golang.org/x/exp/slices" "kr.dev/errorfmt" + "tier.run/mirror/x/exp/slices" "tier.run/refs" "tier.run/stripe" "tier.run/types/payment" diff --git a/control/schedule_test.go b/control/schedule_test.go index dd41976..a27658d 100644 --- a/control/schedule_test.go +++ b/control/schedule_test.go @@ -16,10 +16,10 @@ import ( "github.com/kr/pretty" "github.com/tailscale/hujson" - "golang.org/x/exp/slices" "golang.org/x/sync/errgroup" "kr.dev/diff" "kr.dev/errorfmt" + "tier.run/mirror/x/exp/slices" "tier.run/refs" "tier.run/stripe" "tier.run/types/they" diff --git a/go.mod b/go.mod index ba5870f..3e00fcc 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,10 @@ require ( require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/kr/text v0.2.0 // indirect + golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect + golang.org/x/net v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect ) require ( diff --git a/go.sum b/go.sum index 4d30a6c..dc98ae3 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -142,6 +143,8 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -174,6 +177,8 @@ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fq golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/mirror/staticcheck.conf b/mirror/staticcheck.conf new file mode 100644 index 0000000..179d25f --- /dev/null +++ b/mirror/staticcheck.conf @@ -0,0 +1 @@ +checks = ["-all"] diff --git a/mirror/x/exp/slices/slices.go b/mirror/x/exp/slices/slices.go new file mode 100644 index 0000000..8a7cf20 --- /dev/null +++ b/mirror/x/exp/slices/slices.go @@ -0,0 +1,282 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package slices defines various functions useful with slices of any type. +// Unless otherwise specified, these functions all apply to the elements +// of a slice at index 0 <= i < len(s). +// +// Note that the less function in IsSortedFunc, SortFunc, SortStableFunc requires a +// strict weak ordering (https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings), +// or the sorting may fail to sort correctly. A common case is when sorting slices of +// floating-point numbers containing NaN values. +package slices + +import "golang.org/x/exp/constraints" + +// Equal reports whether two slices are equal: the same length and all +// elements equal. If the lengths are different, Equal returns false. +// 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 { + if len(s1) != len(s2) { + return false + } + for i := range s1 { + if s1[i] != s2[i] { + return false + } + } + return true +} + +// EqualFunc reports whether two slices are equal using a comparison +// 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 { + if len(s1) != len(s2) { + return false + } + for i, v1 := range s1 { + v2 := s2[i] + if !eq(v1, v2) { + return false + } + } + return true +} + +// Compare compares the elements of s1 and s2. +// 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 { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + switch { + case v1 < v2: + return -1 + case v1 > v2: + return +1 + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// 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. +// 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 { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + if c := cmp(v1, v2); c != 0 { + return c + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// 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 { + for i := range s { + if v == s[i] { + return i + } + } + return -1 +} + +// 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 { + for i := range s { + if f(s[i]) { + return i + } + } + return -1 +} + +// Contains reports whether v is present in s. +func Contains[E comparable](s []E, v E) bool { + return Index(s, v) >= 0 +} + +// ContainsFunc reports whether at least one +// element e of s satisfies f(e). +func ContainsFunc[E any](s []E, f func(E) bool) bool { + return IndexFunc(s, f) >= 0 +} + +// Insert inserts the values v... into s at index i, +// returning the modified slice. +// In the returned slice r, r[i] == v[0]. +// Insert panics if i is out of range. +// This function is O(len(s) + len(v)). +func Insert[S ~[]E, E any](s S, i int, v ...E) S { + tot := len(s) + len(v) + if tot <= cap(s) { + s2 := s[:tot] + copy(s2[i+len(v):], s[i:]) + copy(s2[i:], v) + return s2 + } + s2 := make(S, tot) + copy(s2, s[:i]) + copy(s2[i:], v) + copy(s2[i+len(v):], s[i:]) + return s2 +} + +// 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. +func Delete[S ~[]E, E any](s S, i, j int) S { + _ = s[i:j] // bounds check + + return append(s[:i], s[j:]...) +} + +// 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 { + // Don't start copying elements until we find one to delete. + for i, v := range s { + if del(v) { + j := i + for i++; i < len(s); i++ { + v = s[i] + if !del(v) { + s[j] = v + j++ + } + } + return s[:j] + } + } + return s +} + +// 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. +func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { + _ = s[i:j] // verify that i:j is a valid subslice + tot := len(s[:i]) + len(v) + len(s[j:]) + if tot <= cap(s) { + s2 := s[:tot] + copy(s2[i+len(v):], s[j:]) + copy(s2[i:], v) + return s2 + } + s2 := make(S, tot) + copy(s2, s[:i]) + copy(s2[i:], v) + copy(s2[i+len(v):], s[j:]) + return s2 +} + +// Clone returns a copy of the slice. +// The elements are copied using assignment, so this is a shallow clone. +func Clone[S ~[]E, E any](s S) S { + // Preserve nil in case it matters. + if s == nil { + return nil + } + return append(S([]E{}), 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. +// 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. +func Compact[S ~[]E, E comparable](s S) S { + if len(s) < 2 { + return s + } + i := 1 + for k := 1; k < len(s); k++ { + if s[k] != s[k-1] { + if i != k { + s[i] = s[k] + } + i++ + } + } + return s[:i] +} + +// CompactFunc is like Compact but uses a comparison function. +func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { + if len(s) < 2 { + return s + } + i := 1 + for k := 1; k < len(s); k++ { + if !eq(s[k], s[k-1]) { + if i != k { + s[i] = s[k] + } + i++ + } + } + return s[:i] +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. If n is negative or too large to +// allocate the memory, Grow panics. +func Grow[S ~[]E, E any](s S, n int) S { + if n < 0 { + panic("cannot be negative") + } + if n -= cap(s) - len(s); n > 0 { + // TODO(https://go.dev/issue/53888): Make using []E instead of S + // to workaround a compiler bug where the runtime.growslice optimization + // does not take effect. Revert when the compiler is fixed. + s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)] + } + return s +} + +// Clip removes unused capacity from the slice, returning s[:len(s):len(s)]. +func Clip[S ~[]E, E any](s S) S { + return s[:len(s):len(s)] +} diff --git a/mirror/x/exp/slices/slices_race_test.go b/mirror/x/exp/slices/slices_race_test.go new file mode 100644 index 0000000..c85d6ee --- /dev/null +++ b/mirror/x/exp/slices/slices_race_test.go @@ -0,0 +1,10 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build race +// +build race + +package slices + +func init() { raceEnabled = true } diff --git a/mirror/x/exp/slices/slices_test.go b/mirror/x/exp/slices/slices_test.go new file mode 100644 index 0000000..c2402dd --- /dev/null +++ b/mirror/x/exp/slices/slices_test.go @@ -0,0 +1,862 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import ( + "math" + "strings" + "testing" + + "golang.org/x/exp/constraints" +) + +var raceEnabled bool + +var equalIntTests = []struct { + s1, s2 []int + want bool +}{ + { + []int{1}, + nil, + false, + }, + { + []int{}, + nil, + true, + }, + { + []int{1, 2, 3}, + []int{1, 2, 3}, + true, + }, + { + []int{1, 2, 3}, + []int{1, 2, 3, 4}, + false, + }, +} + +var equalFloatTests = []struct { + s1, s2 []float64 + wantEqual bool + wantEqualNaN bool +}{ + { + []float64{1, 2}, + []float64{1, 2}, + true, + true, + }, + { + []float64{1, 2, math.NaN()}, + []float64{1, 2, math.NaN()}, + false, + true, + }, +} + +func TestEqual(t *testing.T) { + for _, test := range equalIntTests { + if got := Equal(test.s1, test.s2); got != test.want { + t.Errorf("Equal(%v, %v) = %t, want %t", test.s1, test.s2, got, test.want) + } + } + for _, test := range equalFloatTests { + if got := Equal(test.s1, test.s2); got != test.wantEqual { + t.Errorf("Equal(%v, %v) = %t, want %t", test.s1, test.s2, got, test.wantEqual) + } + } +} + +// equal is simply ==. +func equal[T comparable](v1, v2 T) bool { + return v1 == v2 +} + +// equalNaN is like == except that all NaNs are equal. +func equalNaN[T comparable](v1, v2 T) bool { + isNaN := func(f T) bool { return f != f } + return v1 == v2 || (isNaN(v1) && isNaN(v2)) +} + +// offByOne returns true if integers v1 and v2 differ by 1. +func offByOne[E constraints.Integer](v1, v2 E) bool { + return v1 == v2+1 || v1 == v2-1 +} + +func TestEqualFunc(t *testing.T) { + for _, test := range equalIntTests { + if got := EqualFunc(test.s1, test.s2, equal[int]); got != test.want { + t.Errorf("EqualFunc(%v, %v, equal[int]) = %t, want %t", test.s1, test.s2, got, test.want) + } + } + for _, test := range equalFloatTests { + if got := EqualFunc(test.s1, test.s2, equal[float64]); got != test.wantEqual { + t.Errorf("Equal(%v, %v, equal[float64]) = %t, want %t", test.s1, test.s2, got, test.wantEqual) + } + if got := EqualFunc(test.s1, test.s2, equalNaN[float64]); got != test.wantEqualNaN { + t.Errorf("Equal(%v, %v, equalNaN[float64]) = %t, want %t", test.s1, test.s2, got, test.wantEqualNaN) + } + } + + s1 := []int{1, 2, 3} + s2 := []int{2, 3, 4} + if EqualFunc(s1, s1, offByOne[int]) { + t.Errorf("EqualFunc(%v, %v, offByOne) = true, want false", s1, s1) + } + if !EqualFunc(s1, s2, offByOne[int]) { + t.Errorf("EqualFunc(%v, %v, offByOne) = false, want true", s1, s2) + } + + s3 := []string{"a", "b", "c"} + s4 := []string{"A", "B", "C"} + if !EqualFunc(s3, s4, strings.EqualFold) { + t.Errorf("EqualFunc(%v, %v, strings.EqualFold) = false, want true", s3, s4) + } + + cmpIntString := func(v1 int, v2 string) bool { + return string(rune(v1)-1+'a') == v2 + } + if !EqualFunc(s1, s3, cmpIntString) { + t.Errorf("EqualFunc(%v, %v, cmpIntString) = false, want true", s1, s3) + } +} + +func BenchmarkEqualFunc_Large(b *testing.B) { + type Large [4 * 1024]byte + + xs := make([]Large, 1024) + ys := make([]Large, 1024) + for i := 0; i < b.N; i++ { + _ = EqualFunc(xs, ys, func(x, y Large) bool { return x == y }) + } +} + +var compareIntTests = []struct { + s1, s2 []int + want int +}{ + { + []int{1, 2, 3}, + []int{1, 2, 3, 4}, + -1, + }, + { + []int{1, 2, 3, 4}, + []int{1, 2, 3}, + +1, + }, + { + []int{1, 2, 3}, + []int{1, 4, 3}, + -1, + }, + { + []int{1, 4, 3}, + []int{1, 2, 3}, + +1, + }, +} + +var compareFloatTests = []struct { + s1, s2 []float64 + want int +}{ + { + []float64{1, 2, math.NaN()}, + []float64{1, 2, math.NaN()}, + 0, + }, + { + []float64{1, math.NaN(), 3}, + []float64{1, math.NaN(), 4}, + -1, + }, + { + []float64{1, math.NaN(), 3}, + []float64{1, 2, 4}, + -1, + }, + { + []float64{1, math.NaN(), 3}, + []float64{1, 2, math.NaN()}, + 0, + }, + { + []float64{1, math.NaN(), 3, 4}, + []float64{1, 2, math.NaN()}, + +1, + }, +} + +func TestCompare(t *testing.T) { + intWant := func(want bool) string { + if want { + return "0" + } + return "!= 0" + } + for _, test := range equalIntTests { + if got := Compare(test.s1, test.s2); (got == 0) != test.want { + t.Errorf("Compare(%v, %v) = %d, want %s", test.s1, test.s2, got, intWant(test.want)) + } + } + for _, test := range equalFloatTests { + if got := Compare(test.s1, test.s2); (got == 0) != test.wantEqualNaN { + t.Errorf("Compare(%v, %v) = %d, want %s", test.s1, test.s2, got, intWant(test.wantEqualNaN)) + } + } + + for _, test := range compareIntTests { + if got := Compare(test.s1, test.s2); got != test.want { + t.Errorf("Compare(%v, %v) = %d, want %d", test.s1, test.s2, got, test.want) + } + } + for _, test := range compareFloatTests { + if got := Compare(test.s1, test.s2); got != test.want { + t.Errorf("Compare(%v, %v) = %d, want %d", test.s1, test.s2, got, test.want) + } + } +} + +func equalToCmp[T comparable](eq func(T, T) bool) func(T, T) int { + return func(v1, v2 T) int { + if eq(v1, v2) { + return 0 + } + return 1 + } +} + +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 { + return "0" + } + return "!= 0" + } + for _, test := range equalIntTests { + if got := CompareFunc(test.s1, test.s2, equalToCmp(equal[int])); (got == 0) != test.want { + t.Errorf("CompareFunc(%v, %v, equalToCmp(equal[int])) = %d, want %s", test.s1, test.s2, got, intWant(test.want)) + } + } + for _, test := range equalFloatTests { + if got := CompareFunc(test.s1, test.s2, equalToCmp(equal[float64])); (got == 0) != test.wantEqual { + t.Errorf("CompareFunc(%v, %v, equalToCmp(equal[float64])) = %d, want %s", test.s1, test.s2, got, intWant(test.wantEqual)) + } + } + + 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) + } + } + 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) + } + } + + s1 := []int{1, 2, 3} + s2 := []int{2, 3, 4} + if got := CompareFunc(s1, s2, equalToCmp(offByOne[int])); got != 0 { + t.Errorf("CompareFunc(%v, %v, offByOne) = %d, want 0", s1, s2, got) + } + + s3 := []string{"a", "b", "c"} + s4 := []string{"A", "B", "C"} + if got := CompareFunc(s3, s4, strings.Compare); got != 1 { + t.Errorf("CompareFunc(%v, %v, strings.Compare) = %d, want 1", s3, s4, got) + } + + compareLower := func(v1, v2 string) int { + return strings.Compare(strings.ToLower(v1), strings.ToLower(v2)) + } + if got := CompareFunc(s3, s4, compareLower); got != 0 { + t.Errorf("CompareFunc(%v, %v, compareLower) = %d, want 0", s3, s4, got) + } + + cmpIntString := func(v1 int, v2 string) int { + return strings.Compare(string(rune(v1)-1+'a'), v2) + } + if got := CompareFunc(s1, s3, cmpIntString); got != 0 { + t.Errorf("CompareFunc(%v, %v, cmpIntString) = %d, want 0", s1, s3, got) + } +} + +var indexTests = []struct { + s []int + v int + want int +}{ + { + nil, + 0, + -1, + }, + { + []int{}, + 0, + -1, + }, + { + []int{1, 2, 3}, + 2, + 1, + }, + { + []int{1, 2, 2, 3}, + 2, + 1, + }, + { + []int{1, 2, 3, 2}, + 2, + 1, + }, +} + +func TestIndex(t *testing.T) { + for _, test := range indexTests { + if got := Index(test.s, test.v); got != test.want { + t.Errorf("Index(%v, %v) = %d, want %d", test.s, test.v, got, test.want) + } + } +} + +func equalToIndex[T any](f func(T, T) bool, v1 T) func(T) bool { + return func(v2 T) bool { + return f(v1, v2) + } +} + +func BenchmarkIndex_Large(b *testing.B) { + type Large [4 * 1024]byte + + ss := make([]Large, 1024) + for i := 0; i < b.N; i++ { + _ = Index(ss, Large{1}) + } +} + +func TestIndexFunc(t *testing.T) { + for _, test := range indexTests { + if got := IndexFunc(test.s, equalToIndex(equal[int], test.v)); got != test.want { + t.Errorf("IndexFunc(%v, equalToIndex(equal[int], %v)) = %d, want %d", test.s, test.v, got, test.want) + } + } + + s1 := []string{"hi", "HI"} + if got := IndexFunc(s1, equalToIndex(equal[string], "HI")); got != 1 { + t.Errorf("IndexFunc(%v, equalToIndex(equal[string], %q)) = %d, want %d", s1, "HI", got, 1) + } + if got := IndexFunc(s1, equalToIndex(strings.EqualFold, "HI")); got != 0 { + t.Errorf("IndexFunc(%v, equalToIndex(strings.EqualFold, %q)) = %d, want %d", s1, "HI", got, 0) + } +} + +func BenchmarkIndexFunc_Large(b *testing.B) { + type Large [4 * 1024]byte + + ss := make([]Large, 1024) + for i := 0; i < b.N; i++ { + _ = IndexFunc(ss, func(e Large) bool { + return e == Large{1} + }) + } +} + +func TestContains(t *testing.T) { + for _, test := range indexTests { + if got := Contains(test.s, test.v); got != (test.want != -1) { + t.Errorf("Contains(%v, %v) = %t, want %t", test.s, test.v, got, test.want != -1) + } + } +} + +func TestContainsFunc(t *testing.T) { + for _, test := range indexTests { + if got := ContainsFunc(test.s, equalToIndex(equal[int], test.v)); got != (test.want != -1) { + t.Errorf("ContainsFunc(%v, equalToIndex(equal[int], %v)) = %t, want %t", test.s, test.v, got, test.want != -1) + } + } + + s1 := []string{"hi", "HI"} + if got := ContainsFunc(s1, equalToIndex(equal[string], "HI")); got != true { + t.Errorf("ContainsFunc(%v, equalToContains(equal[string], %q)) = %t, want %t", s1, "HI", got, true) + } + if got := ContainsFunc(s1, equalToIndex(equal[string], "hI")); got != false { + t.Errorf("ContainsFunc(%v, equalToContains(strings.EqualFold, %q)) = %t, want %t", s1, "hI", got, false) + } + if got := ContainsFunc(s1, equalToIndex(strings.EqualFold, "hI")); got != true { + t.Errorf("ContainsFunc(%v, equalToContains(strings.EqualFold, %q)) = %t, want %t", s1, "hI", got, true) + } +} + +var insertTests = []struct { + s []int + i int + add []int + want []int +}{ + { + []int{1, 2, 3}, + 0, + []int{4}, + []int{4, 1, 2, 3}, + }, + { + []int{1, 2, 3}, + 1, + []int{4}, + []int{1, 4, 2, 3}, + }, + { + []int{1, 2, 3}, + 3, + []int{4}, + []int{1, 2, 3, 4}, + }, + { + []int{1, 2, 3}, + 2, + []int{4, 5}, + []int{1, 2, 4, 5, 3}, + }, +} + +func TestInsert(t *testing.T) { + s := []int{1, 2, 3} + if got := Insert(s, 0); !Equal(got, s) { + t.Errorf("Insert(%v, 0) = %v, want %v", s, got, s) + } + for _, test := range insertTests { + copy := Clone(test.s) + if got := Insert(copy, test.i, test.add...); !Equal(got, test.want) { + t.Errorf("Insert(%v, %d, %v...) = %v, want %v", test.s, test.i, test.add, got, test.want) + } + } +} + +var deleteTests = []struct { + s []int + i, j int + want []int +}{ + { + []int{1, 2, 3}, + 0, + 0, + []int{1, 2, 3}, + }, + { + []int{1, 2, 3}, + 0, + 1, + []int{2, 3}, + }, + { + []int{1, 2, 3}, + 3, + 3, + []int{1, 2, 3}, + }, + { + []int{1, 2, 3}, + 0, + 2, + []int{3}, + }, + { + []int{1, 2, 3}, + 0, + 3, + []int{}, + }, +} + +func TestDelete(t *testing.T) { + for _, test := range deleteTests { + copy := Clone(test.s) + if got := Delete(copy, test.i, test.j); !Equal(got, test.want) { + t.Errorf("Delete(%v, %d, %d) = %v, want %v", test.s, test.i, test.j, got, test.want) + } + } +} + +var deleteFuncTests = []struct { + s []int + fn func(int) bool + want []int +}{ + { + nil, + func(int) bool { return true }, + nil, + }, + { + []int{1, 2, 3}, + func(int) bool { return true }, + nil, + }, + { + []int{1, 2, 3}, + func(int) bool { return false }, + []int{1, 2, 3}, + }, + { + []int{1, 2, 3}, + func(i int) bool { return i > 2 }, + []int{1, 2}, + }, + { + []int{1, 2, 3}, + func(i int) bool { return i < 2 }, + []int{2, 3}, + }, + { + []int{10, 2, 30}, + func(i int) bool { return i >= 10 }, + []int{2}, + }, +} + +func TestDeleteFunc(t *testing.T) { + for i, test := range deleteFuncTests { + copy := Clone(test.s) + if got := DeleteFunc(copy, test.fn); !Equal(got, test.want) { + t.Errorf("DeleteFunc case %d: got %v, want %v", i, got, test.want) + } + } +} + +func panics(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + b = true + } + }() + f() + return false +} + +func TestDeletePanics(t *testing.T) { + for _, test := range []struct { + name string + s []int + i, j int + }{ + {"with negative first index", []int{42}, -2, 1}, + {"with negative second index", []int{42}, 1, -1}, + {"with out-of-bounds first index", []int{42}, 2, 3}, + {"with out-of-bounds second index", []int{42}, 0, 2}, + {"with invalid i>j", []int{42}, 1, 0}, + } { + if !panics(func() { Delete(test.s, test.i, test.j) }) { + t.Errorf("Delete %s: got no panic, want panic", test.name) + } + } +} + +func TestClone(t *testing.T) { + s1 := []int{1, 2, 3} + s2 := Clone(s1) + if !Equal(s1, s2) { + t.Errorf("Clone(%v) = %v, want %v", s1, s2, s1) + } + s1[0] = 4 + want := []int{1, 2, 3} + if !Equal(s2, want) { + t.Errorf("Clone(%v) changed unexpectedly to %v", want, s2) + } + if got := Clone([]int(nil)); got != nil { + t.Errorf("Clone(nil) = %#v, want nil", got) + } + if got := Clone(s1[:0]); got == nil || len(got) != 0 { + t.Errorf("Clone(%v) = %#v, want %#v", s1[:0], got, s1[:0]) + } +} + +var compactTests = []struct { + name string + s []int + want []int +}{ + { + "nil", + nil, + nil, + }, + { + "one", + []int{1}, + []int{1}, + }, + { + "sorted", + []int{1, 2, 3}, + []int{1, 2, 3}, + }, + { + "1 item", + []int{1, 1, 2}, + []int{1, 2}, + }, + { + "unsorted", + []int{1, 2, 1}, + []int{1, 2, 1}, + }, + { + "many", + []int{1, 2, 2, 3, 3, 4}, + []int{1, 2, 3, 4}, + }, +} + +func TestCompact(t *testing.T) { + for _, test := range compactTests { + copy := Clone(test.s) + if got := Compact(copy); !Equal(got, test.want) { + t.Errorf("Compact(%v) = %v, want %v", test.s, got, test.want) + } + } +} + +func BenchmarkCompact(b *testing.B) { + for _, c := range compactTests { + b.Run(c.name, func(b *testing.B) { + ss := make([]int, 0, 64) + for k := 0; k < b.N; k++ { + ss = ss[:0] + ss = append(ss, c.s...) + _ = Compact(ss) + } + }) + } +} + +func BenchmarkCompact_Large(b *testing.B) { + type Large [4 * 1024]byte + + ss := make([]Large, 1024) + for i := 0; i < b.N; i++ { + _ = Compact(ss) + } +} + +func TestCompactFunc(t *testing.T) { + for _, test := range compactTests { + copy := Clone(test.s) + if got := CompactFunc(copy, equal[int]); !Equal(got, test.want) { + t.Errorf("CompactFunc(%v, equal[int]) = %v, want %v", test.s, got, test.want) + } + } + + s1 := []string{"a", "a", "A", "B", "b"} + copy := Clone(s1) + want := []string{"a", "B"} + if got := CompactFunc(copy, strings.EqualFold); !Equal(got, want) { + t.Errorf("CompactFunc(%v, strings.EqualFold) = %v, want %v", s1, got, want) + } +} + +func BenchmarkCompactFunc_Large(b *testing.B) { + type Large [4 * 1024]byte + + ss := make([]Large, 1024) + for i := 0; i < b.N; i++ { + _ = CompactFunc(ss, func(a, b Large) bool { return a == b }) + } +} + +func TestGrow(t *testing.T) { + s1 := []int{1, 2, 3} + + copy := Clone(s1) + s2 := Grow(copy, 1000) + if !Equal(s1, s2) { + t.Errorf("Grow(%v) = %v, want %v", s1, s2, s1) + } + if cap(s2) < 1000+len(s1) { + t.Errorf("after Grow(%v) cap = %d, want >= %d", s1, cap(s2), 1000+len(s1)) + } + + // Test mutation of elements between length and capacity. + copy = Clone(s1) + s3 := Grow(copy[:1], 2)[:3] + if !Equal(s1, s3) { + t.Errorf("Grow should not mutate elements between length and capacity") + } + s3 = Grow(copy[:1], 1000)[:3] + if !Equal(s1, s3) { + t.Errorf("Grow should not mutate elements between length and capacity") + } + + // Test number of allocations. + if n := testing.AllocsPerRun(100, func() { Grow(s2, cap(s2)-len(s2)) }); n != 0 { + t.Errorf("Grow should not allocate when given sufficient capacity; allocated %v times", n) + } + if n := testing.AllocsPerRun(100, func() { Grow(s2, cap(s2)-len(s2)+1) }); n != 1 { + errorf := t.Errorf + if raceEnabled { + errorf = t.Logf // this allocates multiple times in race detector mode + } + errorf("Grow should allocate once when given insufficient capacity; allocated %v times", n) + } + + // Test for negative growth sizes. + var gotPanic bool + func() { + defer func() { gotPanic = recover() != nil }() + Grow(s1, -1) + }() + if !gotPanic { + t.Errorf("Grow(-1) did not panic; expected a panic") + } +} + +func TestClip(t *testing.T) { + s1 := []int{1, 2, 3, 4, 5, 6}[:3] + orig := Clone(s1) + if len(s1) != 3 { + t.Errorf("len(%v) = %d, want 3", s1, len(s1)) + } + if cap(s1) < 6 { + t.Errorf("cap(%v[:3]) = %d, want >= 6", orig, cap(s1)) + } + s2 := Clip(s1) + if !Equal(s1, s2) { + t.Errorf("Clip(%v) = %v, want %v", s1, s2, s1) + } + if cap(s2) != 3 { + t.Errorf("cap(Clip(%v)) = %d, want 3", orig, cap(s2)) + } +} + +// naiveReplace is a baseline implementation to the Replace function. +func naiveReplace[S ~[]E, E any](s S, i, j int, v ...E) S { + s = Delete(s, i, j) + s = Insert(s, i, v...) + return s +} + +func TestReplace(t *testing.T) { + for _, test := range []struct { + s, v []int + i, j int + }{ + {}, // all zero value + { + s: []int{1, 2, 3, 4}, + v: []int{5}, + i: 1, + j: 2, + }, + { + s: []int{1, 2, 3, 4}, + v: []int{5, 6, 7, 8}, + i: 1, + j: 2, + }, + { + s: func() []int { + s := make([]int, 3, 20) + s[0] = 0 + s[1] = 1 + s[2] = 2 + return s + }(), + v: []int{3, 4, 5, 6, 7}, + i: 0, + j: 1, + }, + } { + ss, vv := Clone(test.s), Clone(test.v) + want := naiveReplace(ss, test.i, test.j, vv...) + got := Replace(test.s, test.i, test.j, test.v...) + if !Equal(got, want) { + t.Errorf("Replace(%v, %v, %v, %v) = %v, want %v", test.s, test.i, test.j, test.v, got, want) + } + } +} + +func TestReplacePanics(t *testing.T) { + for _, test := range []struct { + name string + s, v []int + i, j int + }{ + {"indexes out of order", []int{1, 2}, []int{3}, 2, 1}, + {"large index", []int{1, 2}, []int{3}, 1, 10}, + {"negative index", []int{1, 2}, []int{3}, -1, 2}, + } { + ss, vv := Clone(test.s), Clone(test.v) + if !panics(func() { Replace(ss, test.i, test.j, vv...) }) { + t.Errorf("Replace %s: should have panicked", test.name) + } + } +} + +func BenchmarkReplace(b *testing.B) { + cases := []struct { + name string + s, v func() []int + i, j int + }{ + { + name: "fast", + s: func() []int { + return make([]int, 100) + }, + v: func() []int { + return make([]int, 20) + }, + i: 10, + j: 40, + }, + { + name: "slow", + s: func() []int { + return make([]int, 100) + }, + v: func() []int { + return make([]int, 20) + }, + i: 0, + j: 2, + }, + } + + for _, c := range cases { + b.Run("naive-"+c.name, func(b *testing.B) { + for k := 0; k < b.N; k++ { + s := c.s() + v := c.v() + _ = naiveReplace(s, c.i, c.j, v...) + } + }) + b.Run("optimized-"+c.name, func(b *testing.B) { + for k := 0; k < b.N; k++ { + s := c.s() + v := c.v() + _ = Replace(s, c.i, c.j, v...) + } + }) + } + +} diff --git a/mirror/x/exp/slices/sort.go b/mirror/x/exp/slices/sort.go new file mode 100644 index 0000000..231b644 --- /dev/null +++ b/mirror/x/exp/slices/sort.go @@ -0,0 +1,128 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import ( + "math/bits" + + "golang.org/x/exp/constraints" +) + +// 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) { + n := len(x) + pdqsortOrdered(x, 0, n, bits.Len(uint(n))) +} + +// SortFunc sorts the slice x in ascending order as determined by the less function. +// This sort is not guaranteed to be stable. +// +// SortFunc requires that less 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) { + n := len(x) + pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less) +} + +// 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) { + stableLessFunc(x, len(x), less) +} + +// IsSorted reports whether x is sorted in ascending order. +func IsSorted[E constraints.Ordered](x []E) bool { + for i := len(x) - 1; i > 0; i-- { + if x[i] < x[i-1] { + return false + } + } + return true +} + +// 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 { + for i := len(x) - 1; i > 0; i-- { + if less(x[i], x[i-1]) { + return false + } + } + return true +} + +// 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) { + // Inlining is faster than calling BinarySearchFunc with a lambda. + n := len(x) + // Define x[-1] < target and x[n] >= target. + // Invariant: x[i-1] < target, x[j] >= target. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if x[h] < target { + i = h + 1 // preserves x[i-1] < target + } else { + j = h // preserves x[j] >= target + } + } + // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i. + return i, i < n && x[i] == 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 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[E, T any](x []E, target T, cmp func(E, T) int) (int, bool) { + n := len(x) + // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 . + // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if cmp(x[h], target) < 0 { + i = h + 1 // preserves cmp(x[i - 1], target) < 0 + } else { + j = h // preserves cmp(x[j], target) >= 0 + } + } + // i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i. + return i, i < n && cmp(x[i], target) == 0 +} + +type sortedHint int // hint for pdqsort when choosing the pivot + +const ( + unknownHint sortedHint = iota + increasingHint + decreasingHint +) + +// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf +type xorshift uint64 + +func (r *xorshift) Next() uint64 { + *r ^= *r << 13 + *r ^= *r >> 17 + *r ^= *r << 5 + return uint64(*r) +} + +func nextPowerOfTwo(length int) uint { + return 1 << bits.Len(uint(length)) +} diff --git a/mirror/x/exp/slices/sort_benchmark_test.go b/mirror/x/exp/slices/sort_benchmark_test.go new file mode 100644 index 0000000..ee49f66 --- /dev/null +++ b/mirror/x/exp/slices/sort_benchmark_test.go @@ -0,0 +1,238 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import ( + "fmt" + "math/rand" + "sort" + "strings" + "testing" +) + +// These benchmarks compare sorting a large slice of int with sort.Ints vs. +// slices.Sort +func makeRandomInts(n int) []int { + rand.Seed(42) + ints := make([]int, n) + for i := 0; i < n; i++ { + ints[i] = rand.Intn(n) + } + return ints +} + +func makeSortedInts(n int) []int { + ints := make([]int, n) + for i := 0; i < n; i++ { + ints[i] = i + } + return ints +} + +func makeReversedInts(n int) []int { + ints := make([]int, n) + for i := 0; i < n; i++ { + ints[i] = n - i + } + return ints +} + +const N = 100_000 + +func BenchmarkSortInts(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeRandomInts(N) + b.StartTimer() + sort.Ints(ints) + } +} + +func BenchmarkSlicesSortInts(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeRandomInts(N) + b.StartTimer() + Sort(ints) + } +} + +func BenchmarkSlicesSortInts_Sorted(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeSortedInts(N) + b.StartTimer() + Sort(ints) + } +} + +func BenchmarkSlicesSortInts_Reversed(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeReversedInts(N) + b.StartTimer() + Sort(ints) + } +} + +// Since we're benchmarking these sorts against each other, make sure that they +// generate similar results. +func TestIntSorts(t *testing.T) { + ints := makeRandomInts(200) + ints2 := Clone(ints) + + sort.Ints(ints) + Sort(ints2) + + for i := range ints { + if ints[i] != ints2[i] { + t.Fatalf("ints2 mismatch at %d; %d != %d", i, ints[i], ints2[i]) + } + } +} + +// The following is a benchmark for sorting strings. + +// makeRandomStrings generates n random strings with alphabetic runes of +// varying lenghts. +func makeRandomStrings(n int) []string { + rand.Seed(42) + var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + ss := make([]string, n) + for i := 0; i < n; i++ { + var sb strings.Builder + slen := 2 + rand.Intn(50) + for j := 0; j < slen; j++ { + sb.WriteRune(letters[rand.Intn(len(letters))]) + } + ss[i] = sb.String() + } + return ss +} + +func TestStringSorts(t *testing.T) { + ss := makeRandomStrings(200) + ss2 := Clone(ss) + + sort.Strings(ss) + Sort(ss2) + + for i := range ss { + if ss[i] != ss2[i] { + t.Fatalf("ss2 mismatch at %d; %s != %s", i, ss[i], ss2[i]) + } + } +} + +func BenchmarkSortStrings(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ss := makeRandomStrings(N) + b.StartTimer() + sort.Strings(ss) + } +} + +func BenchmarkSlicesSortStrings(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ss := makeRandomStrings(N) + b.StartTimer() + Sort(ss) + } +} + +// These benchmarks compare sorting a slice of structs with sort.Sort vs. +// slices.SortFunc. +type myStruct struct { + a, b, c, d string + n int +} + +type myStructs []*myStruct + +func (s myStructs) Len() int { return len(s) } +func (s myStructs) Less(i, j int) bool { return s[i].n < s[j].n } +func (s myStructs) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func makeRandomStructs(n int) myStructs { + rand.Seed(42) + structs := make([]*myStruct, n) + for i := 0; i < n; i++ { + structs[i] = &myStruct{n: rand.Intn(n)} + } + return structs +} + +func TestStructSorts(t *testing.T) { + ss := makeRandomStructs(200) + ss2 := make([]*myStruct, len(ss)) + for i := range ss { + ss2[i] = &myStruct{n: ss[i].n} + } + + sort.Sort(ss) + SortFunc(ss2, func(a, b *myStruct) bool { return a.n < b.n }) + + for i := range ss { + if *ss[i] != *ss2[i] { + t.Fatalf("ints2 mismatch at %d; %v != %v", i, *ss[i], *ss2[i]) + } + } +} + +func BenchmarkSortStructs(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ss := makeRandomStructs(N) + b.StartTimer() + sort.Sort(ss) + } +} + +func BenchmarkSortFuncStructs(b *testing.B) { + lessFunc := func(a, b *myStruct) bool { return a.n < b.n } + for i := 0; i < b.N; i++ { + b.StopTimer() + ss := makeRandomStructs(N) + b.StartTimer() + SortFunc(ss, lessFunc) + } +} + +func BenchmarkBinarySearchFloats(b *testing.B) { + for _, size := range []int{16, 32, 64, 128, 512, 1024} { + b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) { + floats := make([]float64, size) + for i := range floats { + floats[i] = float64(i) + } + midpoint := len(floats) / 2 + needle := (floats[midpoint] + floats[midpoint+1]) / 2 + b.ResetTimer() + for i := 0; i < b.N; i++ { + BinarySearch(floats, needle) + } + }) + } +} + +func BenchmarkBinarySearchFuncStruct(b *testing.B) { + for _, size := range []int{16, 32, 64, 128, 512, 1024} { + b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) { + structs := make([]*myStruct, size) + for i := range structs { + structs[i] = &myStruct{n: i} + } + midpoint := len(structs) / 2 + needle := &myStruct{n: (structs[midpoint].n + structs[midpoint+1].n) / 2} + lessFunc := func(a, b *myStruct) int { return a.n - b.n } + b.ResetTimer() + for i := 0; i < b.N; i++ { + BinarySearchFunc(structs, needle, lessFunc) + } + }) + } +} diff --git a/mirror/x/exp/slices/sort_test.go b/mirror/x/exp/slices/sort_test.go new file mode 100644 index 0000000..3befa3e --- /dev/null +++ b/mirror/x/exp/slices/sort_test.go @@ -0,0 +1,294 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import ( + "math" + "math/rand" + "sort" + "strconv" + "strings" + "testing" +) + +var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586} +var float64s = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8, 74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3} +var float64sWithNaNs = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.NaN(), math.NaN(), math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8} +var strs = [...]string{"", "Hello", "foo", "bar", "foo", "f00", "%*&^*&^&", "***"} + +func TestSortIntSlice(t *testing.T) { + data := Clone(ints[:]) + Sort(data) + if !IsSorted(data) { + t.Errorf("sorted %v", ints) + t.Errorf(" got %v", data) + } +} + +func TestSortFuncIntSlice(t *testing.T) { + data := Clone(ints[:]) + SortFunc(data, func(a, b int) bool { return a < b }) + if !IsSorted(data) { + t.Errorf("sorted %v", ints) + t.Errorf(" got %v", data) + } +} + +func TestSortFloat64Slice(t *testing.T) { + data := Clone(float64s[:]) + Sort(data) + if !IsSorted(data) { + t.Errorf("sorted %v", float64s) + t.Errorf(" got %v", data) + } +} + +func TestSortFloat64SliceWithNaNs(t *testing.T) { + data := float64sWithNaNs[:] + input := Clone(data) + + // Make sure Sort doesn't panic when the slice contains NaNs. + Sort(data) + // Check whether the result is a permutation of the input. + sort.Float64s(data) + sort.Float64s(input) + for i, v := range input { + if data[i] != v && !(math.IsNaN(data[i]) && math.IsNaN(v)) { + t.Fatalf("the result is not a permutation of the input\ngot %v\nwant %v", data, input) + } + } +} + +func TestSortStringSlice(t *testing.T) { + data := Clone(strs[:]) + Sort(data) + if !IsSorted(data) { + t.Errorf("sorted %v", strs) + t.Errorf(" got %v", data) + } +} + +func TestSortLarge_Random(t *testing.T) { + n := 1000000 + if testing.Short() { + n /= 100 + } + data := make([]int, n) + for i := 0; i < len(data); i++ { + data[i] = rand.Intn(100) + } + if IsSorted(data) { + t.Fatalf("terrible rand.rand") + } + Sort(data) + if !IsSorted(data) { + t.Errorf("sort didn't sort - 1M ints") + } +} + +type intPair struct { + a, b int +} + +type intPairs []intPair + +// Pairs compare on a only. +func intPairLess(x, y intPair) bool { + return x.a < y.a +} + +// Record initial order in B. +func (d intPairs) initB() { + for i := range d { + d[i].b = i + } +} + +// InOrder checks if a-equal elements were not reordered. +func (d intPairs) inOrder() bool { + lastA, lastB := -1, 0 + for i := 0; i < len(d); i++ { + if lastA != d[i].a { + lastA = d[i].a + lastB = d[i].b + continue + } + if d[i].b <= lastB { + return false + } + lastB = d[i].b + } + return true +} + +func TestStability(t *testing.T) { + n, m := 100000, 1000 + if testing.Short() { + n, m = 1000, 100 + } + data := make(intPairs, n) + + // random distribution + for i := 0; i < len(data); i++ { + data[i].a = rand.Intn(m) + } + if IsSortedFunc(data, intPairLess) { + t.Fatalf("terrible rand.rand") + } + data.initB() + SortStableFunc(data, intPairLess) + if !IsSortedFunc(data, intPairLess) { + t.Errorf("Stable didn't sort %d ints", n) + } + if !data.inOrder() { + t.Errorf("Stable wasn't stable on %d ints", n) + } + + // already sorted + data.initB() + SortStableFunc(data, intPairLess) + if !IsSortedFunc(data, intPairLess) { + t.Errorf("Stable shuffled sorted %d ints (order)", n) + } + if !data.inOrder() { + t.Errorf("Stable shuffled sorted %d ints (stability)", n) + } + + // sorted reversed + for i := 0; i < len(data); i++ { + data[i].a = len(data) - i + } + data.initB() + SortStableFunc(data, intPairLess) + if !IsSortedFunc(data, intPairLess) { + t.Errorf("Stable didn't sort %d ints", n) + } + if !data.inOrder() { + t.Errorf("Stable wasn't stable on %d ints", n) + } +} + +func TestBinarySearch(t *testing.T) { + str1 := []string{"foo"} + str2 := []string{"ab", "ca"} + str3 := []string{"mo", "qo", "vo"} + str4 := []string{"ab", "ad", "ca", "xy"} + + // slice with repeating elements + strRepeats := []string{"ba", "ca", "da", "da", "da", "ka", "ma", "ma", "ta"} + + // slice with all element equal + strSame := []string{"xx", "xx", "xx"} + + tests := []struct { + data []string + target string + wantPos int + wantFound bool + }{ + {[]string{}, "foo", 0, false}, + {[]string{}, "", 0, false}, + + {str1, "foo", 0, true}, + {str1, "bar", 0, false}, + {str1, "zx", 1, false}, + + {str2, "aa", 0, false}, + {str2, "ab", 0, true}, + {str2, "ad", 1, false}, + {str2, "ca", 1, true}, + {str2, "ra", 2, false}, + + {str3, "bb", 0, false}, + {str3, "mo", 0, true}, + {str3, "nb", 1, false}, + {str3, "qo", 1, true}, + {str3, "tr", 2, false}, + {str3, "vo", 2, true}, + {str3, "xr", 3, false}, + + {str4, "aa", 0, false}, + {str4, "ab", 0, true}, + {str4, "ac", 1, false}, + {str4, "ad", 1, true}, + {str4, "ax", 2, false}, + {str4, "ca", 2, true}, + {str4, "cc", 3, false}, + {str4, "dd", 3, false}, + {str4, "xy", 3, true}, + {str4, "zz", 4, false}, + + {strRepeats, "da", 2, true}, + {strRepeats, "db", 5, false}, + {strRepeats, "ma", 6, true}, + {strRepeats, "mb", 8, false}, + + {strSame, "xx", 0, true}, + {strSame, "ab", 0, false}, + {strSame, "zz", 3, false}, + } + for _, tt := range tests { + t.Run(tt.target, func(t *testing.T) { + { + pos, found := BinarySearch(tt.data, tt.target) + if pos != tt.wantPos || found != tt.wantFound { + t.Errorf("BinarySearch got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound) + } + } + + { + pos, found := BinarySearchFunc(tt.data, tt.target, strings.Compare) + if pos != tt.wantPos || found != tt.wantFound { + t.Errorf("BinarySearchFunc got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound) + } + } + }) + } +} + +func TestBinarySearchInts(t *testing.T) { + data := []int{20, 30, 40, 50, 60, 70, 80, 90} + tests := []struct { + target int + wantPos int + wantFound bool + }{ + {20, 0, true}, + {23, 1, false}, + {43, 3, false}, + {80, 6, true}, + } + for _, tt := range tests { + t.Run(strconv.Itoa(tt.target), func(t *testing.T) { + { + pos, found := BinarySearch(data, tt.target) + if pos != tt.wantPos || found != tt.wantFound { + t.Errorf("BinarySearch got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound) + } + } + + { + cmp := func(a, b int) int { + return a - b + } + pos, found := BinarySearchFunc(data, tt.target, cmp) + if pos != tt.wantPos || found != tt.wantFound { + t.Errorf("BinarySearchFunc got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound) + } + } + }) + } +} + +func TestBinarySearchFunc(t *testing.T) { + data := []int{1, 10, 11, 2} // sorted lexicographically + cmp := func(a int, b string) int { + return strings.Compare(strconv.Itoa(a), b) + } + pos, found := BinarySearchFunc(data, "2", cmp) + if pos != 3 || !found { + t.Errorf("BinarySearchFunc(%v, %q, cmp) = %v, %v, want %v, %v", data, "2", pos, found, 3, true) + } +} diff --git a/mirror/x/exp/slices/zsortfunc.go b/mirror/x/exp/slices/zsortfunc.go new file mode 100644 index 0000000..2a63247 --- /dev/null +++ b/mirror/x/exp/slices/zsortfunc.go @@ -0,0 +1,479 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +// insertionSortLessFunc sorts data[a:b] using insertion sort. +func insertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + for i := a + 1; i < b; i++ { + for j := i; j > a && less(data[j], data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownLessFunc implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownLessFunc[E any](data []E, lo, hi, first int, less func(a, b E) bool) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && less(data[first+child], data[first+child+1]) { + child++ + } + if !less(data[first+root], data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownLessFunc(data, i, hi, first, less) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownLessFunc(data, lo, i, first, less) + } +} + +// pdqsortLessFunc sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) { + const maxInsertion = 12 + + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSortLessFunc(data, a, b, less) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSortLessFunc(data, a, b, less) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatternsLessFunc(data, a, b, less) + limit-- + } + + pivot, hint := choosePivotLessFunc(data, a, b, less) + if hint == decreasingHint { + reverseRangeLessFunc(data, a, b, less) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSortLessFunc(data, a, b, less) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !less(data[a-1], data[pivot]) { + mid := partitionEqualLessFunc(data, a, b, pivot, less) + a = mid + continue + } + + mid, alreadyPartitioned := partitionLessFunc(data, a, b, pivot, less) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsortLessFunc(data, a, mid, limit, less) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsortLessFunc(data, mid+1, b, limit, less) + b = mid + } + } +} + +// partitionLessFunc does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int, alreadyPartitioned bool) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && less(data[i], data[a]) { + i++ + } + for i <= j && !less(data[j], data[a]) { + j-- + } + if i > j { + data[j], data[a] = data[a], data[j] + return j, true + } + data[i], data[j] = data[j], data[i] + i++ + j-- + + for { + for i <= j && less(data[i], data[a]) { + i++ + } + for i <= j && !less(data[j], data[a]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + data[j], data[a] = data[a], data[j] + return j, false +} + +// partitionEqualLessFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqualLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !less(data[a], data[i]) { + i++ + } + for i <= j && less(data[a], data[j]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + return i +} + +// partialInsertionSortLessFunc partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !less(data[i], data[i-1]) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data[i], data[i-1] = data[i-1], data[i] + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !less(data[j], data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !less(data[j], data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + } + return false +} + +// breakPatternsLessFunc scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatternsLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data[idx], data[a+other] = data[a+other], data[idx] + } + } +} + +// choosePivotLessFunc chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacentLessFunc(data, i, &swaps, less) + j = medianAdjacentLessFunc(data, j, &swaps, less) + k = medianAdjacentLessFunc(data, k, &swaps, less) + } + // Find the median among i, j, k and stores it into j. + j = medianLessFunc(data, i, j, k, &swaps, less) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2LessFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2LessFunc[E any](data []E, a, b int, swaps *int, less func(a, b E) bool) (int, int) { + if less(data[b], data[a]) { + *swaps++ + return b, a + } + return a, b +} + +// medianLessFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianLessFunc[E any](data []E, a, b, c int, swaps *int, less func(a, b E) bool) int { + a, b = order2LessFunc(data, a, b, swaps, less) + b, c = order2LessFunc(data, b, c, swaps, less) + a, b = order2LessFunc(data, a, b, swaps, less) + return b +} + +// medianAdjacentLessFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentLessFunc[E any](data []E, a int, swaps *int, less func(a, b E) bool) int { + return medianLessFunc(data, a-1, a, a+1, swaps, less) +} + +func reverseRangeLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + i := a + j := b - 1 + for i < j { + data[i], data[j] = data[j], data[i] + i++ + j-- + } +} + +func swapRangeLessFunc[E any](data []E, a, b, n int, less func(a, b E) bool) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func stableLessFunc[E any](data []E, n int, less func(a, b E) bool) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortLessFunc(data, a, b, less) + a = b + b += blockSize + } + insertionSortLessFunc(data, a, n, less) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeLessFunc(data, a, a+blockSize, b, less) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeLessFunc(data, a, m, n, less) + } + blockSize *= 2 + } +} + +// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if less(data[h], data[a]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !less(data[m], data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !less(data[p-c], data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateLessFunc(data, start, m, end, less) + } + if a < start && start < mid { + symMergeLessFunc(data, a, start, mid, less) + } + if mid < end && end < b { + symMergeLessFunc(data, mid, end, b, less) + } +} + +// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeLessFunc(data, m-i, m, j, less) + i -= j + } else { + swapRangeLessFunc(data, m-i, m+j-i, i, less) + j -= i + } + } + // i == j + swapRangeLessFunc(data, m-i, m, i, less) +} diff --git a/mirror/x/exp/slices/zsortordered.go b/mirror/x/exp/slices/zsortordered.go new file mode 100644 index 0000000..efaa1c8 --- /dev/null +++ b/mirror/x/exp/slices/zsortordered.go @@ -0,0 +1,481 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import "golang.org/x/exp/constraints" + +// insertionSortOrdered sorts data[a:b] using insertion sort. +func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) { + for i := a + 1; i < b; i++ { + for j := i; j > a && (data[j] < data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownOrdered implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && (data[first+child] < data[first+child+1]) { + child++ + } + if !(data[first+root] < data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortOrdered[E constraints.Ordered](data []E, a, b int) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownOrdered(data, i, hi, first) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownOrdered(data, lo, i, first) + } +} + +// pdqsortOrdered sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) { + const maxInsertion = 12 + + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSortOrdered(data, a, b) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSortOrdered(data, a, b) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatternsOrdered(data, a, b) + limit-- + } + + pivot, hint := choosePivotOrdered(data, a, b) + if hint == decreasingHint { + reverseRangeOrdered(data, a, b) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSortOrdered(data, a, b) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !(data[a-1] < data[pivot]) { + mid := partitionEqualOrdered(data, a, b, pivot) + a = mid + continue + } + + mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsortOrdered(data, a, mid, limit) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsortOrdered(data, mid+1, b, limit) + b = mid + } + } +} + +// partitionOrdered does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && (data[i] < data[a]) { + i++ + } + for i <= j && !(data[j] < data[a]) { + j-- + } + if i > j { + data[j], data[a] = data[a], data[j] + return j, true + } + data[i], data[j] = data[j], data[i] + i++ + j-- + + for { + for i <= j && (data[i] < data[a]) { + i++ + } + for i <= j && !(data[j] < data[a]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + data[j], data[a] = data[a], data[j] + return j, false +} + +// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !(data[a] < data[i]) { + i++ + } + for i <= j && (data[a] < data[j]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + return i +} + +// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !(data[i] < data[i-1]) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data[i], data[i-1] = data[i-1], data[i] + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !(data[j] < data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !(data[j] < data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + } + return false +} + +// breakPatternsOrdered scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data[idx], data[a+other] = data[a+other], data[idx] + } + } +} + +// choosePivotOrdered chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacentOrdered(data, i, &swaps) + j = medianAdjacentOrdered(data, j, &swaps) + k = medianAdjacentOrdered(data, k, &swaps) + } + // Find the median among i, j, k and stores it into j. + j = medianOrdered(data, i, j, k, &swaps) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) { + if data[b] < data[a] { + *swaps++ + return b, a + } + return a, b +} + +// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int { + a, b = order2Ordered(data, a, b, swaps) + b, c = order2Ordered(data, b, c, swaps) + a, b = order2Ordered(data, a, b, swaps) + return b +} + +// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int { + return medianOrdered(data, a-1, a, a+1, swaps) +} + +func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) { + i := a + j := b - 1 + for i < j { + data[i], data[j] = data[j], data[i] + i++ + j-- + } +} + +func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func stableOrdered[E constraints.Ordered](data []E, n int) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortOrdered(data, a, b) + a = b + b += blockSize + } + insertionSortOrdered(data, a, n) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeOrdered(data, a, a+blockSize, b) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeOrdered(data, a, m, n) + } + blockSize *= 2 + } +} + +// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if data[h] < data[a] { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !(data[m] < data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !(data[p-c] < data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateOrdered(data, start, m, end) + } + if a < start && start < mid { + symMergeOrdered(data, a, start, mid) + } + if mid < end && end < b { + symMergeOrdered(data, mid, end, b) + } +} + +// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeOrdered(data, m-i, m, j) + i -= j + } else { + swapRangeOrdered(data, m-i, m+j-i, i) + j -= i + } + } + // i == j + swapRangeOrdered(data, m-i, m, i) +} diff --git a/refs/refs.go b/refs/refs.go index 9ba898f..780b6ec 100644 --- a/refs/refs.go +++ b/refs/refs.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "golang.org/x/exp/slices" + "tier.run/mirror/x/exp/slices" ) // marshlers diff --git a/refs/refs_test.go b/refs/refs_test.go index 9832f42..e622317 100644 --- a/refs/refs_test.go +++ b/refs/refs_test.go @@ -6,8 +6,8 @@ import ( "regexp" "testing" - "golang.org/x/exp/slices" "kr.dev/diff" + "tier.run/mirror/x/exp/slices" ) func TestParsePlan(t *testing.T) {