-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Squashed commit of the following: commit 8d87787 Author: Ainar Garipov <[email protected]> Date: Fri Nov 1 12:16:54 2024 +0300 errors: fix type commit 986169f Author: Ainar Garipov <[email protected]> Date: Thu Oct 31 19:30:53 2024 +0300 all: add tests, imp code commit 0393ff0 Author: Ainar Garipov <[email protected]> Date: Thu Oct 31 18:45:47 2024 +0300 all: imp docs, tests commit ff7d161 Author: Ainar Garipov <[email protected]> Date: Thu Oct 31 17:19:00 2024 +0300 container: add sorted slice set; urlutil: add helpers
- Loading branch information
Showing
21 changed files
with
934 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package container_test | ||
|
||
import ( | ||
"math/rand" | ||
"time" | ||
) | ||
|
||
// Common sinks for benchmarks. | ||
var ( | ||
sinkBool bool | ||
) | ||
|
||
// Common constants for tests. | ||
const ( | ||
randStrLen = 8 | ||
setMaxLen = 100_000 | ||
) | ||
|
||
// newRandStrs returns a slice of random strings of length l with each string | ||
// being strLen bytes long. | ||
func newRandStrs(l, strLen int) (strs []string) { | ||
src := rand.NewSource(time.Now().UnixNano()) | ||
rng := rand.New(src) | ||
|
||
strs = make([]string, 0, l) | ||
for range l { | ||
data := make([]byte, strLen) | ||
_, _ = rng.Read(data) | ||
|
||
strs = append(strs, string(data)) | ||
} | ||
|
||
return strs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package container_test | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/AdguardTeam/golibs/container" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func BenchmarkMapSet_Add(b *testing.B) { | ||
for n := 10; n <= setMaxLen; n *= 10 { | ||
b.Run(fmt.Sprintf("%d_strings", n), func(b *testing.B) { | ||
var set *container.MapSet[string] | ||
|
||
values := newRandStrs(n, randStrLen) | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for range b.N { | ||
set = container.NewMapSet[string]() | ||
for _, v := range values { | ||
set.Add(v) | ||
} | ||
} | ||
|
||
perIter := b.Elapsed() / time.Duration(b.N) | ||
b.ReportMetric(float64(perIter)/float64(n), "ns/add") | ||
|
||
require.True(b, set.Has(values[0])) | ||
}) | ||
} | ||
|
||
// Most recent results: | ||
// goos: linux | ||
// goarch: amd64 | ||
// pkg: github.com/AdguardTeam/golibs/container | ||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics | ||
// BenchmarkMapSet_Add | ||
// BenchmarkMapSet_Add/10_strings | ||
// BenchmarkMapSet_Add/10_strings-16 702885 1514 ns/op 151.3 ns/add 491 B/op 4 allocs/op | ||
// BenchmarkMapSet_Add/100_strings | ||
// BenchmarkMapSet_Add/100_strings-16 66829 15960 ns/op 159.6 ns/add 5615 B/op 11 allocs/op | ||
// BenchmarkMapSet_Add/1000_strings | ||
// BenchmarkMapSet_Add/1000_strings-16 7400 206750 ns/op 206.7 ns/add 85713 B/op 36 allocs/op | ||
// BenchmarkMapSet_Add/10000_strings | ||
// BenchmarkMapSet_Add/10000_strings-16 592 1982110 ns/op 198.2 ns/add 676473 B/op 216 allocs/op | ||
// BenchmarkMapSet_Add/100000_strings | ||
// BenchmarkMapSet_Add/100000_strings-16 49 23063097 ns/op 230.6 ns/add 5597995 B/op 3903 allocs/op | ||
} | ||
|
||
func BenchmarkMapSet_Has(b *testing.B) { | ||
for n := 10; n <= setMaxLen; n *= 10 { | ||
b.Run(fmt.Sprintf("%d_strings", n), func(b *testing.B) { | ||
values := newRandStrs(n, randStrLen) | ||
set := container.NewMapSet(values...) | ||
value := values[n/2] | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for range b.N { | ||
sinkBool = set.Has(value) | ||
} | ||
|
||
require.True(b, sinkBool) | ||
}) | ||
} | ||
|
||
// Most recent results: | ||
// goos: linux | ||
// goarch: amd64 | ||
// pkg: github.com/AdguardTeam/golibs/container | ||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics | ||
// BenchmarkMapSet_Has | ||
// BenchmarkMapSet_Has/10_strings | ||
// BenchmarkMapSet_Has/10_strings-16 171413164 6.886 ns/op 0 B/op 0 allocs/op | ||
// BenchmarkMapSet_Has/100_strings | ||
// BenchmarkMapSet_Has/100_strings-16 166819746 6.607 ns/op 0 B/op 0 allocs/op | ||
// BenchmarkMapSet_Has/1000_strings | ||
// BenchmarkMapSet_Has/1000_strings-16 179336127 6.870 ns/op 0 B/op 0 allocs/op | ||
// BenchmarkMapSet_Has/10000_strings | ||
// BenchmarkMapSet_Has/10000_strings-16 164002748 6.831 ns/op 0 B/op 0 allocs/op | ||
// BenchmarkMapSet_Has/100000_strings | ||
// BenchmarkMapSet_Has/100000_strings-16 170170257 6.518 ns/op 0 B/op 0 allocs/op | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package container | ||
|
||
import ( | ||
"cmp" | ||
"fmt" | ||
"slices" | ||
) | ||
|
||
// SortedSliceSet is a simple set implementation that has a sorted set of values | ||
// as its underlying storage. | ||
// | ||
// TODO(a.garipov): Consider relaxing the type requirement or adding a version | ||
// with a comparison function. | ||
type SortedSliceSet[T cmp.Ordered] struct { | ||
elems []T | ||
} | ||
|
||
// NewSortedSliceSet returns a new *SortedSliceSet. elems must not be modified | ||
// after calling NewSortedSliceSet. | ||
func NewSortedSliceSet[T cmp.Ordered](elems ...T) (set *SortedSliceSet[T]) { | ||
slices.Sort(elems) | ||
|
||
return &SortedSliceSet[T]{ | ||
elems: elems, | ||
} | ||
} | ||
|
||
// Add adds v to set. | ||
func (set *SortedSliceSet[T]) Add(v T) { | ||
i, ok := slices.BinarySearch(set.elems, v) | ||
if !ok { | ||
set.elems = slices.Insert(set.elems, i, v) | ||
} | ||
} | ||
|
||
// Clear clears set in a way that retains the internal storage for later reuse | ||
// to reduce allocations. Calling Clear on a nil set has no effect, just like a | ||
// clear on a nil slice doesn't. | ||
func (set *SortedSliceSet[T]) Clear() { | ||
if set != nil { | ||
clear(set.elems) | ||
set.elems = set.elems[:0] | ||
} | ||
} | ||
|
||
// Clone returns a clone of set. If set is nil, clone is nil. | ||
// | ||
// NOTE: It calls [slices.Clone] on the underlying storage, so these elements | ||
// are cloned shallowly. | ||
func (set *SortedSliceSet[T]) Clone() (clone *SortedSliceSet[T]) { | ||
if set == nil { | ||
return nil | ||
} | ||
|
||
return NewSortedSliceSet(slices.Clone(set.elems)...) | ||
} | ||
|
||
// Delete deletes v from set. | ||
func (set *SortedSliceSet[T]) Delete(v T) { | ||
i, ok := slices.BinarySearch(set.elems, v) | ||
if ok { | ||
set.elems = slices.Delete(set.elems, i, i+1) | ||
} | ||
} | ||
|
||
// Equal returns true if set is equal to other. set and other may be nil; Equal | ||
// returns true if both are nil, but a nil *SortedSliceSet is not equal to a | ||
// non-nil empty one. | ||
func (set *SortedSliceSet[T]) Equal(other *SortedSliceSet[T]) (ok bool) { | ||
if set == nil || other == nil { | ||
return set == other | ||
} | ||
|
||
return slices.Equal(set.elems, other.elems) | ||
} | ||
|
||
// Has returns true if v is in set. Calling Has on a nil set returns false, | ||
// just like iterating over a nil or empty slice does. | ||
func (set *SortedSliceSet[T]) Has(v T) (ok bool) { | ||
if set == nil { | ||
return false | ||
} | ||
|
||
_, ok = slices.BinarySearch(set.elems, v) | ||
|
||
return ok | ||
} | ||
|
||
// Len returns the length of set. A nil set has a length of zero, just like an | ||
// nil or empty slice. | ||
func (set *SortedSliceSet[T]) Len() (n int) { | ||
if set == nil { | ||
return 0 | ||
} | ||
|
||
return len(set.elems) | ||
} | ||
|
||
// Range calls f with each value of set in their sorted order. If cont is | ||
// false, Range stops the iteration. Calling Range on a nil *SortedSliceSet has | ||
// no effect. | ||
func (set *SortedSliceSet[T]) Range(f func(v T) (cont bool)) { | ||
if set == nil { | ||
return | ||
} | ||
|
||
for _, v := range set.elems { | ||
if !f(v) { | ||
break | ||
} | ||
} | ||
} | ||
|
||
// type check | ||
var _ fmt.Stringer = (*SortedSliceSet[int])(nil) | ||
|
||
// String implements the [fmt.Stringer] interface for *SortedSliceSet. Calling | ||
// String on a nil *SortedSliceSet does not panic. | ||
func (set *SortedSliceSet[T]) String() (s string) { | ||
return fmt.Sprintf("%v", set.Values()) | ||
} | ||
|
||
// Values returns the underlying slice of set. values must not be modified. | ||
// Values returns nil if set is nil. | ||
func (set *SortedSliceSet[T]) Values() (values []T) { | ||
if set == nil { | ||
return nil | ||
} | ||
|
||
return set.elems | ||
} |
Oops, something went wrong.