From b09c6d56832eb1718c07d74abf3bc6ae3fe4e030 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:36:04 +0200 Subject: Adding upstream version 1.1.0. Signed-off-by: Daniel Baumann --- .../slices/slices.go | 218 ++++++++ .../slices/slices_test.go | 552 +++++++++++++++++++++ .../slices/sort.go | 127 +++++ .../slices/sort_benchmark_test.go | 202 ++++++++ .../slices/sort_test.go | 285 +++++++++++ .../slices/zsortfunc.go | 479 ++++++++++++++++++ .../slices/zsortordered.go | 481 ++++++++++++++++++ 7 files changed, 2344 insertions(+) create mode 100644 dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices.go create mode 100644 dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices_test.go create mode 100644 dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort.go create mode 100644 dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_benchmark_test.go create mode 100644 dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_test.go create mode 100644 dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/zsortfunc.go create mode 100644 dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/zsortordered.go (limited to 'dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices') diff --git a/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices.go new file mode 100644 index 0000000..8a237c5 --- /dev/null +++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices.go @@ -0,0 +1,218 @@ +// 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, vs := range s { + if v == vs { + 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, v := range s { + if f(v) { + 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 +} + +// 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-i)), 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. +func Delete[S ~[]E, E any](s S, i, j int) S { + return append(s[:i], s[j:]...) +} + +// 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. +func Compact[S ~[]E, E comparable](s S) S { + if len(s) == 0 { + return s + } + i := 1 + last := s[0] + for _, v := range s[1:] { + if v != last { + s[i] = v + i++ + last = v + } + } + 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) == 0 { + return s + } + i := 1 + last := s[0] + for _, v := range s[1:] { + if !eq(v, last) { + s[i] = v + i++ + last = v + } + } + 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. Grow may modify elements of the +// slice between the length and the capacity. If n is negative or too large to +// allocate the memory, Grow panics. +func Grow[S ~[]E, E any](s S, n int) S { + return append(s, make(S, n)...)[:len(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/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices_test.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices_test.go new file mode 100644 index 0000000..efe9966 --- /dev/null +++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/slices_test.go @@ -0,0 +1,552 @@ +// 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 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) + } +} + +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 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 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) + } + } +} + +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) + } + } +} + +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 { + s []int + want []int +}{ + { + nil, + nil, + }, + { + []int{1}, + []int{1}, + }, + { + []int{1, 2, 3}, + []int{1, 2, 3}, + }, + { + []int{1, 1, 2}, + []int{1, 2}, + }, + { + []int{1, 2, 1}, + []int{1, 2, 1}, + }, + { + []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 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 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)) + } +} + +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)) + } +} diff --git a/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort.go new file mode 100644 index 0000000..c22e74b --- /dev/null +++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort.go @@ -0,0 +1,127 @@ +// 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) +} + +// SortStable 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) { + // search returns the leftmost position where f returns true, or len(x) if f + // returns false for all x. This is the insertion position for target in x, + // and could point to an element that's either == target or not. + pos := search(len(x), func(i int) bool { return x[i] >= target }) + if pos >= len(x) || x[pos] != target { + return pos, false + } else { + return pos, true + } +} + +// 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 any](x []E, target E, cmp func(E, E) int) (int, bool) { + pos := search(len(x), func(i int) bool { return cmp(x[i], target) >= 0 }) + if pos >= len(x) || cmp(x[pos], target) != 0 { + return pos, false + } else { + return pos, true + } +} + +func search(n int, f func(int) bool) int { + // Define f(-1) == false and f(n) == true. + // Invariant: f(i-1) == false, f(j) == true. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if !f(h) { + i = h + 1 // preserves f(i-1) == false + } else { + j = h // preserves f(j) == true + } + } + // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. + return i +} + +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/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_benchmark_test.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_benchmark_test.go new file mode 100644 index 0000000..88b753d --- /dev/null +++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_benchmark_test.go @@ -0,0 +1,202 @@ +// 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/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) + } +} diff --git a/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_test.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_test.go new file mode 100644 index 0000000..9a52992 --- /dev/null +++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/sort_test.go @@ -0,0 +1,285 @@ +// 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 := ints[:] + Sort(data) + if !IsSorted(data) { + t.Errorf("sorted %v", ints) + t.Errorf(" got %v", data) + } +} + +func TestSortFuncIntSlice(t *testing.T) { + data := 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 := float64s[:] + Sort(data) + if !IsSorted(data) { + t.Errorf("sorted %v", float64s) + t.Errorf(" got %v", data) + } +} + +func TestSortFloat64SliceWithNaNs(t *testing.T) { + data := float64sWithNaNs[:] + input := make([]float64, len(float64sWithNaNs)) + for i := range input { + input[i] = float64sWithNaNs[i] + } + // 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 := 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) + } + } + }) + } +} diff --git a/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/zsortfunc.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/zsortfunc.go new file mode 100644 index 0000000..2a63247 --- /dev/null +++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/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/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/zsortordered.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/slices/zsortordered.go new file mode 100644 index 0000000..efaa1c8 --- /dev/null +++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/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) +} -- cgit v1.2.3