Compare commits

...

5 Commits

Author SHA1 Message Date
Raul Jordan
d64197b33d Merge branch 'constraints-slices' of github.com:prysmaticlabs/prysm into constraints-slices 2022-05-26 19:39:03 -04:00
Raul Jordan
0cc87d5d2c gaz 2022-05-26 19:38:57 -04:00
Raul Jordan
df4dde5104 Merge branch 'develop' into constraints-slices 2022-05-26 23:38:22 +00:00
Raul Jordan
420e2c6208 generic versions 2022-05-26 19:35:49 -04:00
Raul Jordan
ec955a14ee constraints slice generic tools 2022-05-26 17:49:19 -04:00
5 changed files with 463 additions and 4 deletions

View File

@@ -0,0 +1,8 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["constraints.go"],
importpath = "github.com/prysmaticlabs/prysm/container/constraints",
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,46 @@
// Package constraints defines a set of useful constraints to be used
// with type parameters.
package constraints
// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
// Complex is a constraint that permits any complex numeric type.
// If future releases of Go add new predeclared complex numeric types,
// this constraint will be modified to include them.
type Complex interface {
~complex64 | ~complex128
}
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered interface {
Integer | Float | ~string
}

View File

@@ -8,7 +8,10 @@ go_library(
],
importpath = "github.com/prysmaticlabs/prysm/container/slice",
visibility = ["//visibility:public"],
deps = ["//consensus-types/primitives:go_default_library"],
deps = [
"//consensus-types/primitives:go_default_library",
"//container/constraints:go_default_library",
],
)
go_test(
@@ -18,5 +21,6 @@ go_test(
deps = [
":go_default_library",
"//consensus-types/primitives:go_default_library",
"//crypto/rand:go_default_library",
],
)

View File

@@ -4,8 +4,150 @@ import (
"strings"
types "github.com/prysmaticlabs/prysm/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/container/constraints"
)
// Subset returns true if the first slice is
// completely contained in the second slice with time
// complexity of approximately o(n).
func Subset[T constraints.Ordered](a, b []T) bool {
if len(a) > len(b) {
return false
}
set := make(map[T]uint64, len(b))
for _, v := range b {
set[v]++
}
for _, v := range a {
if count, found := set[v]; !found {
return false
} else if count < 1 {
return false
} else {
set[v] = count - 1
}
}
return true
}
// Intersection of any number of slices with time
// complexity of approximately O(n) leveraging a map to
// check for element existence off by a constant factor
// of underlying map efficiency.
func Intersection[T constraints.Ordered](s ...[]T) []T {
if len(s) == 0 {
return []T{}
}
if len(s) == 1 {
return s[0]
}
intersect := make([]T, 0)
m := make(map[T]int)
for _, k := range s[0] {
m[k] = 1
}
for i, num := 1, len(s); i < num; i++ {
for _, k := range s[i] {
// Increment and check only if item is present in both, and no increment has happened yet.
if _, found := m[k]; found && i == m[k] {
m[k]++
if m[k] == num {
intersect = append(intersect, k)
}
}
}
}
return intersect
}
// Union of any number of uint64 slices with time
// complexity of approximately O(n) leveraging a map to
// check for element existence off by a constant factor
// of underlying map efficiency.
func Union[T constraints.Ordered](s ...[]T) []T {
if len(s) == 0 {
return []T{}
}
if len(s) == 1 {
return s[0]
}
set := s[0]
m := make(map[T]bool)
for i := 1; i < len(s); i++ {
a := s[i-1]
b := s[i]
for j := 0; j < len(a); j++ {
m[a[j]] = true
}
for j := 0; j < len(b); j++ {
if _, found := m[b[j]]; !found {
set = append(set, b[j])
}
}
}
return set
}
// Uniq returns a slice with only unique
// values from the provided list of indices.
func Uniq[T constraints.Ordered](a []T) []T {
// Remove duplicates indices.
intMap := map[T]bool{}
cleanedIndices := make([]T, 0, len(a))
for _, idx := range a {
if intMap[idx] {
continue
}
intMap[idx] = true
cleanedIndices = append(cleanedIndices, idx)
}
return cleanedIndices
}
// IsSorted verifies if a slice is sorted in ascending order.
func IsSorted[T constraints.Ordered](a []T) bool {
if len(a) == 0 || len(a) == 1 {
return true
}
for i := 1; i < len(a); i++ {
if a[i-1] > a[i] {
return false
}
}
return true
}
// NotIn returns the items in slice b that are
// not in slice a with time complexity of approximately
// O(n) leveraging a map to check for element existence
// off by a constant factor of underlying map efficiency.
func NotIn[T constraints.Ordered](a, b []T) []T {
set := make([]T, 0)
m := make(map[T]bool)
for i := 0; i < len(a); i++ {
m[a[i]] = true
}
for i := 0; i < len(b); i++ {
if _, found := m[b[i]]; !found {
set = append(set, b[i])
}
}
return set
}
// IsIn returns true if a is in b and False otherwise.
func IsIn[T constraints.Ordered](a T, b []T) bool {
for _, v := range b {
if a == v {
return true
}
}
return false
}
// SubsetUint64 returns true if the first array is
// completely contained in the second array with time
// complexity of approximately o(n).

View File

@@ -7,9 +7,10 @@ import (
types "github.com/prysmaticlabs/prysm/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/container/slice"
"github.com/prysmaticlabs/prysm/crypto/rand"
)
func TestSubsetUint64(t *testing.T) {
func TestSubset(t *testing.T) {
testCases := []struct {
setA []uint64
setB []uint64
@@ -30,7 +31,28 @@ func TestSubsetUint64(t *testing.T) {
}
}
func TestIntersectionUint64(t *testing.T) {
func TestSubset_Generic(t *testing.T) {
testCases := []struct {
setA []uint64
setB []uint64
out bool
}{
{[]uint64{1}, []uint64{1, 2, 3, 4}, true},
{[]uint64{1, 2, 3, 4}, []uint64{1, 2, 3, 4}, true},
{[]uint64{1, 1}, []uint64{1, 2, 3, 4}, false},
{[]uint64{}, []uint64{1}, true},
{[]uint64{1}, []uint64{}, false},
{[]uint64{1, 2, 3, 4, 5}, []uint64{1, 2, 3, 4}, false},
}
for _, tt := range testCases {
result := slice.Subset[uint64](tt.setA, tt.setB)
if result != tt.out {
t.Errorf("%v, got %v, want %v", tt.setA, result, tt.out)
}
}
}
func TestIntersection(t *testing.T) {
testCases := []struct {
setA []uint64
setB []uint64
@@ -76,7 +98,53 @@ func TestIntersectionUint64(t *testing.T) {
}
}
func TestIsSortedUint64(t *testing.T) {
func TestIntersection_Generic(t *testing.T) {
testCases := []struct {
setA []uint64
setB []uint64
setC []uint64
out []uint64
}{
{[]uint64{2, 3, 5}, []uint64{3}, []uint64{3}, []uint64{3}},
{[]uint64{2, 3, 5}, []uint64{3, 5}, []uint64{5}, []uint64{5}},
{[]uint64{2, 3, 5}, []uint64{3, 5}, []uint64{3, 5}, []uint64{3, 5}},
{[]uint64{2, 3, 5}, []uint64{5, 3, 2}, []uint64{3, 2, 5}, []uint64{2, 3, 5}},
{[]uint64{3, 2, 5}, []uint64{5, 3, 2}, []uint64{3, 2, 5}, []uint64{2, 3, 5}},
{[]uint64{3, 3, 5}, []uint64{5, 3, 2}, []uint64{3, 2, 5}, []uint64{3, 5}},
{[]uint64{2, 3, 5}, []uint64{2, 3, 5}, []uint64{2, 3, 5}, []uint64{2, 3, 5}},
{[]uint64{2, 3, 5}, []uint64{}, []uint64{}, []uint64{}},
{[]uint64{2, 3, 5}, []uint64{2, 3, 5}, []uint64{}, []uint64{}},
{[]uint64{2, 3}, []uint64{2, 3, 5}, []uint64{5}, []uint64{}},
{[]uint64{2, 2, 2}, []uint64{2, 2, 2}, []uint64{}, []uint64{}},
{[]uint64{}, []uint64{2, 3, 5}, []uint64{}, []uint64{}},
{[]uint64{}, []uint64{}, []uint64{}, []uint64{}},
{[]uint64{1}, []uint64{1}, []uint64{}, []uint64{}},
{[]uint64{1, 1, 1}, []uint64{1, 1}, []uint64{1, 2, 3}, []uint64{1}},
}
for _, tt := range testCases {
setA := append([]uint64{}, tt.setA...)
setB := append([]uint64{}, tt.setB...)
setC := append([]uint64{}, tt.setC...)
result := slice.Intersection[uint64](setA, setB, setC)
sort.Slice(result, func(i, j int) bool {
return result[i] < result[j]
})
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
if !reflect.DeepEqual(setA, tt.setA) {
t.Errorf("slice modified, got %v, want %v", setA, tt.setA)
}
if !reflect.DeepEqual(setB, tt.setB) {
t.Errorf("slice modified, got %v, want %v", setB, tt.setB)
}
if !reflect.DeepEqual(setC, tt.setC) {
t.Errorf("slice modified, got %v, want %v", setC, tt.setC)
}
}
}
func TestIsSorted(t *testing.T) {
testCases := []struct {
setA []uint64
out bool
@@ -94,6 +162,24 @@ func TestIsSortedUint64(t *testing.T) {
}
}
func TestIsSorted_Generic(t *testing.T) {
testCases := []struct {
setA []uint64
out bool
}{
{[]uint64{1, 2, 3}, true},
{[]uint64{3, 1, 3}, false},
{[]uint64{1}, true},
{[]uint64{}, true},
}
for _, tt := range testCases {
result := slice.IsSorted[uint64](tt.setA)
if result != tt.out {
t.Errorf("got %v, want %v", result, tt.out)
}
}
}
func TestIntersectionInt64(t *testing.T) {
testCases := []struct {
setA []int64
@@ -205,6 +291,39 @@ func TestUnionInt64(t *testing.T) {
}
}
func TestUnion_Generic(t *testing.T) {
testCases := []struct {
setA []uint64
setB []uint64
out []uint64
}{
{[]uint64{2, 3, 5}, []uint64{4, 6}, []uint64{2, 3, 5, 4, 6}},
{[]uint64{2, 3, 5}, []uint64{3, 5}, []uint64{2, 3, 5}},
{[]uint64{2, 3, 5}, []uint64{2, 3, 5}, []uint64{2, 3, 5}},
{[]uint64{2, 3, 5}, []uint64{}, []uint64{2, 3, 5}},
{[]uint64{}, []uint64{2, 3, 5}, []uint64{2, 3, 5}},
{[]uint64{}, []uint64{}, []uint64{}},
{[]uint64{1}, []uint64{1}, []uint64{1}},
}
for _, tt := range testCases {
result := slice.Union[uint64](tt.setA, tt.setB)
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
}
items := [][]uint64{
{3, 4, 5},
{6, 7, 8},
{9, 10, 11},
}
variadicResult := slice.Union[uint64](items...)
want := []uint64{3, 4, 5, 6, 7, 8, 9, 10, 11}
if !reflect.DeepEqual(want, variadicResult) {
t.Errorf("Received %v, wanted %v", variadicResult, want)
}
}
func TestCleanUint64(t *testing.T) {
testCases := []struct {
in []uint64
@@ -225,6 +344,26 @@ func TestCleanUint64(t *testing.T) {
}
}
func TestUniq_Generic(t *testing.T) {
testCases := []struct {
in []uint64
out []uint64
}{
{[]uint64{2, 4, 4, 6, 6}, []uint64{2, 4, 6}},
{[]uint64{3, 5, 5}, []uint64{3, 5}},
{[]uint64{2, 2, 2}, []uint64{2}},
{[]uint64{1, 4, 5, 9, 9}, []uint64{1, 4, 5, 9}},
{[]uint64{}, []uint64{}},
{[]uint64{1}, []uint64{1}},
}
for _, tt := range testCases {
result := slice.Uniq[uint64](tt.in)
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
}
}
func TestNotUint64(t *testing.T) {
testCases := []struct {
setA []uint64
@@ -247,6 +386,28 @@ func TestNotUint64(t *testing.T) {
}
}
func TestNotIn_Generic(t *testing.T) {
testCases := []struct {
setA []uint64
setB []uint64
out []uint64
}{
{[]uint64{4, 6}, []uint64{2, 3, 5, 4, 6}, []uint64{2, 3, 5}},
{[]uint64{3, 5}, []uint64{2, 3, 5}, []uint64{2}},
{[]uint64{2, 3, 5}, []uint64{2, 3, 5}, []uint64{}},
{[]uint64{2}, []uint64{2, 3, 5}, []uint64{3, 5}},
{[]uint64{}, []uint64{2, 3, 5}, []uint64{2, 3, 5}},
{[]uint64{}, []uint64{}, []uint64{}},
{[]uint64{1}, []uint64{1}, []uint64{}},
}
for _, tt := range testCases {
result := slice.NotIn[uint64](tt.setA, tt.setB)
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
}
}
func TestNotInt64(t *testing.T) {
testCases := []struct {
setA []int64
@@ -309,6 +470,26 @@ func TestIsInInt64(t *testing.T) {
}
}
func TestIsIn_Generic(t *testing.T) {
testCases := []struct {
a uint64
b []uint64
result bool
}{
{0, []uint64{}, false},
{0, []uint64{0}, true},
{4, []uint64{2, 3, 5, 4, 6}, true},
{100, []uint64{2, 3, 5, 4, 6}, false},
}
for _, tt := range testCases {
result := slice.IsIn[uint64](tt.a, tt.b)
if result != tt.result {
t.Errorf("IsIn(%d, %v)=%v, wanted: %v",
tt.a, tt.b, result, tt.result)
}
}
}
func TestUnionByteSlices(t *testing.T) {
testCases := []struct {
setA [][]byte
@@ -587,3 +768,81 @@ func TestIsInSlots(t *testing.T) {
}
}
}
func BenchmarkIntersection_Specific(b *testing.B) {
b.StopTimer()
sets := setupSlices()
b.StartTimer()
for i := 0; i < b.N; i++ {
slice.IntersectionUint64(sets...)
}
}
func BenchmarkIntersection_Generic(b *testing.B) {
b.StopTimer()
sets := setupSlices()
b.StartTimer()
for i := 0; i < b.N; i++ {
slice.Intersection[uint64](sets...)
}
}
func BenchmarkUnion_Specific(b *testing.B) {
b.StopTimer()
sets := setupSlices()
b.StartTimer()
for i := 0; i < b.N; i++ {
slice.UnionUint64(sets...)
}
}
func BenchmarkUnion_Generic(b *testing.B) {
b.StopTimer()
sets := setupSlices()
b.StartTimer()
for i := 0; i < b.N; i++ {
slice.Union[uint64](sets...)
}
}
func BenchmarkUniq_Specific(b *testing.B) {
b.StopTimer()
input := make([]uint64, 0, 1000)
gen := rand.NewGenerator()
for i := 0; i < 1000; i++ {
n := gen.Uint64()
input = append(input, n, n)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
slice.SetUint64(input)
}
}
func BenchmarkUniq_Generic(b *testing.B) {
b.StopTimer()
input := make([]uint64, 0, 1000)
gen := rand.NewGenerator()
for i := 0; i < 1000; i++ {
n := gen.Uint64()
input = append(input, n, n)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
slice.Uniq[uint64](input)
}
}
func setupSlices() [][]uint64 {
sets := make([][]uint64, 1000)
gen := rand.NewGenerator()
for i := 0; i < len(sets); i++ {
setSize := gen.Intn(100)
set := make([]uint64, setSize)
for j := 0; j < setSize; j++ {
set[j] = uint64(j)
}
sets[i] = set
}
return sets
}