├── go.mod ├── doc.go ├── swapper_new.go ├── swapper_old.go ├── die.go ├── .github └── workflows │ └── go.yml ├── math_test.go ├── deletion_test.go ├── math.go ├── errors.go ├── slice.go ├── README.md ├── deletion.go ├── concurrency.go ├── slice_test.go └── concurrency_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/unixpickle/essentials 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package essentials provides functions that I find 2 | // myself needing on a day to day basis in pretty much 3 | // every Go program I write. 4 | package essentials 5 | -------------------------------------------------------------------------------- /swapper_new.go: -------------------------------------------------------------------------------- 1 | //+build go1.8 2 | 3 | package essentials 4 | 5 | import "reflect" 6 | 7 | func swapper(list interface{}) func(i, j int) { 8 | return reflect.Swapper(list) 9 | } 10 | -------------------------------------------------------------------------------- /swapper_old.go: -------------------------------------------------------------------------------- 1 | //+build !go1.8 2 | 3 | package essentials 4 | 5 | import "reflect" 6 | 7 | func swapper(list interface{}) func(i, j int) { 8 | val := reflect.ValueOf(list) 9 | return func(i, j int) { 10 | val1 := val.Index(i) 11 | val2 := val.Index(j) 12 | backup := val1.Interface() 13 | val1.Set(val2) 14 | val2.Set(reflect.ValueOf(backup)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /die.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // Die prints the arguments to standard error in a style 9 | // like the one used by fmt.Println, then exits with an 10 | // error status code. 11 | func Die(args ...interface{}) { 12 | fmt.Fprintln(os.Stderr, args...) 13 | os.Exit(1) 14 | } 15 | 16 | // Must dies with the error if it is non-nil. 17 | // If the error is nil, Must is a no-op. 18 | func Must(err error) { 19 | if err != nil { 20 | Die(err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | 29 | - name: Test 30 | run: go test -v ./... 31 | -------------------------------------------------------------------------------- /math_test.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import "testing" 4 | 5 | func TestRound(t *testing.T) { 6 | ins := []float64{ 7 | 2.33646, 8 | -3.25800, 9 | 3.95544, 10 | 4.41466, 11 | -9.20760, 12 | -1.76616, 13 | 4.91430, 14 | -11.16655, 15 | -3.72920, 16 | 3.41797, 17 | 2.31483, 18 | -7.12645, 19 | 0.45609, 20 | -15.44078, 21 | -12.33272, 22 | 7.08801, 23 | -9.69377, 24 | -4.42842, 25 | -8.67027, 26 | 12.98316, 27 | 2.5, 28 | -2.5, 29 | 3.5, 30 | -3.5, 31 | } 32 | outs := []float64{ 33 | 2, 34 | -3, 35 | 4, 36 | 4, 37 | -9, 38 | -2, 39 | 5, 40 | -11, 41 | -4, 42 | 3, 43 | 2, 44 | -7, 45 | 0, 46 | -15, 47 | -12, 48 | 7, 49 | -10, 50 | -4, 51 | -9, 52 | 13, 53 | 3, 54 | -3, 55 | 4, 56 | -4, 57 | } 58 | for i, in := range ins { 59 | actual := Round(in) 60 | expected := outs[i] 61 | if actual != expected { 62 | t.Errorf("round(%f) gave %f but should give %f", in, actual, expected) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /deletion_test.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import "testing" 4 | 5 | func TestUnorderedDelete(t *testing.T) { 6 | slice := []int{1, 2, 3, 4, 5, 6} 7 | UnorderedDelete(&slice, 3) 8 | if !slicesEqual(slice, []int{1, 2, 3, 6, 5}) { 9 | t.Fatalf("unexpected slice: %v", slice) 10 | } 11 | UnorderedDelete(&slice, 4) 12 | if !slicesEqual(slice, []int{1, 2, 3, 6}) { 13 | t.Fatalf("unexpected slice: %v", slice) 14 | } 15 | } 16 | 17 | func TestOrderedDelete(t *testing.T) { 18 | slice := []int{1, 2, 3, 4, 5, 6} 19 | OrderedDelete(&slice, 3) 20 | if !slicesEqual(slice, []int{1, 2, 3, 5, 6}) { 21 | t.Fatalf("unexpected slice: %v", slice) 22 | } 23 | OrderedDelete(&slice, 4) 24 | if !slicesEqual(slice, []int{1, 2, 3, 5}) { 25 | t.Fatalf("unexpected slice: %v", slice) 26 | } 27 | } 28 | 29 | func slicesEqual(s1, s2 []int) bool { 30 | if len(s1) != len(s2) { 31 | return false 32 | } 33 | for i, x := range s1 { 34 | if s2[i] != x { 35 | return false 36 | } 37 | } 38 | return true 39 | } 40 | -------------------------------------------------------------------------------- /math.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import "math" 4 | 5 | // MaxInt computes the maximum of the arguments. 6 | // If no arguments are provided, 0 is returned. 7 | func MaxInt(ns ...int) int { 8 | if len(ns) == 0 { 9 | return 0 10 | } 11 | max := ns[0] 12 | for _, x := range ns[1:] { 13 | if x > max { 14 | max = x 15 | } 16 | } 17 | return max 18 | } 19 | 20 | // MinInt computes the minimum of the arguments. 21 | // If no arguments are provided, 0 is returned. 22 | func MinInt(ns ...int) int { 23 | if len(ns) == 0 { 24 | return 0 25 | } 26 | min := ns[0] 27 | for _, x := range ns[1:] { 28 | if x < min { 29 | min = x 30 | } 31 | } 32 | return min 33 | } 34 | 35 | // AbsInt computes the absolute value of an int. 36 | func AbsInt(n int) int { 37 | if n < 0 { 38 | return -n 39 | } 40 | return n 41 | } 42 | 43 | // Round rounds to the nearest whole number. 44 | // 45 | // When x is the same distance from two different whole 46 | // numbers, the one further from zero is selected. 47 | func Round(x float64) float64 { 48 | if x < 0 { 49 | return math.Ceil(x - 0.5) 50 | } else { 51 | return math.Floor(x + 0.5) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | // CtxError is an error with some added context. 4 | type CtxError struct { 5 | Context string 6 | Original error 7 | } 8 | 9 | // Cause returns the original error. 10 | func (c *CtxError) Cause() error { 11 | return c.Original 12 | } 13 | 14 | // Unwrap returns the original error. 15 | func (c *CtxError) Unwrap() error { 16 | return c.Original 17 | } 18 | 19 | // AddCtx creates a *CtxError by adding some context 20 | // to an existing error. 21 | // If the original error is nil, then this returns nil. 22 | func AddCtx(ctx string, err error) error { 23 | if err == nil { 24 | return nil 25 | } 26 | return &CtxError{Context: ctx, Original: err} 27 | } 28 | 29 | // AddCtxTo is like doing 30 | // 31 | // *err = AddCtx(ctx, *err) 32 | // 33 | // It is useful for adding context to a named return 34 | // argument, like in: 35 | // 36 | // func MyMethod() (err error) { 37 | // defer essentials.AddCtxTo("MyMethod", &err) 38 | // // Code here... 39 | // } 40 | func AddCtxTo(ctx string, err *error) { 41 | *err = AddCtx(ctx, *err) 42 | } 43 | 44 | // Error returns an error message with added context. 45 | // The message is of the form "Context: Original.Error()". 46 | func (c *CtxError) Error() string { 47 | return c.Context + ": " + c.Original.Error() 48 | } 49 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | ) 7 | 8 | // Reverse reverses a slice in-place. 9 | func Reverse(slice interface{}) { 10 | size := reflect.ValueOf(slice).Len() 11 | sw := swapper(slice) 12 | for i := 0; i < size/2; i++ { 13 | sw(i, size-i-1) 14 | } 15 | } 16 | 17 | // Contains checks if a slice contains a value. 18 | // Comparisons are performed using the == operator. 19 | func Contains(slice, value interface{}) bool { 20 | sliceVal := reflect.ValueOf(slice) 21 | for i := 0; i < sliceVal.Len(); i++ { 22 | if sliceVal.Index(i).Interface() == value { 23 | return true 24 | } 25 | } 26 | return false 27 | } 28 | 29 | // VoodooSort sorts the list using the comparator, while 30 | // simultaneously re-ordering a set of other lists to 31 | // match the re-ordering of the sorted list. 32 | // In a sense, what is done to the sorted list is also 33 | // done to the other lists, making the sorted list like a 34 | // voodoo doll. 35 | func VoodooSort(slice interface{}, less func(i, j int) bool, other ...interface{}) { 36 | vs := &voodooSorter{ 37 | length: reflect.ValueOf(slice).Len(), 38 | swappers: []func(i, j int){swapper(slice)}, 39 | less: less, 40 | } 41 | for _, o := range other { 42 | vs.swappers = append(vs.swappers, swapper(o)) 43 | } 44 | sort.Sort(vs) 45 | } 46 | 47 | type voodooSorter struct { 48 | length int 49 | swappers []func(i, j int) 50 | less func(i, j int) bool 51 | } 52 | 53 | func (v *voodooSorter) Len() int { 54 | return v.length 55 | } 56 | 57 | func (v *voodooSorter) Swap(i, j int) { 58 | for _, s := range v.swappers { 59 | s(i, j) 60 | } 61 | } 62 | 63 | func (v *voodooSorter) Less(i, j int) bool { 64 | return v.less(i, j) 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # essentials 2 | 3 | There are some simple things that should be built-in to Go. As I find such things, I'll add them to this package. See the [GoDoc](https://godoc.org/github.com/unixpickle/essentials) for more. I will try to document some of the package's functionality here in the README, but not everything. 4 | 5 | # Slice deletion 6 | 7 | Deleting elements from slices in Go is annoying and error prone. With `essentials`, the hard work is done for you: 8 | 9 | ```go 10 | mySlice := []int{1, 2, 3, 4} 11 | essentials.OrderedDelete(&mySlice, 2) 12 | fmt.Println(mySlice) // [1 2 4] 13 | ``` 14 | 15 | If you don't care about the order of your slice, then `essentials.UnorderedDelete` is faster and will also do the job. 16 | 17 | # The Die API 18 | 19 | This API is useful for CLI apps where you want to exit with an error code in several places. Take this for example: 20 | 21 | ```go 22 | if dataFile == "" { 23 | fmt.Fprintln(os.Stderr, "Missing -data flag. See -help for more info.") 24 | os.Exit(1) 25 | } 26 | 27 | log.Println("Loading encoder...") 28 | var enc tweetenc.Encoder 29 | if err := serializer.LoadAny(encFile, &enc.Block); err != nil { 30 | fmt.Fprintln(os.Stderr, "Load encoder:", err) 31 | os.Exit(1) 32 | } 33 | 34 | dataReader, err := os.Open(dataFile) 35 | if err != nil { 36 | fmt.Fprintln(os.Stderr, "Open data:", err) 37 | os.Exit(1) 38 | } 39 | ``` 40 | 41 | In three different places, I print to standard error and then exit with a status code. It would be so much less typing to do this: 42 | 43 | ```go 44 | if dataFile == "" { 45 | essentials.Die("Missing -data flag. See -help for more info.") 46 | } 47 | 48 | log.Println("Loading encoder...") 49 | var enc tweetenc.Encoder 50 | if err := serializer.LoadAny(encFile, &enc.Block); err != nil { 51 | essentials.Die("Load encoder:", err) 52 | } 53 | 54 | dataReader, err := os.Open(dataFile) 55 | if err != nil { 56 | essentials.Die("Open data:", err) 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /deletion.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import "reflect" 4 | 5 | // UnorderedDelete removes the indexed element from the 6 | // slice, potentially changing the order of the slice in 7 | // the process. 8 | // 9 | // The slicePtr argument must be a pointer to a slice. 10 | // 11 | // This performs the deletion in O(1) time, at the expense 12 | // of re-ordering the set. 13 | // For an order-preserving deletion, see OrderedDelete. 14 | func UnorderedDelete(slicePtr interface{}, idx int) { 15 | slicePtrVal := reflect.ValueOf(slicePtr) 16 | if slicePtrVal.Type().Kind() != reflect.Ptr || 17 | slicePtrVal.Type().Elem().Kind() != reflect.Slice { 18 | panic("first argument must be slice pointer") 19 | } 20 | slice := slicePtrVal.Elem() 21 | if idx < 0 || idx >= slice.Len() { 22 | panic("index out of range") 23 | } 24 | slice.Index(idx).Set(slice.Index(slice.Len() - 1)) 25 | shrinkSlice(slice) 26 | } 27 | 28 | // OrderedDelete removes the indexed element from the 29 | // slice and moves the following elements to fill its 30 | // place. 31 | // 32 | // The slicePtr argument must be a pointer to a slice. 33 | // 34 | // This performs the deletion in O(N) time, with the 35 | // benefit that it preserves the order of the slice. 36 | // For a deletion that ignores order, see UnorderedDelete. 37 | func OrderedDelete(slicePtr interface{}, idx int) { 38 | slicePtrVal := reflect.ValueOf(slicePtr) 39 | if slicePtrVal.Type().Kind() != reflect.Ptr || 40 | slicePtrVal.Type().Elem().Kind() != reflect.Slice { 41 | panic("first argument must be slice pointer") 42 | } 43 | slice := slicePtrVal.Elem() 44 | if idx < 0 || idx >= slice.Len() { 45 | panic("index out of range") 46 | } 47 | reflect.Copy(slice.Slice(idx, slice.Len()-1), slice.Slice(idx+1, slice.Len())) 48 | shrinkSlice(slice) 49 | } 50 | 51 | func shrinkSlice(slice reflect.Value) { 52 | // Zero last element to prevent memory leak. 53 | lastElem := slice.Index(slice.Len() - 1) 54 | lastElem.Set(reflect.Zero(lastElem.Type())) 55 | 56 | slice.Set(slice.Slice(0, slice.Len()-1)) 57 | } 58 | -------------------------------------------------------------------------------- /concurrency.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | // ConcurrentMap calls f for every integer in [0, n). 10 | // 11 | // If maxGos is 0, then GOMAXPROCS goroutines are started. 12 | // Otherwise, up to maxGos goroutines are used. 13 | func ConcurrentMap(maxGos, n int, f func(i int)) { 14 | StatefulConcurrentMap(maxGos, n, func() func(int) { 15 | return f 16 | }) 17 | } 18 | 19 | // StatefulConcurrentMap is like ConcurrentMap, but it 20 | // calls g once per Goroutine, and then calls the result 21 | // of g with every index on that Goroutine. 22 | // Note that g may be called concurrently from multiple 23 | // Goroutines at once. 24 | // 25 | // This can be useful if Goroutines each have their own 26 | // local set of resources that they can reuse. 27 | // For example, each Goroutine might have a connection 28 | // pool or a local random number generator instance. 29 | func StatefulConcurrentMap(maxGos, n int, g func() func(i int)) { 30 | ReduceConcurrentMap(maxGos, n, func() (func(int), func()) { 31 | return g(), nil 32 | }) 33 | } 34 | 35 | // ReduceConcurrentMap is like StatefulConcurrentMap, but 36 | // a final reduction function is called at the end of each 37 | // Goroutine. 38 | // 39 | // The reduce function is called from one Goroutine at a 40 | // time to allow aggregation operations to be unsafe. 41 | // If the reduction function is nil, this is equivalent to 42 | // StatefulConcurrencyMap. 43 | // 44 | // This can be used to have each Goroutine accumulate some 45 | // partial information which is then aggregated. 46 | func ReduceConcurrentMap(maxGos, n int, g func() (iter func(i int), reduce func())) { 47 | if maxGos == 0 { 48 | maxGos = runtime.GOMAXPROCS(0) 49 | } 50 | if maxGos > n { 51 | maxGos = n 52 | } 53 | 54 | var counter int64 55 | n64 := int64(n) 56 | 57 | var wg sync.WaitGroup 58 | var lock sync.Mutex 59 | for i := 0; i < maxGos; i++ { 60 | wg.Add(1) 61 | go func(start int) { 62 | defer wg.Done() 63 | f, reduce := g() 64 | for { 65 | idxPlus1 := atomic.AddInt64(&counter, 1) 66 | if idxPlus1 > n64 { 67 | break 68 | } 69 | f(int(idxPlus1 - 1)) 70 | } 71 | if reduce != nil { 72 | lock.Lock() 73 | defer lock.Unlock() 74 | reduce() 75 | } 76 | }(i) 77 | } 78 | wg.Wait() 79 | } 80 | -------------------------------------------------------------------------------- /slice_test.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestReverse(t *testing.T) { 9 | pairs := [][2][]int{ 10 | {[]int{}, []int{}}, 11 | {[]int{1}, []int{1}}, 12 | {[]int{1, 2}, []int{2, 1}}, 13 | {[]int{1, 2, 3}, []int{3, 2, 1}}, 14 | {[]int{1, 2, 3, 4}, []int{4, 3, 2, 1}}, 15 | {[]int{1, 2, 3, 4, 5}, []int{5, 4, 3, 2, 1}}, 16 | } 17 | for _, pair := range pairs { 18 | old := append([]int{}, pair[0]...) 19 | Reverse(pair[0]) 20 | if !reflect.DeepEqual(pair[0], pair[1]) { 21 | t.Errorf("reverse of %v should be %v but got %v", old, pair[1], pair[0]) 22 | } 23 | } 24 | } 25 | 26 | func TestContains(t *testing.T) { 27 | list1 := []int{1, 2, 3} 28 | for _, val := range list1 { 29 | if !Contains(list1, val) { 30 | t.Errorf("did not contain %d", val) 31 | } 32 | } 33 | if Contains(list1, 4) { 34 | t.Error("should not contain 4") 35 | } 36 | 37 | list2 := []interface{}{"hey", nil, "test"} 38 | for _, val := range list2 { 39 | if !Contains(list2, val) { 40 | t.Errorf("did not contain %v", val) 41 | } 42 | } 43 | if Contains(list2, "yo") { 44 | t.Error("should not contain \"yo\"") 45 | } 46 | } 47 | 48 | func TestVoodooSort(t *testing.T) { 49 | list := []int{5, 3, 4, 7, 9, 1} 50 | other1 := []string{ 51 | "fourth", 52 | "second", 53 | "third", 54 | "fifth", 55 | "sixth", 56 | "first", 57 | } 58 | other2 := make([]sillyStruct, len(list)) 59 | for i, j := range list { 60 | other2[i] = sillyStruct{field1: uint64(i), field3: uint64(j)} 61 | } 62 | VoodooSort(list, func(i, j int) bool { 63 | return list[i] < list[j] 64 | }, other1, other2) 65 | 66 | expectedList := []int{1, 3, 4, 5, 7, 9} 67 | expectedOther1 := []string{"first", "second", "third", "fourth", "fifth", "sixth"} 68 | expectedOther2 := []sillyStruct{ 69 | {5, 0, 1}, 70 | {1, 0, 3}, 71 | {2, 0, 4}, 72 | {0, 0, 5}, 73 | {3, 0, 7}, 74 | {4, 0, 9}, 75 | } 76 | if !reflect.DeepEqual(list, expectedList) { 77 | t.Errorf("list should be %v but got %v", expectedList, list) 78 | } 79 | if !reflect.DeepEqual(other1, expectedOther1) { 80 | t.Errorf("other 1 should be %v but got %v", expectedOther1, other1) 81 | } 82 | if !reflect.DeepEqual(other2, expectedOther2) { 83 | t.Errorf("other 2 should be %v but got %v", expectedOther2, other2) 84 | } 85 | } 86 | 87 | type sillyStruct struct { 88 | field1 uint64 89 | field2 uint64 90 | field3 uint64 91 | } 92 | -------------------------------------------------------------------------------- /concurrency_test.go: -------------------------------------------------------------------------------- 1 | package essentials 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func ExampleConcurrentMap() { 12 | squares := make([]int, 20) 13 | ConcurrentMap(0, len(squares), func(i int) { 14 | squares[i] = i * i 15 | }) 16 | fmt.Println(squares) 17 | 18 | // Output: [0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361] 19 | } 20 | 21 | func ExampleStatefulConcurrentMap() { 22 | isPrime := make([]bool, 70) 23 | StatefulConcurrentMap(0, len(isPrime), func() func(int) { 24 | // Each Goroutine has its own cache of primes. 25 | var primeCache []int 26 | 27 | return func(i int) { 28 | if i < 2 { 29 | // Don't test 0 and 1 for primality. 30 | return 31 | } 32 | // Speedup by checking if i is divisible by an existing 33 | // known prime. 34 | for _, p := range primeCache { 35 | if i%p == 0 { 36 | return 37 | } 38 | } 39 | // Brute force check. 40 | for j := 2; j < i; j++ { 41 | if i%j == 0 { 42 | primeCache = append(primeCache, j) 43 | return 44 | } 45 | } 46 | isPrime[i] = true 47 | primeCache = append(primeCache, i) 48 | } 49 | }) 50 | 51 | // Print out the primes in a pretty way. 52 | var primes []string 53 | for i, x := range isPrime { 54 | if x { 55 | primes = append(primes, strconv.Itoa(i)) 56 | } 57 | } 58 | fmt.Println(strings.Join(primes, ", ")) 59 | 60 | // Output: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 61 | } 62 | 63 | func ExampleReduceConcurrentMap() { 64 | // Compute the sum of integers from 0 to numSum - 1. 65 | numSum := 10000 66 | var totalSum int 67 | ReduceConcurrentMap(0, numSum, func() (func(int), func()) { 68 | var localSum int 69 | return func(i int) { 70 | // The iter function is not synchronized across Goroutines. 71 | localSum += i 72 | }, func() { 73 | // The reduce function is synchronized across Goroutines. 74 | totalSum += localSum 75 | } 76 | }) 77 | fmt.Println(totalSum) 78 | 79 | // Output: 49995000 80 | } 81 | 82 | func BenchmarkConcurrentMap(b *testing.B) { 83 | b.Run("EvenWorkload", func(b *testing.B) { 84 | for i := 0; i < b.N; i++ { 85 | ConcurrentMap(2, 100, func(i int) { 86 | time.Sleep(time.Millisecond) 87 | }) 88 | } 89 | }) 90 | b.Run("UnbalancedWorkload", func(b *testing.B) { 91 | for i := 0; i < b.N; i++ { 92 | ConcurrentMap(2, 100, func(i int) { 93 | if i%2 == 0 { 94 | time.Sleep(time.Millisecond * 2) 95 | } 96 | }) 97 | } 98 | }) 99 | b.Run("HugeWorkload", func(b *testing.B) { 100 | for i := 0; i < b.N; i++ { 101 | ConcurrentMap(2, 1000000, func(i int) { 102 | time.Sleep(time.Nanosecond) 103 | }) 104 | } 105 | }) 106 | } 107 | --------------------------------------------------------------------------------