├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── asc ├── README.md ├── errors.go ├── numeric.go ├── numeric_test.go ├── time.go └── time_test.go ├── bounds.go ├── delete.go ├── delete_bench_test.go ├── delete_test.go ├── desc ├── README.md ├── errors.go ├── numeric.go ├── numeric_test.go ├── time.go └── time_test.go ├── errors.go ├── examples └── README.md ├── get.go ├── get_bench_test.go ├── get_test.go ├── has.go ├── has_bench_test.go ├── has_test.go ├── insert.go ├── insert_bench_test.go ├── insert_test.go ├── insertsort.go ├── iter.go ├── iter_test.go ├── keys.go ├── keys_test.go ├── map.go ├── map_test.go ├── replace.go ├── replace_bench_test.go ├── replace_test.go ├── sliceutils.go ├── sortedmap.go ├── sortedmap_bench_test.go ├── sortedmap_test.go └── testing_utils_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.8 4 | - 1.x 5 | os: 6 | - linux 7 | before_install: 8 | - go get -t -v ./... 9 | script: 10 | - go test -race -coverprofile=coverage.txt -covermode=atomic 11 | after_success: 12 | - bash <(curl -s https://codecov.io/bash) 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Feel free to submit an issue or pull request. Each change will be considered based on the style and technical merits of the submitted solution or suggestion. 4 | 5 | ## Issues 6 | 7 | When creating an issue, please be sure to include the following to ensure that your question can be answered: 8 | 9 | * A detailed description of the issue or question. 10 | * A working snippet of code that demonstrates the issue being brought up. 11 | * Steps taken to try and solve the issue or answer the question independently. If you are unsure of any steps to attempt, please say so. 12 | * The version of Go being used. 13 | * The commit ID of the code being used: 14 | ```sh 15 | git -C $GOPATH/src/github.com/umpc/go-sortedmap rev-parse --short HEAD 16 | ``` 17 | 18 | ## Pull Requests 19 | 20 | * Ensure that your changes will benefit the project's userbase and will fit its purpose, without venturing out of scope. 21 | * Make sure that the coding style of submitted pull requests matches the established coding style. 22 | * Please do not submit pull requests that fail existing tests. 23 | * Please create new tests for submitted work in order to keep code coverage at 100%. 24 | 25 | Thank you for reading and considering this policy. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Justin Lowery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SortedMap 2 | 3 | [![Build Status](https://travis-ci.org/umpc/go-sortedmap.svg?branch=master)](https://travis-ci.org/umpc/go-sortedmap) [![Coverage Status](https://codecov.io/github/umpc/go-sortedmap/badge.svg?branch=master)](https://codecov.io/github/umpc/go-sortedmap?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/umpc/go-sortedmap)](https://goreportcard.com/report/github.com/umpc/go-sortedmap) [![GoDoc](https://godoc.org/github.com/umpc/go-sortedmap?status.svg)](https://godoc.org/github.com/umpc/go-sortedmap) 4 | 5 | SortedMap is a simple library that provides a value-sorted ```map[interface{}]interface{}``` type and methods combined from Go 1 map and slice primitives. 6 | 7 | This data structure allows for roughly constant-time reads and for efficiently iterating over only a section of stored values. 8 | 9 | ```sh 10 | go get -u github.com/umpc/go-sortedmap 11 | ``` 12 | 13 | ### Complexity 14 | Operation | Worst-Case 15 | ------------------------|----------- 16 | Has, Get | ```O(1)``` 17 | Delete, Insert, Replace | ```O(n)``` 18 | 19 | ## Example Usage 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "time" 27 | 28 | "github.com/umpc/go-sortedmap" 29 | "github.com/umpc/go-sortedmap/asc" 30 | ) 31 | 32 | func main() { 33 | // Create an empty SortedMap with a size suggestion and a less than function: 34 | sm := sortedmap.New(4, asc.Time) 35 | 36 | // Insert example records: 37 | sm.Insert("OpenBSD", time.Date(1995, 10, 18, 8, 37, 1, 0, time.UTC)) 38 | sm.Insert("UnixTime", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)) 39 | sm.Insert("Linux", time.Date(1991, 8, 25, 20, 57, 8, 0, time.UTC)) 40 | sm.Insert("GitHub", time.Date(2008, 4, 10, 0, 0, 0, 0, time.UTC)) 41 | 42 | // Set iteration options: 43 | reversed := true 44 | lowerBound := time.Date(1994, 1, 1, 0, 0, 0, 0, time.UTC) 45 | upperBound := time.Now() 46 | 47 | // Select values > lowerBound and values <= upperBound. 48 | // Loop through the values, in reverse order: 49 | iterCh, err := sm.BoundedIterCh(reversed, lowerBound, upperBound) 50 | if err != nil { 51 | fmt.Println(err) 52 | return 53 | } 54 | defer iterCh.Close() 55 | 56 | for rec := range iterCh.Records() { 57 | fmt.Printf("%+v\n", rec) 58 | } 59 | } 60 | ``` 61 | 62 | Check out the [examples](https://github.com/umpc/go-sortedmap/tree/master/examples), [documentation](https://godoc.org/github.com/umpc/go-sortedmap), and test files, for more features and further explanations. 63 | 64 | ## Benchmarks 65 | 66 | ```sh 67 | BenchmarkNew-8 20000000 98.8 ns/op 96 B/op 2 allocs/op 68 | 69 | BenchmarkHas1of1CachedRecords-8 50000000 27.3 ns/op 0 B/op 0 allocs/op 70 | BenchmarkHas1of1Records-8 20000000 94.1 ns/op 0 B/op 0 allocs/op 71 | 72 | BenchmarkGet1of1CachedRecords-8 50000000 27.3 ns/op 0 B/op 0 allocs/op 73 | BenchmarkGet1of1Records-8 20000000 96.8 ns/op 0 B/op 0 allocs/op 74 | 75 | BenchmarkDelete1of1Records-8 5000000 285 ns/op 0 B/op 0 allocs/op 76 | 77 | BenchmarkInsert1Record-8 3000000 442 ns/op 304 B/op 2 allocs/op 78 | BenchmarkReplace1of1Records-8 5000000 378 ns/op 0 B/op 0 allocs/op 79 | 80 | BenchmarkDelete1of10Records-8 2000000 615 ns/op 0 B/op 0 allocs/op 81 | BenchmarkDelete1of100Records-8 1000000 1005 ns/op 0 B/op 0 allocs/op 82 | BenchmarkDelete1of1000Records-8 1000000 1987 ns/op 0 B/op 0 allocs/op 83 | BenchmarkDelete1of10000Records-8 300000 5473 ns/op 0 B/op 0 allocs/op 84 | 85 | BenchmarkBatchDelete10of10Records-8 500000 3410 ns/op 16 B/op 1 allocs/op 86 | BenchmarkBatchDelete100of100Records-8 30000 47069 ns/op 112 B/op 1 allocs/op 87 | BenchmarkBatchDelete1000of1000Records-8 2000 721201 ns/op 1024 B/op 1 allocs/op 88 | BenchmarkBatchDelete10000of10000Records-8 100 19275331 ns/op 10240 B/op 1 allocs/op 89 | 90 | BenchmarkBatchGet10of10Records-8 2000000 902 ns/op 176 B/op 2 allocs/op 91 | BenchmarkBatchGet100of100Records-8 300000 5550 ns/op 1904 B/op 2 allocs/op 92 | BenchmarkBatchGet1000of1000Records-8 30000 49057 ns/op 17408 B/op 2 allocs/op 93 | BenchmarkBatchGet10000of10000Records-8 2000 710611 ns/op 174080 B/op 2 allocs/op 94 | 95 | BenchmarkBatchHas10of10Records-8 2000000 742 ns/op 16 B/op 1 allocs/op 96 | BenchmarkBatchHas100of100Records-8 300000 5102 ns/op 112 B/op 1 allocs/op 97 | BenchmarkBatchHas1000of1000Records-8 30000 46257 ns/op 1024 B/op 1 allocs/op 98 | BenchmarkBatchHas10000of10000Records-8 3000 519497 ns/op 10240 B/op 1 allocs/op 99 | 100 | BenchmarkBatchInsert10Records-8 300000 4164 ns/op 1382 B/op 8 allocs/op 101 | BenchmarkBatchInsert100Records-8 30000 54184 ns/op 14912 B/op 19 allocs/op 102 | BenchmarkBatchInsert1000Records-8 2000 844344 ns/op 201969 B/op 78 allocs/op 103 | BenchmarkBatchInsert10000Records-8 100 25911455 ns/op 2121554 B/op 584 allocs/op 104 | 105 | BenchmarkReplace1of10Records-8 1000000 1021 ns/op 0 B/op 0 allocs/op 106 | BenchmarkReplace1of100Records-8 1000000 1669 ns/op 0 B/op 0 allocs/op 107 | BenchmarkReplace1of1000Records-8 500000 3187 ns/op 0 B/op 0 allocs/op 108 | BenchmarkReplace1of10000Records-8 200000 8778 ns/op 0 B/op 0 allocs/op 109 | 110 | BenchmarkBatchReplace10of10Records-8 200000 6934 ns/op 0 B/op 0 allocs/op 111 | BenchmarkBatchReplace100of100Records-8 10000 100186 ns/op 0 B/op 0 allocs/op 112 | BenchmarkBatchReplace1000of1000Records-8 1000 1546355 ns/op 0 B/op 0 allocs/op 113 | BenchmarkBatchReplace10000of10000Records-8 20 58396360 ns/op 0 B/op 0 allocs/op 114 | ``` 115 | 116 | The above benchmark tests were ran using a 4.0GHz Intel Core i7-4790K (Haswell) CPU. 117 | 118 | ## License 119 | 120 | The source code is available under the [MIT License](https://opensource.org/licenses/MIT). 121 | -------------------------------------------------------------------------------- /asc/README.md: -------------------------------------------------------------------------------- 1 | # Asc 2 | 3 | [![Build Status](https://travis-ci.org/umpc/go-sortedmap.svg?branch=master)](https://travis-ci.org/umpc/go-sortedmap) [![Coverage Status](https://codecov.io/github/umpc/go-sortedmap/badge.svg?branch=master)](https://codecov.io/github/umpc/go-sortedmap?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/umpc/go-sortedmap)](https://goreportcard.com/report/github.com/umpc/go-sortedmap) [![GoDoc](https://godoc.org/github.com/umpc/go-sortedmap/asc?status.svg)](https://godoc.org/github.com/umpc/go-sortedmap/asc) 4 | 5 | Asc allows for a simple method of selecting an ascending insertion sort function for any of the supported types. 6 | 7 | This package currently supports ```numeric``` types and ```time.Time```. -------------------------------------------------------------------------------- /asc/errors.go: -------------------------------------------------------------------------------- 1 | package asc 2 | 3 | const greaterThanErr = "the higher value was less than the lesser value!" 4 | -------------------------------------------------------------------------------- /asc/numeric.go: -------------------------------------------------------------------------------- 1 | package asc 2 | 3 | // Uint8 is a less than comparison function for the Uint8 numeric type. 4 | func Uint8(i, j interface{}) bool { 5 | return i.(uint8) < j.(uint8) 6 | } 7 | 8 | // Uint16 is a less than comparison function for the Uint16 numeric type. 9 | func Uint16(i, j interface{}) bool { 10 | return i.(uint16) < j.(uint16) 11 | } 12 | 13 | // Uint32 is a less than comparison function for the Uint32 numeric type. 14 | func Uint32(i, j interface{}) bool { 15 | return i.(uint32) < j.(uint32) 16 | } 17 | 18 | // Uint64 is a less than comparison function for the Uint64 numeric type. 19 | func Uint64(i, j interface{}) bool { 20 | return i.(uint64) < j.(uint64) 21 | } 22 | 23 | // Int8 is a less than comparison function for the Int8 numeric type. 24 | func Int8(i, j interface{}) bool { 25 | return i.(int8) < j.(int8) 26 | } 27 | 28 | // Int16 is a less than comparison function for the Int16 numeric type. 29 | func Int16(i, j interface{}) bool { 30 | return i.(int16) < j.(int16) 31 | } 32 | 33 | // Int32 is a less than comparison function for the Int32 numeric type. 34 | func Int32(i, j interface{}) bool { 35 | return i.(int32) < j.(int32) 36 | } 37 | 38 | // Int64 is a less than comparison function for the Int64 numeric type. 39 | func Int64(i, j interface{}) bool { 40 | return i.(int64) < j.(int64) 41 | } 42 | 43 | // Float32 is a less than comparison function for the Float32 numeric type. 44 | func Float32(i, j interface{}) bool { 45 | return i.(float32) < j.(float32) 46 | } 47 | 48 | // Float64 is a less than comparison function for the Float64 numeric type. 49 | func Float64(i, j interface{}) bool { 50 | return i.(float64) < j.(float64) 51 | } 52 | 53 | // Uint is a less than comparison function for the Uint numeric type. 54 | func Uint(i, j interface{}) bool { 55 | return i.(uint) < j.(uint) 56 | } 57 | 58 | // Int is a less than comparison function for the Int numeric type. 59 | func Int(i, j interface{}) bool { 60 | return i.(int) < j.(int) 61 | } 62 | -------------------------------------------------------------------------------- /asc/numeric_test.go: -------------------------------------------------------------------------------- 1 | package asc 2 | 3 | import "testing" 4 | 5 | func TestUint8(t *testing.T) { 6 | if Uint8(uint8(1), uint8(0)) { 7 | t.Fatalf("asc.TestUint8 failed: %v\n", greaterThanErr) 8 | } 9 | } 10 | 11 | func TestUint16(t *testing.T) { 12 | if Uint16(uint16(1), uint16(0)) { 13 | t.Fatalf("asc.TestUint16 failed: %v\n", greaterThanErr) 14 | } 15 | } 16 | 17 | func TestUint32(t *testing.T) { 18 | if Uint32(uint32(1), uint32(0)) { 19 | t.Fatalf("asc.TestUint32 failed: %v\n", greaterThanErr) 20 | } 21 | } 22 | 23 | func TestUint64(t *testing.T) { 24 | if Uint64(uint64(1), uint64(0)) { 25 | t.Fatalf("asc.TestUint64 failed: %v\n", greaterThanErr) 26 | } 27 | } 28 | 29 | func TestInt8(t *testing.T) { 30 | if Int8(int8(1), int8(0)) { 31 | t.Fatalf("asc.TestInt8 failed: %v\n", greaterThanErr) 32 | } 33 | } 34 | 35 | func TestInt16(t *testing.T) { 36 | if Int16(int16(1), int16(0)) { 37 | t.Fatalf("asc.TestInt16 failed: %v\n", greaterThanErr) 38 | } 39 | } 40 | 41 | func TestInt32(t *testing.T) { 42 | if Int32(int32(1), int32(0)) { 43 | t.Fatalf("asc.TestInt32 failed: %v\n", greaterThanErr) 44 | } 45 | } 46 | 47 | func TestInt64(t *testing.T) { 48 | if Int64(int64(1), int64(0)) { 49 | t.Fatalf("asc.TestInt64 failed: %v\n", greaterThanErr) 50 | } 51 | } 52 | 53 | func TestFloat32(t *testing.T) { 54 | if Float32(float32(1), float32(0)) { 55 | t.Fatalf("asc.TestFloat32 failed: %v\n", greaterThanErr) 56 | } 57 | } 58 | 59 | func TestFloat64(t *testing.T) { 60 | if Float64(float64(1), float64(0)) { 61 | t.Fatalf("asc.TestFloat64 failed: %v\n", greaterThanErr) 62 | } 63 | } 64 | 65 | func TestUint(t *testing.T) { 66 | if Uint(uint(1), uint(0)) { 67 | t.Fatalf("asc.TestUint failed: %v\n", greaterThanErr) 68 | } 69 | } 70 | 71 | func TestInt(t *testing.T) { 72 | if Int(int(1), 0) { 73 | t.Fatalf("asc.TestInt failed: %v\n", greaterThanErr) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /asc/time.go: -------------------------------------------------------------------------------- 1 | package asc 2 | 3 | import "time" 4 | 5 | // Time is a less than comparison function for the time.Time type. 6 | func Time(i, j interface{}) bool { 7 | return i.(time.Time).Before(j.(time.Time)) 8 | } 9 | -------------------------------------------------------------------------------- /asc/time_test.go: -------------------------------------------------------------------------------- 1 | package asc 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTime(t *testing.T) { 9 | earlerDate := time.Date(2017, 06, 05, 18, 0, 0, 0, time.UTC) 10 | laterDate := time.Date(2018, 07, 06, 21, 0, 0, 0, time.UTC) 11 | 12 | if Time(laterDate, earlerDate) { 13 | t.Fatal("asc.TestTime failed: laterDate was before earlierDate") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bounds.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "sort" 4 | 5 | func (sm *SortedMap) setBoundIdx(boundVal interface{}) int { 6 | return sort.Search(len(sm.sorted), func(i int) bool { 7 | return sm.lessFn(boundVal, sm.idx[sm.sorted[i]]) 8 | }) 9 | } 10 | 11 | func (sm *SortedMap) boundsIdxSearch(lowerBound, upperBound interface{}) []int { 12 | smLen := len(sm.sorted) 13 | if smLen == 0 { 14 | return nil 15 | } 16 | 17 | if lowerBound != nil && upperBound != nil { 18 | if sm.lessFn(upperBound, lowerBound) { 19 | return nil 20 | } 21 | } 22 | 23 | lowerBoundIdx := 0 24 | if lowerBound != nil { 25 | lowerBoundIdx = sm.setBoundIdx(lowerBound) 26 | 27 | if lowerBoundIdx == smLen { 28 | lowerBoundIdx-- 29 | } 30 | if lowerBoundIdx >= 0 && sm.lessFn(sm.idx[sm.sorted[lowerBoundIdx]], lowerBound) { 31 | lowerBoundIdx++ 32 | } 33 | } 34 | 35 | upperBoundIdx := smLen - 1 36 | if upperBound != nil { 37 | upperBoundIdx = sm.setBoundIdx(upperBound) 38 | if upperBoundIdx == smLen { 39 | upperBoundIdx-- 40 | } 41 | if upperBoundIdx >= 0 && sm.lessFn(upperBound, sm.idx[sm.sorted[upperBoundIdx]]) { 42 | upperBoundIdx-- 43 | } 44 | } 45 | 46 | if lowerBoundIdx > upperBoundIdx { 47 | return nil 48 | } 49 | 50 | return []int{ 51 | lowerBoundIdx, 52 | upperBoundIdx, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /delete.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | ) 7 | 8 | func (sm *SortedMap) delete(key interface{}) bool { 9 | if val, ok := sm.idx[key]; ok { 10 | 11 | smLen := len(sm.sorted) 12 | i := sort.Search(smLen, func(i int) bool { 13 | return sm.lessFn(val, sm.idx[sm.sorted[i]]) 14 | }) 15 | 16 | if i == smLen { 17 | i-- 18 | } else if i < smLen-1 { 19 | i++ 20 | } 21 | for sm.sorted[i] != key { 22 | i-- 23 | } 24 | 25 | delete(sm.idx, key) 26 | sm.sorted = deleteInterface(sm.sorted, i) 27 | 28 | return true 29 | } 30 | return false 31 | } 32 | 33 | func (sm *SortedMap) boundedDelete(lowerBound, upperBound interface{}) error { 34 | iterBounds := sm.boundsIdxSearch(lowerBound, upperBound) 35 | if iterBounds == nil { 36 | return errors.New(noValuesErr) 37 | } 38 | for i, deleted := iterBounds[0], 0; i <= iterBounds[1]-deleted; i++ { 39 | delete(sm.idx, sm.sorted[i]) 40 | sm.sorted = deleteInterface(sm.sorted, i) 41 | deleted++ 42 | } 43 | return nil 44 | } 45 | 46 | // Delete removes a value from the collection, using the given key. 47 | // Because the index position of each sorted key changes on each insert and a simpler structure was ideal, deletes can have a worse-case complexity of O(n), meaning the goroutine must loop through the sorted slice to find and delete the given key. 48 | func (sm *SortedMap) Delete(key interface{}) bool { 49 | return sm.delete(key) 50 | } 51 | 52 | // BatchDelete removes values from the collection, using the given keys, returning a slice of the results. 53 | func (sm *SortedMap) BatchDelete(keys []interface{}) []bool { 54 | results := make([]bool, len(keys)) 55 | for i, key := range keys { 56 | results[i] = sm.delete(key) 57 | } 58 | return results 59 | } 60 | 61 | // BoundedDelete removes values that are between the given values from the collection. 62 | // BoundedDelete returns true if the operation was successful, or false otherwise. 63 | func (sm *SortedMap) BoundedDelete(lowerBound, upperBound interface{}) error { 64 | return sm.boundedDelete(lowerBound, upperBound) 65 | } 66 | -------------------------------------------------------------------------------- /delete_bench_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | func delete1ofNRecords(b *testing.B, n int) { 6 | sm, _, keys, err := newRandSortedMapWithKeys(n) 7 | if err != nil { 8 | b.Fatal(err) 9 | } 10 | 11 | b.ResetTimer() 12 | for i := 0; i < b.N; i++ { 13 | sm.Delete(keys[0]) 14 | 15 | b.StopTimer() 16 | sm, _, keys, err = newRandSortedMapWithKeys(n) 17 | if err != nil { 18 | b.Fatal(err) 19 | } 20 | b.StartTimer() 21 | } 22 | } 23 | 24 | func batchDeleteNofNRecords(b *testing.B, n int) { 25 | sm, _, keys, err := newRandSortedMapWithKeys(n) 26 | if err != nil { 27 | b.Fatal(err) 28 | } 29 | 30 | b.ResetTimer() 31 | for i := 0; i < b.N; i++ { 32 | sm.BatchDelete(keys) 33 | 34 | b.StopTimer() 35 | sm, _, keys, err = newRandSortedMapWithKeys(n) 36 | if err != nil { 37 | b.Fatal(err) 38 | } 39 | b.StartTimer() 40 | } 41 | } 42 | 43 | func BenchmarkDelete1of1Records(b *testing.B) { 44 | delete1ofNRecords(b, 1) 45 | } 46 | 47 | func BenchmarkDelete1of10Records(b *testing.B) { 48 | delete1ofNRecords(b, 10) 49 | } 50 | 51 | func BenchmarkDelete1of100Records(b *testing.B) { 52 | delete1ofNRecords(b, 100) 53 | } 54 | 55 | func BenchmarkDelete1of1000Records(b *testing.B) { 56 | delete1ofNRecords(b, 1000) 57 | } 58 | 59 | func BenchmarkDelete1of10000Records(b *testing.B) { 60 | delete1ofNRecords(b, 10000) 61 | } 62 | 63 | func BenchmarkBatchDelete10of10Records(b *testing.B) { 64 | batchDeleteNofNRecords(b, 10) 65 | } 66 | 67 | func BenchmarkBatchDelete100of100Records(b *testing.B) { 68 | batchDeleteNofNRecords(b, 100) 69 | } 70 | 71 | func BenchmarkBatchDelete1000of1000Records(b *testing.B) { 72 | batchDeleteNofNRecords(b, 1000) 73 | } 74 | 75 | func BenchmarkBatchDelete10000of10000Records(b *testing.B) { 76 | batchDeleteNofNRecords(b, 10000) 77 | } 78 | -------------------------------------------------------------------------------- /delete_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestDelete(t *testing.T) { 9 | const shouldFailErr = "Equal bound values that do not match a stored value should always fail." 10 | 11 | sm, records, err := newSortedMapFromRandRecords(300) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | 16 | if sm.Delete("") { 17 | t.Fatalf("Delete: %v", invalidDelete) 18 | } 19 | 20 | for _, rec := range records { 21 | sm.Delete(rec.Key) 22 | } 23 | 24 | _, err = sm.IterCh() 25 | if err == nil { 26 | t.Fatal(shouldFailErr) 27 | } 28 | } 29 | 30 | func TestBatchDelete(t *testing.T) { 31 | sm, records, err := newSortedMapFromRandRecords(300) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | keys := make([]interface{}, 0) 37 | for i, rec := range records { 38 | if i == 50 { 39 | break 40 | } 41 | keys = append(keys, rec.Key) 42 | } 43 | 44 | for _, ok := range sm.BatchDelete(keys) { 45 | if !ok { 46 | t.Fatalf("BatchDelete: %v", invalidDelete) 47 | } 48 | } 49 | 50 | iterCh, err := sm.IterCh() 51 | if err != nil { 52 | t.Fatal(err) 53 | } else { 54 | defer iterCh.Close() 55 | 56 | if err := verifyRecords(iterCh.Records(), false); err != nil { 57 | t.Fatal(err) 58 | } 59 | } 60 | } 61 | 62 | func TestBoundedDelete(t *testing.T) { 63 | const ( 64 | nilBoundValsErr = "accepted nil bound value" 65 | generalBoundsErr = "general bounds error" 66 | shouldFailErr = "Equal bound values that do not match a stored value should always fail." 67 | ) 68 | 69 | sm, _, err := newSortedMapFromRandRecords(300) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | earlierDate := time.Date(200, 1, 1, 0, 0, 0, 0, time.UTC) 75 | 76 | if err := sm.BoundedDelete(nil, nil); err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | if err := sm.BoundedDelete(nil, time.Now()); err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | if err := sm.BoundedDelete(time.Now(), nil); err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | if err := sm.BoundedDelete(earlierDate, time.Now()); err != nil { 89 | t.Fatal(err) 90 | } 91 | 92 | if err := sm.BoundedDelete(time.Now(), earlierDate); err == nil { 93 | t.Fatal(shouldFailErr) 94 | } 95 | 96 | if err := sm.BoundedDelete(earlierDate, earlierDate); err == nil { 97 | t.Fatal(shouldFailErr) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /desc/README.md: -------------------------------------------------------------------------------- 1 | # Desc 2 | 3 | [![Build Status](https://travis-ci.org/umpc/go-sortedmap.svg?branch=master)](https://travis-ci.org/umpc/go-sortedmap) [![Coverage Status](https://codecov.io/github/umpc/go-sortedmap/badge.svg?branch=master)](https://codecov.io/github/umpc/go-sortedmap?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/umpc/go-sortedmap)](https://goreportcard.com/report/github.com/umpc/go-sortedmap) [![GoDoc](https://godoc.org/github.com/umpc/go-sortedmap/desc?status.svg)](https://godoc.org/github.com/umpc/go-sortedmap/desc) 4 | 5 | Desc allows for a simple method of selecting a descending insertion sort function for any of the supported types. 6 | 7 | This package currently supports ```numeric``` types and ```time.Time```. -------------------------------------------------------------------------------- /desc/errors.go: -------------------------------------------------------------------------------- 1 | package desc 2 | 3 | const greaterThanErr = "the lesser value was greater than the higher value!" 4 | -------------------------------------------------------------------------------- /desc/numeric.go: -------------------------------------------------------------------------------- 1 | package desc 2 | 3 | // Uint8 is a greater than comparison function for the Uint8 numeric type. 4 | func Uint8(i, j interface{}) bool { 5 | return i.(uint8) > j.(uint8) 6 | } 7 | 8 | // Uint16 is a greater than comparison function for the Uint16 numeric type. 9 | func Uint16(i, j interface{}) bool { 10 | return i.(uint16) > j.(uint16) 11 | } 12 | 13 | // Uint32 is a greater than comparison function for the Uint32 numeric type. 14 | func Uint32(i, j interface{}) bool { 15 | return i.(uint32) > j.(uint32) 16 | } 17 | 18 | // Uint64 is a greater than comparison function for the Uint64 numeric type. 19 | func Uint64(i, j interface{}) bool { 20 | return i.(uint64) > j.(uint64) 21 | } 22 | 23 | // Int8 is a greater than comparison function for the Int8 numeric type. 24 | func Int8(i, j interface{}) bool { 25 | return i.(int8) > j.(int8) 26 | } 27 | 28 | // Int16 is a greater than comparison function for the Int16 numeric type. 29 | func Int16(i, j interface{}) bool { 30 | return i.(int16) > j.(int16) 31 | } 32 | 33 | // Int32 is a greater than comparison function for the Int32 numeric type. 34 | func Int32(i, j interface{}) bool { 35 | return i.(int32) > j.(int32) 36 | } 37 | 38 | // Int64 is a greater than comparison function for the Int64 numeric type. 39 | func Int64(i, j interface{}) bool { 40 | return i.(int64) > j.(int64) 41 | } 42 | 43 | // Float32 is a greater than comparison function for the Float32 numeric type. 44 | func Float32(i, j interface{}) bool { 45 | return i.(float32) > j.(float32) 46 | } 47 | 48 | // Float64 is a greater than comparison function for the Float64 numeric type. 49 | func Float64(i, j interface{}) bool { 50 | return i.(float64) > j.(float64) 51 | } 52 | 53 | // Uint is a greater than comparison function for the Uint numeric type. 54 | func Uint(i, j interface{}) bool { 55 | return i.(uint) > j.(uint) 56 | } 57 | 58 | // Int is a greater than comparison function for the Int numeric type. 59 | func Int(i, j interface{}) bool { 60 | return i.(int) > j.(int) 61 | } 62 | -------------------------------------------------------------------------------- /desc/numeric_test.go: -------------------------------------------------------------------------------- 1 | package desc 2 | 3 | import "testing" 4 | 5 | func TestUint8(t *testing.T) { 6 | if Uint8(uint8(0), uint8(1)) { 7 | t.Fatalf("desc.TestUint8 failed: %v\n", greaterThanErr) 8 | } 9 | } 10 | 11 | func TestUint16(t *testing.T) { 12 | if Uint16(uint16(0), uint16(1)) { 13 | t.Fatalf("desc.TestUint16 failed: %v\n", greaterThanErr) 14 | } 15 | } 16 | 17 | func TestUint32(t *testing.T) { 18 | if Uint32(uint32(0), uint32(1)) { 19 | t.Fatalf("desc.TestUint32 failed: %v\n", greaterThanErr) 20 | } 21 | } 22 | 23 | func TestUint64(t *testing.T) { 24 | if Uint64(uint64(0), uint64(1)) { 25 | t.Fatalf("desc.TestUint64 failed: %v\n", greaterThanErr) 26 | } 27 | } 28 | 29 | func TestInt8(t *testing.T) { 30 | if Int8(int8(0), int8(1)) { 31 | t.Fatalf("desc.TestInt8 failed: %v\n", greaterThanErr) 32 | } 33 | } 34 | 35 | func TestInt16(t *testing.T) { 36 | if Int16(int16(0), int16(1)) { 37 | t.Fatalf("desc.TestInt16 failed: %v\n", greaterThanErr) 38 | } 39 | } 40 | 41 | func TestInt32(t *testing.T) { 42 | if Int32(int32(0), int32(1)) { 43 | t.Fatalf("desc.TestInt32 failed: %v\n", greaterThanErr) 44 | } 45 | } 46 | 47 | func TestInt64(t *testing.T) { 48 | if Int64(int64(0), int64(1)) { 49 | t.Fatalf("desc.TestInt64 failed: %v\n", greaterThanErr) 50 | } 51 | } 52 | 53 | func TestFloat32(t *testing.T) { 54 | if Float32(float32(0), float32(1)) { 55 | t.Fatalf("desc.TestFloat32 failed: %v\n", greaterThanErr) 56 | } 57 | } 58 | 59 | func TestFloat64(t *testing.T) { 60 | if Float64(float64(0), float64(1)) { 61 | t.Fatalf("desc.TestFloat64 failed: %v\n", greaterThanErr) 62 | } 63 | } 64 | 65 | func TestUint(t *testing.T) { 66 | if Uint(uint(0), uint(1)) { 67 | t.Fatalf("desc.TestUint failed: %v\n", greaterThanErr) 68 | } 69 | } 70 | 71 | func TestInt(t *testing.T) { 72 | if Int(int(0), 0) { 73 | t.Fatalf("desc.TestInt failed: %v\n", greaterThanErr) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /desc/time.go: -------------------------------------------------------------------------------- 1 | package desc 2 | 3 | import "time" 4 | 5 | // Time is a greater than comparison function for the time.Time type. 6 | func Time(i, j interface{}) bool { 7 | return i.(time.Time).After(j.(time.Time)) 8 | } 9 | -------------------------------------------------------------------------------- /desc/time_test.go: -------------------------------------------------------------------------------- 1 | package desc 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTime(t *testing.T) { 9 | earlerDate := time.Date(2017, 06, 05, 18, 0, 0, 0, time.UTC) 10 | laterDate := time.Date(2018, 07, 06, 21, 0, 0, 0, time.UTC) 11 | 12 | if Time(earlerDate, laterDate) { 13 | t.Fatal("desc.TestTime failed: earlierDate was after laterDate") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | const noValuesErr = "No values found that were equal to or within the given bounds." 4 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # SortedMap Examples 2 | 3 | ## Table of Contents 4 | 5 | * [Test Data](#test-data) 6 | * [Insert / Get / Replace / Delete / Has](#insert--get--replace--delete--has) 7 | * [Iteration](#iteration) 8 | * [IterCh](#iterch) 9 | * [BoundedIterCh](#boundediterch) 10 | * [CustomIterCh](#customiterch) 11 | * [IterFunc](#iterfunc) 12 | * [BoundedIterFunc](#boundediterfunc) 13 | * [Map & Keys Loop](#map--keys-loop) 14 | * [Map & Bounded Keys Loop](#map--bounded-keys-loop) 15 | * [Bounded Delete](#boundeddelete) 16 | 17 | ## Test Data 18 | 19 | The following function is used to generate test data in the examples: 20 | 21 | ```go 22 | func randRecords(n int) []sortedmap.Record { 23 | mrand.Seed(time.Now().UTC().UnixNano()) 24 | records := make([]sortedmap.Record, n) 25 | for i := range records { 26 | year := mrand.Intn(2058) 27 | for year < 2000 { 28 | year = mrand.Intn(2058) 29 | } 30 | mth := time.Month(mrand.Intn(12)) 31 | if mth < 1 { 32 | mth++ 33 | } 34 | day := mrand.Intn(28) 35 | if day < 1 { 36 | day++ 37 | } 38 | hour := mrand.Intn(23) 39 | min := mrand.Intn(59) 40 | sec := mrand.Intn(59) 41 | 42 | t := time.Date(year, mth, day, hour, min, sec, 0, time.UTC) 43 | 44 | records[i] = sortedmap.Record{ 45 | Key: t.Format(time.UnixDate), 46 | Val: t, 47 | } 48 | } 49 | return records 50 | } 51 | ``` 52 | 53 | ## Insert / Get / Replace / Delete / Has 54 | 55 | Below is an example containing common operations that are used with a single record. 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "fmt" 62 | "time" 63 | mrand "math/rand" 64 | 65 | "github.com/umpc/go-sortedmap" 66 | "github.com/umpc/go-sortedmap/asc" 67 | ) 68 | 69 | func main() { 70 | const n = 1 71 | 72 | records := randRecords(n) 73 | rec := records[0] 74 | 75 | // Create a new collection. This reserves memory for one item 76 | // before allocating a new backing array and appending to it: 77 | sm := sortedmap.New(n, asc.Time) 78 | 79 | // Insert the example record: 80 | if !sm.Insert(rec.Key, rec.Val) { 81 | fmt.Printf("The key already exists: %+v", rec.Key) 82 | } 83 | 84 | // Get and print the value: 85 | if val, ok := sm.Get(rec.Key); ok { 86 | fmt.Printf("%+v\n", val) 87 | } 88 | 89 | // Replace the example record: 90 | sm.Replace(rec.Key, time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)) 91 | 92 | // Remove the example record: 93 | sm.Delete(rec.Key) 94 | 95 | // Check if the index has the key: 96 | const recHasFmt = "The key %v.\n" 97 | if sm.Has(rec.Key) { 98 | fmt.Printf(recHasFmt, "exists") 99 | } else { 100 | fmt.Printf(recHasFmt, "does not exist") 101 | } 102 | } 103 | ``` 104 | 105 | ## Iteration 106 | 107 | SortedMap supports three specific ways of processing list data: 108 | 109 | * Channels 110 | * Callback Functions 111 | * Maps & Slices 112 | 113 | ### IterCh 114 | 115 | ```IterCh``` is a simple way of iterating over the entire set, in order. The returned value should be closed when finished using it, to close any sending goroutines. 116 | 117 | ```go 118 | package main 119 | 120 | import ( 121 | "fmt" 122 | "time" 123 | mrand "math/rand" 124 | 125 | "github.com/umpc/go-sortedmap" 126 | "github.com/umpc/go-sortedmap/asc" 127 | ) 128 | 129 | func main() { 130 | const n = 25 131 | records := randRecords(n) 132 | 133 | // Create a new collection. 134 | sm := sortedmap.New(n, asc.Time) 135 | 136 | // BatchInsert the example records: 137 | sm.BatchInsert(records) 138 | 139 | iterCh, err := sm.IterCh() 140 | if err != nil { 141 | fmt.Println(err) 142 | } else { 143 | defer iterCh.Close() 144 | 145 | for rec := range iterCh.Records() { 146 | fmt.Printf("%+v\n", rec) 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | ### BoundedIterCh 153 | 154 | ```BoundedIterCh``` selects values that are greater than the lower bound and are less than or equal to the upper bound. Its first argument allows for reversing the order of the returned records. 155 | 156 | ```go 157 | package main 158 | 159 | import ( 160 | "fmt" 161 | "time" 162 | mrand "math/rand" 163 | 164 | "github.com/umpc/go-sortedmap" 165 | "github.com/umpc/go-sortedmap/asc" 166 | ) 167 | 168 | func main() { 169 | const n = 25 170 | records := randRecords(n) 171 | 172 | // Create a new collection. 173 | sm := sortedmap.New(n, asc.Time) 174 | 175 | // BatchInsert the example records: 176 | sm.BatchInsert(records) 177 | 178 | iterCh, err := sm.BoundedIterCh(false, time.Time{}, time.Now()) 179 | if err != nil { 180 | fmt.Println(err) 181 | } else { 182 | defer iterCh.Close() 183 | 184 | for rec := range iterCh.Records() { 185 | fmt.Printf("%+v\n", rec) 186 | } 187 | } 188 | } 189 | ``` 190 | 191 | ### CustomIterCh 192 | 193 | ```CustomIterCh``` provides all ```IterCh``` functionality and accepts a special ```IterChParams``` value to read settings. 194 | 195 | This method offers a deadline timeout for channel sends and it is *highly recommended* that this method, or Map + Slice, be used where reliability over a long run-time counts. 196 | 197 | ```go 198 | package main 199 | 200 | import ( 201 | "fmt" 202 | "time" 203 | mrand "math/rand" 204 | 205 | "github.com/umpc/go-sortedmap" 206 | "github.com/umpc/go-sortedmap/asc" 207 | ) 208 | 209 | func main() { 210 | const n = 25 211 | records := randRecords(n) 212 | 213 | // Create a new collection. 214 | sm := sortedmap.New(n, asc.Time) 215 | 216 | // BatchInsert the example records: 217 | sm.BatchInsert(records) 218 | 219 | params := sortedmap.IterChParams{ 220 | SendTimeout: 5 * time.Minute, 221 | Reversed: true, 222 | } 223 | 224 | iterCh, err := sm.CustomIterCh(params) 225 | if err != nil { 226 | fmt.Println(err) 227 | } else { 228 | defer iterCh.Close() 229 | 230 | for rec := range iterCh.Records() { 231 | fmt.Printf("%+v\n", rec) 232 | } 233 | } 234 | } 235 | ``` 236 | 237 | ### IterFunc 238 | 239 | ```IterFunc``` is like ```IterCh```, but runs a callback function passing in each record instead of sending the records on a channel. 240 | 241 | ```go 242 | package main 243 | 244 | import ( 245 | "fmt" 246 | "time" 247 | mrand "math/rand" 248 | 249 | "github.com/umpc/go-sortedmap" 250 | "github.com/umpc/go-sortedmap/asc" 251 | ) 252 | 253 | func main() { 254 | const n = 25 255 | records := randRecords(n) 256 | 257 | // Create a new collection. 258 | sm := sortedmap.New(n, asc.Time) 259 | 260 | // BatchInsert the example records: 261 | sm.BatchInsert(records) 262 | 263 | sm.IterFunc(false, func(rec sortedmap.Record) bool { 264 | fmt.Printf("%+v\n", rec) 265 | return true 266 | }) 267 | } 268 | ``` 269 | 270 | ### BoundedIterFunc 271 | 272 | ```BoundedIterFunc``` is the callback function equivalent to ```BoundedIterCh```/```CustomIterCh```. 273 | 274 | ```go 275 | package main 276 | 277 | import ( 278 | "fmt" 279 | "time" 280 | mrand "math/rand" 281 | 282 | "github.com/umpc/go-sortedmap" 283 | "github.com/umpc/go-sortedmap/asc" 284 | ) 285 | 286 | func main() { 287 | const n = 25 288 | records := randRecords(n) 289 | 290 | // Create a new collection. 291 | sm := sortedmap.New(n, asc.Time) 292 | 293 | // BatchInsert the example records: 294 | sm.BatchInsert(records) 295 | 296 | if err := sm.BoundedIterFunc(false, time.Time{}, time.Now(), func(rec sortedmap.Record) bool { 297 | fmt.Printf("%+v\n", rec) 298 | return true 299 | }) 300 | err != nil { 301 | fmt.Println(err) 302 | } 303 | } 304 | ``` 305 | 306 | ### Map & Keys Loop 307 | 308 | The ```Map``` and ```Keys``` methods offer a way of iterating throughout the map using a combination of Go's native map and slice types. 309 | 310 | ```go 311 | package main 312 | 313 | import ( 314 | "fmt" 315 | "time" 316 | mrand "math/rand" 317 | 318 | "github.com/umpc/go-sortedmap" 319 | "github.com/umpc/go-sortedmap/asc" 320 | ) 321 | 322 | func main() { 323 | const n = 25 324 | records := randRecords(n) 325 | 326 | // Create a new collection. 327 | sm := sortedmap.New(n, asc.Time) 328 | 329 | // BatchInsert the example records: 330 | sm.BatchInsert(records) 331 | 332 | m, keys := sm.Map(), sm.Keys() 333 | for _, k := range keys { 334 | fmt.Printf("%+v\n", m[k]) 335 | } 336 | } 337 | ``` 338 | 339 | ### Map & Bounded Keys Loop 340 | 341 | Like the above ```Bounded``` methods, this method allows for selecting only the necessary data for processing. 342 | 343 | ```go 344 | package main 345 | 346 | import ( 347 | "fmt" 348 | "time" 349 | mrand "math/rand" 350 | 351 | "github.com/umpc/go-sortedmap" 352 | "github.com/umpc/go-sortedmap/asc" 353 | ) 354 | 355 | func main() { 356 | const n = 25 357 | records := randRecords(n) 358 | 359 | // Create a new collection. 360 | sm := sortedmap.New(n, asc.Time) 361 | 362 | // BatchInsert the example records: 363 | sm.BatchInsert(records) 364 | 365 | // Copy the map + slice headers. 366 | m := sm.Map() 367 | keys, err := sm.BoundedKeys(time.Time{}, time.Now()) 368 | if err != nil { 369 | fmt.Println(err) 370 | } else { 371 | for _, k := range keys { 372 | fmt.Printf("%+v\n", m[k]) 373 | } 374 | } 375 | } 376 | ``` 377 | 378 | ### BoundedDelete 379 | 380 | ```BoundedDelete``` is a similar pattern as the above ```Bounded``` methods. ```BoundedDelete``` removes values that are greater than the lower bound and lower than or equal to the upper bound. 381 | 382 | ```go 383 | package main 384 | 385 | import ( 386 | "fmt" 387 | "time" 388 | mrand "math/rand" 389 | 390 | "github.com/umpc/go-sortedmap" 391 | "github.com/umpc/go-sortedmap/asc" 392 | ) 393 | 394 | func main() { 395 | const n = 25 396 | records := randRecords(n) 397 | 398 | // Create a new collection. 399 | sm := sortedmap.New(n, asc.Time) 400 | 401 | // BatchInsert the example records: 402 | sm.BatchInsert(records) 403 | 404 | // Delete values after the lower bound value and before or equal to the upper bound value. 405 | if err := sm.BoundedDelete(time.Time{}, time.Now()); err != nil { 406 | fmt.Println(err) 407 | } 408 | } 409 | ``` 410 | 411 | For more options and features, check out the documentation: 412 | 413 | [![GoDoc](https://godoc.org/github.com/umpc/go-sortedmap?status.svg)](https://godoc.org/github.com/umpc/go-sortedmap) -------------------------------------------------------------------------------- /get.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | // Get retrieves a value from the collection, using the given key. 4 | func (sm *SortedMap) Get(key interface{}) (interface{}, bool) { 5 | val, ok := sm.idx[key] 6 | return val, ok 7 | } 8 | 9 | // BatchGet retrieves values with their read statuses from the collection, using the given keys. 10 | func (sm *SortedMap) BatchGet(keys []interface{}) ([]interface{}, []bool) { 11 | vals := make([]interface{}, len(keys)) 12 | results := make([]bool, len(keys)) 13 | 14 | for i, key := range keys { 15 | vals[i], results[i] = sm.idx[key] 16 | } 17 | 18 | return vals, results 19 | } 20 | -------------------------------------------------------------------------------- /get_bench_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | func get1ofNRecords(b *testing.B, n int) { 6 | sm, _, keys, err := newRandSortedMapWithKeys(n) 7 | if err != nil { 8 | b.Fatal(err) 9 | } 10 | 11 | b.ResetTimer() 12 | for i := 0; i < b.N; i++ { 13 | sm.Get(keys[0]) 14 | 15 | b.StopTimer() 16 | sm, _, keys, err = newRandSortedMapWithKeys(n) 17 | if err != nil { 18 | b.Fatal(err) 19 | } 20 | b.StartTimer() 21 | } 22 | } 23 | 24 | func batchGetNofNRecords(b *testing.B, n int) { 25 | sm, _, keys, err := newRandSortedMapWithKeys(n) 26 | if err != nil { 27 | b.Fatal(err) 28 | } 29 | 30 | b.ResetTimer() 31 | for i := 0; i < b.N; i++ { 32 | sm.BatchGet(keys) 33 | 34 | b.StopTimer() 35 | sm, _, keys, err = newRandSortedMapWithKeys(n) 36 | if err != nil { 37 | b.Fatal(err) 38 | } 39 | b.StartTimer() 40 | } 41 | } 42 | 43 | func BenchmarkGet1of1CachedRecords(b *testing.B) { 44 | sm, _, keys, err := newRandSortedMapWithKeys(1) 45 | if err != nil { 46 | b.Fatal(err) 47 | } 48 | 49 | b.ResetTimer() 50 | for i := 0; i < b.N; i++ { 51 | sm.Get(keys[0]) 52 | } 53 | } 54 | 55 | func BenchmarkGet1of1Records(b *testing.B) { 56 | get1ofNRecords(b, 1) 57 | } 58 | 59 | func BenchmarkBatchGet10of10Records(b *testing.B) { 60 | batchGetNofNRecords(b, 10) 61 | } 62 | 63 | func BenchmarkBatchGet100of100Records(b *testing.B) { 64 | batchGetNofNRecords(b, 100) 65 | } 66 | 67 | func BenchmarkBatchGet1000of1000Records(b *testing.B) { 68 | batchGetNofNRecords(b, 1000) 69 | } 70 | 71 | func BenchmarkBatchGet10000of10000Records(b *testing.B) { 72 | batchGetNofNRecords(b, 10000) 73 | } 74 | -------------------------------------------------------------------------------- /get_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | func TestGet(t *testing.T) { 6 | sm, records, err := newSortedMapFromRandRecords(3) 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | 11 | for i := range records { 12 | if val, ok := sm.Get(records[i].Key); val == nil || !ok { 13 | t.Fatalf("TestGet failed: %v", notFoundErr) 14 | } 15 | } 16 | 17 | iterCh, err := sm.IterCh() 18 | if err != nil { 19 | t.Fatal(err) 20 | } else { 21 | defer iterCh.Close() 22 | 23 | if err := verifyRecords(iterCh.Records(), false); err != nil { 24 | t.Fatal(err) 25 | } 26 | } 27 | } 28 | 29 | func TestBatchGet(t *testing.T) { 30 | sm, _, keys, err := newRandSortedMapWithKeys(1000) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | values, results := sm.BatchGet(keys) 36 | for i, ok := range results { 37 | if values[i] == nil || !ok { 38 | t.Fatalf("TestBatchGet failed: %v", notFoundErr) 39 | } 40 | } 41 | 42 | iterCh, err := sm.IterCh() 43 | if err != nil { 44 | t.Fatal(err) 45 | } else { 46 | defer iterCh.Close() 47 | 48 | if err := verifyRecords(iterCh.Records(), false); err != nil { 49 | t.Fatal(err) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /has.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | // Has checks if the key exists in the collection. 4 | func (sm *SortedMap) Has(key interface{}) bool { 5 | _, ok := sm.idx[key] 6 | return ok 7 | } 8 | 9 | // BatchHas checks if the keys exist in the collection and returns a slice containing the results. 10 | func (sm *SortedMap) BatchHas(keys []interface{}) []bool { 11 | results := make([]bool, len(keys)) 12 | for i, key := range keys { 13 | _, results[i] = sm.idx[key] 14 | } 15 | return results 16 | } 17 | -------------------------------------------------------------------------------- /has_bench_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | func has1ofNRecords(b *testing.B, n int) { 6 | sm, _, keys, err := newRandSortedMapWithKeys(n) 7 | if err != nil { 8 | b.Fatal(err) 9 | } 10 | 11 | b.ResetTimer() 12 | for i := 0; i < b.N; i++ { 13 | sm.Has(keys[0]) 14 | 15 | b.StopTimer() 16 | sm, _, keys, err = newRandSortedMapWithKeys(n) 17 | if err != nil { 18 | b.Fatal(err) 19 | } 20 | b.StartTimer() 21 | } 22 | } 23 | 24 | func batchHasNofNRecords(b *testing.B, n int) { 25 | sm, _, keys, err := newRandSortedMapWithKeys(n) 26 | if err != nil { 27 | b.Fatal(err) 28 | } 29 | 30 | b.ResetTimer() 31 | for i := 0; i < b.N; i++ { 32 | sm.BatchHas(keys) 33 | 34 | b.StopTimer() 35 | sm, _, keys, err = newRandSortedMapWithKeys(n) 36 | if err != nil { 37 | b.Fatal(err) 38 | } 39 | b.StartTimer() 40 | } 41 | } 42 | 43 | func BenchmarkHas1of1CachedRecords(b *testing.B) { 44 | sm, _, keys, err := newRandSortedMapWithKeys(1) 45 | if err != nil { 46 | b.Fatal(err) 47 | } 48 | 49 | b.ResetTimer() 50 | for i := 0; i < b.N; i++ { 51 | sm.Has(keys[0]) 52 | } 53 | } 54 | 55 | func BenchmarkHas1of1Records(b *testing.B) { 56 | has1ofNRecords(b, 1) 57 | } 58 | 59 | func BenchmarkBatchHas10of10Records(b *testing.B) { 60 | batchHasNofNRecords(b, 10) 61 | } 62 | 63 | func BenchmarkBatchHas100of100Records(b *testing.B) { 64 | batchHasNofNRecords(b, 100) 65 | } 66 | 67 | func BenchmarkBatchHas1000of1000Records(b *testing.B) { 68 | batchHasNofNRecords(b, 1000) 69 | } 70 | 71 | func BenchmarkBatchHas10000of10000Records(b *testing.B) { 72 | batchHasNofNRecords(b, 10000) 73 | } 74 | -------------------------------------------------------------------------------- /has_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | func TestHas(t *testing.T) { 6 | sm, records, err := newSortedMapFromRandRecords(3) 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | 11 | for i := range records { 12 | if !sm.Has(records[i].Key) { 13 | t.Fatalf("TestHas failed: %v", notFoundErr) 14 | } 15 | } 16 | 17 | iterCh, err := sm.IterCh() 18 | if err != nil { 19 | t.Fatal(err) 20 | } else { 21 | defer iterCh.Close() 22 | 23 | if err := verifyRecords(iterCh.Records(), false); err != nil { 24 | t.Fatal(err) 25 | } 26 | } 27 | } 28 | 29 | func TestBatchHas(t *testing.T) { 30 | sm, _, keys, err := newRandSortedMapWithKeys(1000) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | for _, ok := range sm.BatchHas(keys) { 36 | if !ok { 37 | t.Fatalf("TestBatchHas failed: %v", notFoundErr) 38 | } 39 | } 40 | 41 | iterCh, err := sm.IterCh() 42 | if err != nil { 43 | t.Fatal(err) 44 | } else { 45 | defer iterCh.Close() 46 | 47 | if err := verifyRecords(iterCh.Records(), false); err != nil { 48 | t.Fatal(err) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /insert.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func (sm *SortedMap) insert(key, val interface{}) bool { 9 | if _, ok := sm.idx[key]; !ok { 10 | sm.idx[key] = val 11 | sm.sorted = sm.insertSort(key, val) 12 | return true 13 | } 14 | return false 15 | } 16 | 17 | // Insert uses the provided 'less than' function to insert sort and add the value to the collection and returns a value containing the record's insert status. 18 | // If the key already exists, the value will not be inserted. Use Replace for the alternative functionality. 19 | func (sm *SortedMap) Insert(key, val interface{}) bool { 20 | return sm.insert(key, val) 21 | } 22 | 23 | // BatchInsert adds all given records to the collection and returns a slice containing each record's insert status. 24 | // If a key already exists, the value will not be inserted. Use BatchReplace for the alternative functionality. 25 | func (sm *SortedMap) BatchInsert(recs []Record) []bool { 26 | results := make([]bool, len(recs)) 27 | for i, rec := range recs { 28 | results[i] = sm.insert(rec.Key, rec.Val) 29 | } 30 | return results 31 | } 32 | 33 | func (sm *SortedMap) batchInsertMapInterfaceKeys(m map[interface{}]interface{}) error { 34 | for key, val := range m { 35 | if !sm.insert(key, val) { 36 | return fmt.Errorf("Key already exists: %+v", key) 37 | } 38 | } 39 | return nil 40 | } 41 | 42 | func (sm *SortedMap) batchInsertMapStringKeys(m map[string]interface{}) error { 43 | for key, val := range m { 44 | if !sm.insert(key, val) { 45 | return fmt.Errorf("Key already exists: %+v", key) 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | // BatchInsertMap adds all map keys and values to the collection. 52 | // If a key already exists, the value will not be inserted and an error will be returned. 53 | // Use BatchReplaceMap for the alternative functionality. 54 | func (sm *SortedMap) BatchInsertMap(v interface{}) error { 55 | const unsupportedTypeErr = "Unsupported type." 56 | 57 | switch m := v.(type) { 58 | case map[interface{}]interface{}: 59 | return sm.batchInsertMapInterfaceKeys(m) 60 | 61 | case map[string]interface{}: 62 | return sm.batchInsertMapStringKeys(m) 63 | 64 | default: 65 | return errors.New(unsupportedTypeErr) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /insert_bench_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/umpc/go-sortedmap/asc" 7 | ) 8 | 9 | func insertRecord(b *testing.B) { 10 | records := randRecords(1) 11 | sm := New(0, asc.Time) 12 | 13 | b.ResetTimer() 14 | for i := 0; i < b.N; i++ { 15 | sm.Insert(records[0].Key, records[0].Val) 16 | 17 | b.StopTimer() 18 | records = randRecords(1) 19 | sm = New(0, asc.Time) 20 | b.StartTimer() 21 | } 22 | } 23 | 24 | func batchInsertRecords(b *testing.B, n int) { 25 | records := randRecords(n) 26 | sm := New(0, asc.Time) 27 | 28 | b.ResetTimer() 29 | for i := 0; i < b.N; i++ { 30 | sm.BatchInsert(records) 31 | 32 | b.StopTimer() 33 | records = randRecords(n) 34 | sm = New(0, asc.Time) 35 | b.StartTimer() 36 | } 37 | } 38 | 39 | func BenchmarkInsert1Record(b *testing.B) { 40 | insertRecord(b) 41 | } 42 | 43 | func BenchmarkBatchInsert10Records(b *testing.B) { 44 | batchInsertRecords(b, 10) 45 | } 46 | 47 | func BenchmarkBatchInsert100Records(b *testing.B) { 48 | batchInsertRecords(b, 100) 49 | } 50 | 51 | func BenchmarkBatchInsert1000Records(b *testing.B) { 52 | batchInsertRecords(b, 1000) 53 | } 54 | 55 | func BenchmarkBatchInsert10000Records(b *testing.B) { 56 | batchInsertRecords(b, 10000) 57 | } 58 | -------------------------------------------------------------------------------- /insert_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/umpc/go-sortedmap/asc" 7 | ) 8 | 9 | func TestInsert(t *testing.T) { 10 | const n = 3 11 | records := randRecords(n) 12 | sm := New(n, asc.Time) 13 | 14 | for i := range records { 15 | if !sm.Insert(records[i].Key, records[i].Val) { 16 | t.Fatalf("Insert failed: %v", keyExistsErr) 17 | } 18 | } 19 | 20 | func() { 21 | iterCh, err := sm.IterCh() 22 | if err != nil { 23 | t.Fatal(err) 24 | } else { 25 | defer iterCh.Close() 26 | 27 | if err := verifyRecords(iterCh.Records(), false); err != nil { 28 | t.Fatal(err) 29 | } 30 | } 31 | }() 32 | 33 | for i := range records { 34 | if sm.Insert(records[i].Key, records[i].Val) { 35 | t.Fatalf("Insert failed: %v", notFoundErr) 36 | } 37 | } 38 | 39 | func() { 40 | iterCh, err := sm.IterCh() 41 | if err != nil { 42 | t.Fatal(err) 43 | } else { 44 | defer iterCh.Close() 45 | 46 | if err := verifyRecords(iterCh.Records(), false); err != nil { 47 | t.Fatal(err) 48 | } 49 | } 50 | }() 51 | } 52 | 53 | func TestBatchInsert(t *testing.T) { 54 | const n = 1000 55 | records := randRecords(n) 56 | sm := New(n, asc.Time) 57 | 58 | for _, ok := range sm.BatchInsert(records) { 59 | if !ok { 60 | t.Fatalf("BatchInsert failed: %v", keyExistsErr) 61 | } 62 | } 63 | 64 | func() { 65 | iterCh, err := sm.IterCh() 66 | if err != nil { 67 | t.Fatal(err) 68 | } else { 69 | defer iterCh.Close() 70 | 71 | if err := verifyRecords(iterCh.Records(), false); err != nil { 72 | t.Fatal(err) 73 | } 74 | } 75 | }() 76 | } 77 | 78 | func TestBatchInsertMapWithInterfaceKeys(t *testing.T) { 79 | const n = 1000 80 | records := randRecords(n) 81 | sm := New(n, asc.Time) 82 | 83 | i := 0 84 | m := make(map[interface{}]interface{}, n) 85 | 86 | for _, rec := range records { 87 | m[rec.Key] = rec.Val 88 | i++ 89 | } 90 | if i == 0 { 91 | t.Fatal("Records were not copied to the map.") 92 | } 93 | 94 | if err := sm.BatchInsertMap(m); err != nil { 95 | t.Fatal(err) 96 | } 97 | } 98 | 99 | func TestBatchInsertMapWithStringKeys(t *testing.T) { 100 | const n = 1000 101 | records := randRecords(n) 102 | sm := New(n, asc.Time) 103 | 104 | i := 0 105 | m := make(map[string]interface{}, n) 106 | 107 | for _, rec := range records { 108 | m[rec.Key.(string)] = rec.Val 109 | i++ 110 | } 111 | if i == 0 { 112 | t.Fatal("Records were not copied to the map.") 113 | } 114 | 115 | if err := sm.BatchInsertMap(m); err != nil { 116 | t.Fatal(err) 117 | } 118 | } 119 | 120 | func TestBatchInsertMapWithExistingInterfaceKeys(t *testing.T) { 121 | const n = 1000 122 | sm, records, err := newSortedMapFromRandRecords(1000) 123 | if err != nil { 124 | t.Fatal(err) 125 | } 126 | 127 | i := 0 128 | m := make(map[interface{}]interface{}, n) 129 | 130 | for _, rec := range records { 131 | m[rec.Key] = rec.Val 132 | i++ 133 | } 134 | if i == 0 { 135 | t.Fatal("Records were not copied to the map.") 136 | } 137 | 138 | if err := sm.BatchInsertMap(m); err == nil { 139 | t.Fatal("Inserting existing keys should have caused an error.") 140 | } 141 | } 142 | 143 | func TestBatchInsertMapWithExistingStringKeys(t *testing.T) { 144 | const n = 1000 145 | sm, records, err := newSortedMapFromRandRecords(1000) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | 150 | i := 0 151 | m := make(map[string]interface{}, n) 152 | 153 | for _, rec := range records { 154 | m[rec.Key.(string)] = rec.Val 155 | i++ 156 | } 157 | if i == 0 { 158 | t.Fatal("Records were not copied to the map.") 159 | } 160 | 161 | if err := sm.BatchInsertMap(m); err == nil { 162 | t.Fatal("Inserting existing keys should have caused an error.") 163 | } 164 | } 165 | 166 | func TestBatchInsertMapWithNilType(t *testing.T) { 167 | if err := New(0, asc.Time).BatchInsertMap(nil); err == nil { 168 | t.Fatal("a nil type was allowed where a supported map type is required.") 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /insertsort.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "sort" 4 | 5 | func (sm *SortedMap) insertSort(key, val interface{}) []interface{} { 6 | return insertInterface(sm.sorted, key, sort.Search(len(sm.sorted), func(i int) bool { 7 | return sm.lessFn(val, sm.idx[sm.sorted[i]]) 8 | })) 9 | } 10 | -------------------------------------------------------------------------------- /iter.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | // IterChCloser allows records to be read through a channel that is returned by the Records method. 9 | // IterChCloser values should be closed after use using the Close method. 10 | type IterChCloser struct { 11 | ch chan Record 12 | canceled chan struct{} 13 | } 14 | 15 | // Close cancels a channel-based iteration and causes the sending goroutine to exit. 16 | // Close should be used after an IterChCloser is finished being read from. 17 | func (iterCh *IterChCloser) Close() error { 18 | close(iterCh.canceled) 19 | return nil 20 | } 21 | 22 | // Records returns a channel that records can be read from. 23 | func (iterCh *IterChCloser) Records() <-chan Record { 24 | return iterCh.ch 25 | } 26 | 27 | // IterChParams contains configurable settings for CustomIterCh. 28 | // SendTimeout is disabled by default, though it should be set to allow 29 | // channel send goroutines to time-out. 30 | // BufSize is set to 1 if its field is set to a lower value. 31 | // LowerBound and UpperBound default to regular iteration when left unset. 32 | type IterChParams struct { 33 | Reversed bool 34 | SendTimeout time.Duration 35 | BufSize int 36 | LowerBound, 37 | UpperBound interface{} 38 | } 39 | 40 | // IterCallbackFunc defines the type of function that is passed into an IterFunc method. 41 | // The function is passed a record value argument. 42 | type IterCallbackFunc func(rec Record) bool 43 | 44 | func setBufSize(bufSize int) int { 45 | // initialBufSize must be >= 1 or a blocked channel send goroutine may not exit. 46 | // More info: https://github.com/golang/go/wiki/Timeouts 47 | const initialBufSize = 1 48 | 49 | if bufSize < initialBufSize { 50 | return initialBufSize 51 | } 52 | return bufSize 53 | } 54 | 55 | func (sm *SortedMap) recordFromIdx(i int) Record { 56 | rec := Record{} 57 | rec.Key = sm.sorted[i] 58 | rec.Val = sm.idx[rec.Key] 59 | 60 | return rec 61 | } 62 | 63 | func (sm *SortedMap) sendRecord(iterCh IterChCloser, sendTimeout time.Duration, i int) bool { 64 | 65 | if sendTimeout <= time.Duration(0) { 66 | select { 67 | case <-iterCh.canceled: 68 | return false 69 | 70 | case iterCh.ch <- sm.recordFromIdx(i): 71 | return true 72 | } 73 | } 74 | 75 | select { 76 | case <-iterCh.canceled: 77 | return false 78 | 79 | case iterCh.ch <- sm.recordFromIdx(i): 80 | return true 81 | 82 | case <-time.After(sendTimeout): 83 | return false 84 | } 85 | } 86 | 87 | func (sm *SortedMap) iterCh(params IterChParams) (IterChCloser, error) { 88 | 89 | iterBounds := sm.boundsIdxSearch(params.LowerBound, params.UpperBound) 90 | if iterBounds == nil { 91 | return IterChCloser{}, errors.New(noValuesErr) 92 | } 93 | 94 | iterCh := IterChCloser{ 95 | ch: make(chan Record, setBufSize(params.BufSize)), 96 | canceled: make(chan struct{}), 97 | } 98 | 99 | go func(params IterChParams, iterCh IterChCloser) { 100 | if params.Reversed { 101 | for i := iterBounds[1]; i >= iterBounds[0]; i-- { 102 | if !sm.sendRecord(iterCh, params.SendTimeout, i) { 103 | break 104 | } 105 | } 106 | } else { 107 | for i := iterBounds[0]; i <= iterBounds[1]; i++ { 108 | if !sm.sendRecord(iterCh, params.SendTimeout, i) { 109 | break 110 | } 111 | } 112 | } 113 | close(iterCh.ch) 114 | }(params, iterCh) 115 | 116 | return iterCh, nil 117 | } 118 | 119 | func (sm *SortedMap) iterFunc(reversed bool, lowerBound, upperBound interface{}, f IterCallbackFunc) error { 120 | 121 | iterBounds := sm.boundsIdxSearch(lowerBound, upperBound) 122 | if iterBounds == nil { 123 | return errors.New(noValuesErr) 124 | } 125 | 126 | if reversed { 127 | for i := iterBounds[1]; i >= iterBounds[0]; i-- { 128 | if !f(sm.recordFromIdx(i)) { 129 | break 130 | } 131 | } 132 | } else { 133 | for i := iterBounds[0]; i <= iterBounds[1]; i++ { 134 | if !f(sm.recordFromIdx(i)) { 135 | break 136 | } 137 | } 138 | } 139 | 140 | return nil 141 | } 142 | 143 | // IterCh returns a channel that sorted records can be read from and processed. 144 | // This method defaults to the expected behavior of blocking until a read, with no timeout. 145 | func (sm *SortedMap) IterCh() (IterChCloser, error) { 146 | return sm.iterCh(IterChParams{}) 147 | } 148 | 149 | // BoundedIterCh returns a channel that sorted records can be read from and processed. 150 | // BoundedIterCh starts at the lower bound value and sends all values in the collection until reaching the upper bounds value. 151 | // Sort order is reversed if the reversed argument is set to true. 152 | // This method defaults to the expected behavior of blocking until a channel send completes, with no timeout. 153 | func (sm *SortedMap) BoundedIterCh(reversed bool, lowerBound, upperBound interface{}) (IterChCloser, error) { 154 | return sm.iterCh(IterChParams{ 155 | Reversed: reversed, 156 | LowerBound: lowerBound, 157 | UpperBound: upperBound, 158 | }) 159 | } 160 | 161 | // CustomIterCh returns a channel that sorted records can be read from and processed. 162 | // CustomIterCh starts at the lower bound value and sends all values in the collection until reaching the upper bounds value. 163 | // Sort order is reversed if the reversed argument is set to true. 164 | // This method defaults to the expected behavior of blocking until a channel send completes, with no timeout. 165 | func (sm *SortedMap) CustomIterCh(params IterChParams) (IterChCloser, error) { 166 | return sm.iterCh(params) 167 | } 168 | 169 | // IterFunc passes each record to the specified callback function. 170 | // Sort order is reversed if the reversed argument is set to true. 171 | func (sm *SortedMap) IterFunc(reversed bool, f IterCallbackFunc) { 172 | sm.iterFunc(reversed, nil, nil, f) 173 | } 174 | 175 | // BoundedIterFunc starts at the lower bound value and passes all values in the collection to the callback function until reaching the upper bounds value. 176 | // Sort order is reversed if the reversed argument is set to true. 177 | func (sm *SortedMap) BoundedIterFunc(reversed bool, lowerBound, upperBound interface{}, f IterCallbackFunc) error { 178 | return sm.iterFunc(reversed, lowerBound, upperBound, f) 179 | } 180 | -------------------------------------------------------------------------------- /iter_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/umpc/go-sortedmap/asc" 8 | ) 9 | 10 | const ( 11 | nilBoundValsErr = "accepted nil bound values" 12 | generalBoundsErr = "between bounds error" 13 | nilValErr = "nil value!" 14 | nonNilValErr = "non-nil value" 15 | runawayIterErr = "runaway iterator!" 16 | ) 17 | 18 | var maxTime = time.Unix(1<<63-62135596801, 999999999) 19 | 20 | func TestIterCh(t *testing.T) { 21 | if _, _, err := newSortedMapFromRandRecords(1000); err != nil { 22 | t.Fatal(err) 23 | } 24 | } 25 | 26 | func TestIterChTimeout(t *testing.T) { 27 | sm, _, err := newSortedMapFromRandRecords(1000) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | timeout := 1 * time.Microsecond 32 | sleepDur := 10 * time.Millisecond 33 | 34 | params := IterChParams{ 35 | SendTimeout: timeout, 36 | } 37 | 38 | ch, err := sm.CustomIterCh(params) 39 | if err != nil { 40 | t.Fatal(err) 41 | } else { 42 | func() { 43 | defer ch.Close() 44 | 45 | for i := 0; i < 5; i++ { 46 | time.Sleep(sleepDur) 47 | rec := <-ch.Records() 48 | if i > 1 && rec.Key != nil { 49 | t.Fatalf("TestIterChTimeout failed: %v: %v", nonNilValErr, rec.Key) 50 | } 51 | } 52 | }() 53 | } 54 | 55 | params.LowerBound = time.Time{} 56 | params.UpperBound = maxTime 57 | 58 | ch, err = sm.CustomIterCh(params) 59 | if err != nil { 60 | t.Fatal(err) 61 | } else { 62 | func() { 63 | defer ch.Close() 64 | 65 | for i := 0; i < 5; i++ { 66 | time.Sleep(sleepDur) 67 | rec := <-ch.Records() 68 | if i > 1 && rec.Key != nil { 69 | t.Fatalf("TestIterChTimeout failed: %v: %v", nonNilValErr, rec.Key) 70 | } 71 | } 72 | }() 73 | } 74 | } 75 | 76 | func TestReversedIterChTimeout(t *testing.T) { 77 | sm, _, err := newSortedMapFromRandRecords(1000) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | timeout := 1 * time.Microsecond 82 | sleepDur := 10 * time.Millisecond 83 | 84 | params := IterChParams{ 85 | Reversed: true, 86 | SendTimeout: timeout, 87 | } 88 | 89 | ch, err := sm.CustomIterCh(params) 90 | if err != nil { 91 | t.Fatal(err) 92 | } else { 93 | func() { 94 | defer ch.Close() 95 | 96 | for i := 0; i < 5; i++ { 97 | time.Sleep(sleepDur) 98 | rec := <-ch.Records() 99 | if i > 1 && rec.Key != nil { 100 | t.Fatalf("TestReversedIterChTimeout failed: %v: %v", nonNilValErr, rec.Key) 101 | } 102 | } 103 | }() 104 | } 105 | 106 | params.LowerBound = time.Time{} 107 | params.UpperBound = maxTime 108 | 109 | ch, err = sm.CustomIterCh(params) 110 | if err != nil { 111 | t.Fatal(err) 112 | } else { 113 | func() { 114 | defer ch.Close() 115 | 116 | for i := 0; i < 5; i++ { 117 | time.Sleep(sleepDur) 118 | rec := <-ch.Records() 119 | if i > 1 && rec.Key != nil { 120 | t.Fatalf("TestReversedIterChTimeout failed: %v: %v", nonNilValErr, rec.Key) 121 | } 122 | } 123 | }() 124 | } 125 | } 126 | 127 | func TestBoundedIterCh(t *testing.T) { 128 | sm, _, err := newSortedMapFromRandRecords(1000) 129 | if err != nil { 130 | t.Fatal(err) 131 | } 132 | 133 | reversed := false 134 | earlierDate := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) 135 | laterDate := time.Date(5321, 1, 1, 0, 0, 0, 0, time.UTC) 136 | 137 | ch, err := sm.BoundedIterCh(reversed, nil, nil) 138 | if err != nil { 139 | t.Fatal(err) 140 | } else { 141 | func() { 142 | defer ch.Close() 143 | 144 | if err := verifyRecords(ch.Records(), reversed); err != nil { 145 | t.Fatal(err) 146 | } 147 | }() 148 | } 149 | 150 | ch, err = sm.BoundedIterCh(reversed, time.Time{}, maxTime) 151 | if err != nil { 152 | t.Fatal(err) 153 | } else { 154 | func() { 155 | defer ch.Close() 156 | 157 | if err := verifyRecords(ch.Records(), reversed); err != nil { 158 | t.Fatal(err) 159 | } 160 | }() 161 | } 162 | 163 | ch, err = sm.BoundedIterCh(reversed, earlierDate, time.Now()) 164 | if err != nil { 165 | t.Fatal(err) 166 | } else { 167 | func() { 168 | defer ch.Close() 169 | 170 | if err := verifyRecords(ch.Records(), reversed); err != nil { 171 | t.Fatal(err) 172 | } 173 | }() 174 | } 175 | 176 | ch, err = sm.BoundedIterCh(reversed, time.Now(), laterDate) 177 | if err != nil { 178 | t.Fatal(err) 179 | } else { 180 | func() { 181 | defer ch.Close() 182 | 183 | if err := verifyRecords(ch.Records(), reversed); err != nil { 184 | t.Fatal(err) 185 | } 186 | }() 187 | } 188 | 189 | if _, err := sm.BoundedIterCh(reversed, laterDate, laterDate); err == nil { 190 | t.Fatalf("TestBoundedIterCh failed: %v", "equal bounds values were accepted error") 191 | } 192 | } 193 | 194 | func TestBounds(t *testing.T) { 195 | sm := New(4, asc.Time) 196 | 197 | obsd := time.Date(1995, 10, 18, 8, 37, 1, 0, time.UTC) 198 | unixtime := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) 199 | linux := time.Date(1991, 8, 25, 20, 57, 8, 0, time.UTC) 200 | github := time.Date(2008, 4, 10, 0, 0, 0, 0, time.UTC) 201 | 202 | sm.Insert("OpenBSD", obsd) 203 | reversed := false 204 | 205 | // Test bounds combinations which should not match against the value currently in the map 206 | for _, bounds := range [][]interface{}{ 207 | {unixtime, linux}, {nil, unixtime}, {github, nil}, 208 | } { 209 | _, err := sm.BoundedIterCh(reversed, bounds[0], bounds[1]) 210 | if err == nil || err.Error() != noValuesErr { 211 | t.Fatalf("expected no values match error using bounds (lower: %v, upper: %v)", bounds[0], bounds[1]) 212 | } 213 | } 214 | 215 | // Test bounds combinations which should match against the value currently in the map 216 | for _, bounds := range [][]interface{}{ 217 | {unixtime, github}, {unixtime, nil}, {nil, github}, 218 | } { 219 | iterCh, err := sm.BoundedIterCh(reversed, bounds[0], bounds[1]) 220 | if err != nil { 221 | t.Fatal(err) 222 | } 223 | for rec := range iterCh.Records() { 224 | if rec.Val.(time.Time) != obsd { 225 | t.Fatal("unexpected value returned by bounded iterator") 226 | } 227 | } 228 | iterCh.Close() 229 | } 230 | 231 | sm.Insert("UnixTime", unixtime) 232 | sm.Insert("Linux", linux) 233 | sm.Insert("GitHub", github) 234 | 235 | func() { 236 | iterCh, err := sm.BoundedIterCh(reversed, time.Time{}, unixtime) 237 | if err != nil { 238 | t.Fatal(err) 239 | } else { 240 | defer iterCh.Close() 241 | 242 | if err := verifyRecords(iterCh.Records(), reversed); err != nil { 243 | t.Fatal(err) 244 | } 245 | } 246 | }() 247 | 248 | func() { 249 | iterCh, err := sm.BoundedIterCh(reversed, obsd, github) 250 | if err != nil { 251 | t.Fatal(err) 252 | } else { 253 | defer iterCh.Close() 254 | 255 | if err := verifyRecords(iterCh.Records(), reversed); err != nil { 256 | t.Fatal(err) 257 | } 258 | } 259 | }() 260 | 261 | _, err := sm.BoundedIterCh(reversed, obsd, obsd) 262 | if err == nil { 263 | t.Fatal("equal bounds values were accepted error") 264 | } 265 | } 266 | 267 | func TestCustomIterCh(t *testing.T) { 268 | const ( 269 | nilBoundValsErr = "accepted two nil bound values" 270 | generalBoundsErr = "only one bound value" 271 | ) 272 | sm, _, err := newSortedMapFromRandRecords(1000) 273 | if err != nil { 274 | t.Fatal(err) 275 | } 276 | 277 | reversed := true 278 | buffSize := 64 279 | 280 | earlierDate := time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC) 281 | laterDate := time.Now() 282 | 283 | params := IterChParams{ 284 | Reversed: reversed, 285 | BufSize: buffSize, 286 | } 287 | 288 | func() { 289 | ch, err := sm.CustomIterCh(params) 290 | if err != nil { 291 | t.Fatal(err) 292 | } 293 | defer ch.Close() 294 | 295 | if err := verifyRecords(ch.Records(), params.Reversed); err != nil { 296 | t.Fatal(err) 297 | } 298 | }() 299 | 300 | params = IterChParams{ 301 | Reversed: reversed, 302 | BufSize: buffSize, 303 | LowerBound: earlierDate, 304 | UpperBound: laterDate, 305 | } 306 | 307 | func() { 308 | ch, err := sm.CustomIterCh(params) 309 | if err != nil { 310 | t.Fatal(err) 311 | } 312 | defer ch.Close() 313 | 314 | if err := verifyRecords(ch.Records(), params.Reversed); err != nil { 315 | t.Fatal(err) 316 | } 317 | }() 318 | 319 | params = IterChParams{ 320 | Reversed: reversed, 321 | BufSize: buffSize, 322 | LowerBound: laterDate, 323 | UpperBound: earlierDate, 324 | } 325 | 326 | func() { 327 | _, err := sm.CustomIterCh(params) 328 | if err == nil { 329 | t.Fatal(err) 330 | } 331 | }() 332 | 333 | reversed = false 334 | params = IterChParams{ 335 | Reversed: reversed, 336 | BufSize: 0, 337 | LowerBound: laterDate, 338 | UpperBound: earlierDate, 339 | } 340 | 341 | func() { 342 | _, err := sm.CustomIterCh(params) 343 | if err == nil { 344 | t.Fatal(err) 345 | } 346 | }() 347 | } 348 | 349 | func TestCloseCustomIterCh(t *testing.T) { 350 | sm, _, err := newSortedMapFromRandRecords(1000) 351 | if err != nil { 352 | t.Fatal(err) 353 | } 354 | 355 | earlierDate := time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC) 356 | laterDate := time.Now() 357 | 358 | ch, err := sm.IterCh() 359 | if err != nil { 360 | t.Fatal(err) 361 | } 362 | 363 | ch.Close() 364 | 365 | params := IterChParams{ 366 | SendTimeout: 5 * time.Minute, 367 | LowerBound: earlierDate, 368 | UpperBound: laterDate, 369 | } 370 | 371 | ch, err = sm.CustomIterCh(params) 372 | if err != nil { 373 | t.Fatal(err) 374 | } 375 | 376 | ch.Close() 377 | } 378 | 379 | func TestIterFunc(t *testing.T) { 380 | sm, _, err := newSortedMapFromRandRecords(1000) 381 | if err != nil { 382 | t.Fatal(err) 383 | } 384 | sm.IterFunc(false, func(rec Record) bool { 385 | if rec.Key == nil { 386 | t.Fatalf("TestIterFunc failed: %v", nilValErr) 387 | } 388 | return true 389 | }) 390 | sm.IterFunc(true, func(rec Record) bool { 391 | if rec.Key == nil { 392 | t.Fatalf("TestIterFunc failed: %v", nilValErr) 393 | } 394 | return true 395 | }) 396 | i := 0 397 | sm.IterFunc(false, func(rec Record) bool { 398 | if i > 0 { 399 | t.Fatalf("TestIterFunc failed: %v", runawayIterErr) 400 | } 401 | i++ 402 | return false 403 | }) 404 | i = 0 405 | sm.IterFunc(true, func(rec Record) bool { 406 | if i > 0 { 407 | t.Fatalf("TestIterFunc failed: %v", runawayIterErr) 408 | } 409 | i++ 410 | return false 411 | }) 412 | } 413 | 414 | func TestBoundedIterFunc(t *testing.T) { 415 | sm, _, err := newSortedMapFromRandRecords(1000) 416 | if err != nil { 417 | t.Fatal(err) 418 | } 419 | 420 | earlierDate := time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC) 421 | laterDate := time.Now() 422 | 423 | if err := sm.BoundedIterFunc(false, nil, nil, func(rec Record) bool { 424 | if rec.Key == nil { 425 | t.Fatalf("TestBoundedIterFunc failed: %v", nilValErr) 426 | } 427 | return false 428 | }); err != nil { 429 | t.Fatalf("TestBoundedIterFunc failed: %v", err) 430 | } 431 | 432 | if err := sm.BoundedIterFunc(false, nil, laterDate, func(rec Record) bool { 433 | if rec.Key == nil { 434 | t.Fatalf("TestBoundedIterFunc failed: %v", nilValErr) 435 | } 436 | return false 437 | }); err != nil { 438 | t.Fatalf("TestBoundedIterFunc failed: %v", err) 439 | } 440 | 441 | if err := sm.BoundedIterFunc(false, laterDate, nil, func(rec Record) bool { 442 | if rec.Key == nil { 443 | t.Fatalf("TestBoundedIterFunc failed: %v", nilValErr) 444 | } 445 | return false 446 | }); err != nil { 447 | t.Fatalf("TestBoundedIterFunc failed: %v", err) 448 | } 449 | 450 | if err := sm.BoundedIterFunc(false, earlierDate, laterDate, func(rec Record) bool { 451 | if rec.Key == nil { 452 | t.Fatalf("TestBoundedIterFunc failed: %v", nilValErr) 453 | } 454 | return false 455 | }); err != nil { 456 | t.Fatalf("TestBoundedIterFunc failed: %v", err) 457 | } 458 | } 459 | 460 | func TestBoundedIterFuncWithNoBoundsReturned(t *testing.T) { 461 | sm, _, err := newSortedMapFromRandRecords(1000) 462 | if err != nil { 463 | t.Fatal(err) 464 | } 465 | if err := sm.BoundedIterFunc(false, time.Date(5783, 1, 1, 0, 0, 0, 0, time.UTC), time.Now(), func(rec Record) bool { 466 | if rec.Key == nil { 467 | t.Fatal(nilValErr) 468 | } 469 | return false 470 | }); err == nil { 471 | t.Fatal("Values fall between or are equal to the given bounds when it should not have returned bounds.") 472 | } 473 | } 474 | 475 | func TestReversedBoundedIterFunc(t *testing.T) { 476 | sm, _, err := newSortedMapFromRandRecords(1000) 477 | if err != nil { 478 | t.Fatal(err) 479 | } 480 | 481 | earlierDate := time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC) 482 | laterDate := time.Now() 483 | 484 | if err := sm.BoundedIterFunc(true, nil, nil, func(rec Record) bool { 485 | if rec.Key == nil { 486 | t.Fatalf("TestReversedBoundedIterFunc failed: %v", nilValErr) 487 | } 488 | return false 489 | }); err != nil { 490 | t.Fatalf("TestReversedBoundedIterFunc failed: %v", err) 491 | } 492 | 493 | if err := sm.BoundedIterFunc(true, nil, laterDate, func(rec Record) bool { 494 | if rec.Key == nil { 495 | t.Fatalf("TestReversedBoundedIterFunc failed: %v", nilValErr) 496 | } 497 | return false 498 | }); err != nil { 499 | t.Fatalf("TestReversedBoundedIterFunc failed: %v", err) 500 | } 501 | 502 | if err := sm.BoundedIterFunc(true, laterDate, nil, func(rec Record) bool { 503 | if rec.Key == nil { 504 | t.Fatalf("TestBoundedIterFunc failed: %v", nilValErr) 505 | } 506 | return false 507 | }); err != nil { 508 | t.Fatalf("TestReversedBoundedIterFunc failed: %v", err) 509 | } 510 | 511 | if err := sm.BoundedIterFunc(true, earlierDate, laterDate, func(rec Record) bool { 512 | if rec.Key == nil { 513 | t.Fatalf("TestBoundedIterFunc failed: %v", nilValErr) 514 | } 515 | return false 516 | }); err != nil { 517 | t.Fatalf("TestReversedBoundedIterFunc failed: %v", err) 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "errors" 4 | 5 | func (sm *SortedMap) keys(lowerBound, upperBound interface{}) ([]interface{}, error) { 6 | idxBounds := sm.boundsIdxSearch(lowerBound, upperBound) 7 | if idxBounds == nil { 8 | return nil, errors.New(noValuesErr) 9 | } 10 | return sm.sorted[idxBounds[0] : idxBounds[1]+1], nil 11 | } 12 | 13 | // Keys returns a slice containing sorted keys. 14 | // The returned slice is valid until the next modification to the SortedMap structure. 15 | func (sm *SortedMap) Keys() []interface{} { 16 | keys, _ := sm.keys(nil, nil) 17 | return keys 18 | } 19 | 20 | // BoundedKeys returns a slice containing sorted keys equal to or between the given bounds. 21 | // The returned slice is valid until the next modification to the SortedMap structure. 22 | func (sm *SortedMap) BoundedKeys(lowerBound, upperBound interface{}) ([]interface{}, error) { 23 | return sm.keys(lowerBound, upperBound) 24 | } 25 | -------------------------------------------------------------------------------- /keys_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestKeys(t *testing.T) { 9 | sm, _, err := newSortedMapFromRandRecords(300) 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | i := 0 14 | keys := sm.Keys() 15 | for _, key := range keys { 16 | if key == nil { 17 | t.Fatal("Key's value is nil.") 18 | } 19 | i++ 20 | } 21 | if i == 0 { 22 | t.Fatal("The returned slice was empty.") 23 | } 24 | } 25 | 26 | func TestBoundedKeys(t *testing.T) { 27 | sm, _, err := newSortedMapFromRandRecords(300) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | i := 0 32 | keys, err := sm.BoundedKeys(time.Time{}, time.Now()) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | for _, key := range keys { 37 | if key == nil { 38 | t.Fatal("Key's value is nil.") 39 | } 40 | i++ 41 | } 42 | if i == 0 { 43 | t.Fatal("The returned slice was empty.") 44 | } 45 | } 46 | 47 | func TestBoundedKeysWithNoBoundsReturned(t *testing.T) { 48 | sm, _, err := newSortedMapFromRandRecords(300) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if val, err := sm.BoundedKeys(time.Date(5783, 1, 1, 0, 0, 0, 0, time.UTC), time.Now()); err == nil { 53 | t.Fatalf("Values fall between or are equal to the given bounds when it should not have returned bounds: %+v", sm.idx[val[0]]) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | // Map returns a map containing keys mapped to values. 4 | // The returned map is valid until the next modification to the SortedMap structure. 5 | // The map can be used with ether the Keys or BoundedKeys methods to select a range of items 6 | // and iterate over them using a slice for-range loop, rather than a channel for-range loop. 7 | func (sm *SortedMap) Map() map[interface{}]interface{} { 8 | return sm.idx 9 | } 10 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | func TestMap(t *testing.T) { 6 | sm, _, err := newSortedMapFromRandRecords(300) 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | i := 0 11 | m := sm.Map() 12 | for _, val := range m { 13 | if val == nil { 14 | t.Fatal("Map key's value is nil.") 15 | } 16 | i++ 17 | } 18 | if i == 0 { 19 | t.Fatal("The returned map was empty.") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /replace.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "errors" 4 | 5 | func (sm *SortedMap) replace(key, val interface{}) { 6 | sm.delete(key) 7 | sm.insert(key, val) 8 | } 9 | 10 | // Replace uses the provided 'less than' function to insert sort. 11 | // Even if the key already exists, the value will be inserted. 12 | // Use Insert for the alternative functionality. 13 | func (sm *SortedMap) Replace(key, val interface{}) { 14 | sm.replace(key, val) 15 | } 16 | 17 | // BatchReplace adds all given records to the collection. 18 | // Even if a key already exists, the value will be inserted. 19 | // Use BatchInsert for the alternative functionality. 20 | func (sm *SortedMap) BatchReplace(recs []Record) { 21 | for _, rec := range recs { 22 | sm.replace(rec.Key, rec.Val) 23 | } 24 | } 25 | 26 | func (sm *SortedMap) batchReplaceMapInterfaceKeys(m map[interface{}]interface{}) { 27 | for key, val := range m { 28 | sm.replace(key, val) 29 | } 30 | } 31 | 32 | func (sm *SortedMap) batchReplaceMapStringKeys(m map[string]interface{}) { 33 | for key, val := range m { 34 | sm.replace(key, val) 35 | } 36 | } 37 | 38 | // BatchReplaceMap adds all map keys and values to the collection. 39 | // Even if a key already exists, the value will be inserted. 40 | // Use BatchInsertMap for the alternative functionality. 41 | func (sm *SortedMap) BatchReplaceMap(v interface{}) error { 42 | const unsupportedTypeErr = "Unsupported type." 43 | 44 | switch m := v.(type) { 45 | case map[interface{}]interface{}: 46 | sm.batchReplaceMapInterfaceKeys(m) 47 | return nil 48 | 49 | case map[string]interface{}: 50 | sm.batchReplaceMapStringKeys(m) 51 | return nil 52 | 53 | default: 54 | return errors.New(unsupportedTypeErr) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /replace_bench_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | func replace1ofNRecords(b *testing.B, n int) { 6 | sm, records, _, err := newRandSortedMapWithKeys(n) 7 | if err != nil { 8 | b.Fatal(err) 9 | } 10 | 11 | b.ResetTimer() 12 | for i := 0; i < b.N; i++ { 13 | sm.Replace(records[0].Key, records[0].Val) 14 | 15 | b.StopTimer() 16 | sm, records, _, err = newRandSortedMapWithKeys(n) 17 | if err != nil { 18 | b.Fatal(err) 19 | } 20 | b.StartTimer() 21 | } 22 | } 23 | 24 | func batchReplaceNofNRecords(b *testing.B, n int) { 25 | sm, records, _, err := newRandSortedMapWithKeys(n) 26 | if err != nil { 27 | b.Fatal(err) 28 | } 29 | 30 | b.ResetTimer() 31 | for i := 0; i < b.N; i++ { 32 | sm.BatchReplace(records) 33 | 34 | b.StopTimer() 35 | sm, records, _, err = newRandSortedMapWithKeys(n) 36 | if err != nil { 37 | b.Fatal(err) 38 | } 39 | b.StartTimer() 40 | } 41 | } 42 | 43 | func BenchmarkReplace1of1Records(b *testing.B) { 44 | replace1ofNRecords(b, 1) 45 | } 46 | 47 | func BenchmarkReplace1of10Records(b *testing.B) { 48 | replace1ofNRecords(b, 10) 49 | } 50 | 51 | func BenchmarkReplace1of100Records(b *testing.B) { 52 | replace1ofNRecords(b, 100) 53 | } 54 | 55 | func BenchmarkReplace1of1000Records(b *testing.B) { 56 | replace1ofNRecords(b, 1000) 57 | } 58 | 59 | func BenchmarkReplace1of10000Records(b *testing.B) { 60 | replace1ofNRecords(b, 10000) 61 | } 62 | 63 | func BenchmarkBatchReplace10of10Records(b *testing.B) { 64 | batchReplaceNofNRecords(b, 10) 65 | } 66 | 67 | func BenchmarkBatchReplace100of100Records(b *testing.B) { 68 | batchReplaceNofNRecords(b, 100) 69 | } 70 | 71 | func BenchmarkBatchReplace1000of1000Records(b *testing.B) { 72 | batchReplaceNofNRecords(b, 1000) 73 | } 74 | 75 | func BenchmarkBatchReplace10000of10000Records(b *testing.B) { 76 | batchReplaceNofNRecords(b, 10000) 77 | } 78 | -------------------------------------------------------------------------------- /replace_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/umpc/go-sortedmap/asc" 7 | ) 8 | 9 | func TestReplace(t *testing.T) { 10 | records := randRecords(3) 11 | sm := New(0, asc.Time) 12 | 13 | for i := 0; i < 5; i++ { 14 | for _, rec := range records { 15 | sm.Replace(rec.Key, rec.Val) 16 | } 17 | } 18 | 19 | iterCh, err := sm.IterCh() 20 | if err != nil { 21 | t.Fatal(err) 22 | } else { 23 | defer iterCh.Close() 24 | 25 | if err := verifyRecords(iterCh.Records(), false); err != nil { 26 | t.Fatal(err) 27 | } 28 | } 29 | } 30 | 31 | func TestBatchReplace(t *testing.T) { 32 | if _, _, err := newSortedMapFromRandRecords(1000); err != nil { 33 | t.Fatal(err) 34 | } 35 | } 36 | 37 | func TestBatchReplaceMapWithInterfaceKeys(t *testing.T) { 38 | sm, records, err := newSortedMapFromRandRecords(1000) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | i := 0 43 | m := make(map[interface{}]interface{}, len(records)) 44 | for _, rec := range records { 45 | m[rec.Key] = rec.Val 46 | i++ 47 | } 48 | if i == 0 { 49 | t.Fatal("Records were not copied to the map.") 50 | } 51 | if err := sm.BatchReplaceMap(m); err != nil { 52 | t.Fatal(err) 53 | } 54 | } 55 | 56 | func TestBatchReplaceMapWithStringKeys(t *testing.T) { 57 | sm, records, err := newSortedMapFromRandRecords(1000) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | i := 0 62 | m := make(map[string]interface{}, len(records)) 63 | for _, rec := range records { 64 | m[rec.Key.(string)] = rec.Val 65 | i++ 66 | } 67 | if i == 0 { 68 | t.Fatal("Records were not copied to the map.") 69 | } 70 | if err := sm.BatchReplaceMap(m); err != nil { 71 | t.Fatal(err) 72 | } 73 | } 74 | 75 | func TestBatchReplaceMapWithNilType(t *testing.T) { 76 | if err := New(0, asc.Time).BatchReplaceMap(nil); err == nil { 77 | t.Fatal("a nil type was allowed where a supported map type is required.") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /sliceutils.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | // insertInterface inserts the interface{} value v into slice s, at index i. 4 | // and then returns an updated reference. 5 | func insertInterface(s []interface{}, v interface{}, i int) []interface{} { 6 | s = append(s, nil) 7 | copy(s[i+1:], s[i:]) 8 | s[i] = v 9 | 10 | return s 11 | } 12 | 13 | // deleteInterface deletes an interface{} value from slice s, at index i, 14 | // and then returns an updated reference. 15 | func deleteInterface(s []interface{}, i int) []interface{} { 16 | copy(s[i:], s[i+1:]) 17 | s[len(s)-1] = nil 18 | 19 | return s[:len(s)-1] 20 | } 21 | -------------------------------------------------------------------------------- /sortedmap.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | // SortedMap contains a map, a slice, and references to one or more comparison functions. 4 | // SortedMap is not concurrency-safe, though it can be easily wrapped by a developer-defined type. 5 | type SortedMap struct { 6 | idx map[interface{}]interface{} 7 | sorted []interface{} 8 | lessFn ComparisonFunc 9 | } 10 | 11 | // Record defines a type used in batching and iterations, where keys and values are used together. 12 | type Record struct { 13 | Key, 14 | Val interface{} 15 | } 16 | 17 | // ComparisonFunc defines the type of the comparison function for the chosen value type. 18 | type ComparisonFunc func(i, j interface{}) bool 19 | 20 | func noOpComparisonFunc(_, _ interface{}) bool { 21 | return false 22 | } 23 | 24 | func setComparisonFunc(cmpFn ComparisonFunc) ComparisonFunc { 25 | if cmpFn == nil { 26 | return noOpComparisonFunc 27 | } 28 | return cmpFn 29 | } 30 | 31 | // New creates and initializes a new SortedMap structure and then returns a reference to it. 32 | // New SortedMaps are created with a backing map/slice of length/capacity n. 33 | func New(n int, cmpFn ComparisonFunc) *SortedMap { 34 | return &SortedMap{ 35 | idx: make(map[interface{}]interface{}, n), 36 | sorted: make([]interface{}, 0, n), 37 | lessFn: setComparisonFunc(cmpFn), 38 | } 39 | } 40 | 41 | // Len returns the number of items in the collection. 42 | func (sm *SortedMap) Len() int { 43 | return len(sm.sorted) 44 | } 45 | -------------------------------------------------------------------------------- /sortedmap_bench_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/umpc/go-sortedmap/asc" 7 | ) 8 | 9 | func BenchmarkNew(b *testing.B) { 10 | var sm *SortedMap 11 | 12 | b.ResetTimer() 13 | for i := 0; i < b.N; i++ { 14 | sm = New(0, asc.Time) 15 | } 16 | b.StopTimer() 17 | 18 | if sm == nil { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sortedmap_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import "testing" 4 | 5 | const ( 6 | notFoundErr = "key not found!" 7 | keyExistsErr = "a key already exists in the collection!" 8 | unsortedErr = "SortedMap is not sorted!" 9 | invalidDelete = "invalid delete status!" 10 | ) 11 | 12 | func TestNew(t *testing.T) { 13 | sm := New(0, nil) 14 | 15 | if sm.idx == nil { 16 | t.Fatal("TestNew failed: idx was nil!") 17 | } 18 | if sm.sorted == nil { 19 | t.Fatal("TestNew failed: sorted was nil!") 20 | } 21 | if sm.lessFn == nil { 22 | t.Fatal("TestNew failed: lessFn was nil!") 23 | } 24 | } 25 | 26 | func TestNoOpFuncs(t *testing.T) { 27 | if New(0, nil).lessFn(nil, nil) { 28 | t.Fatal("TestNoOpFuncs failed: lessFn returned true!") 29 | } 30 | } 31 | 32 | func TestLen(t *testing.T) { 33 | const count = 100 34 | sm, _, err := newSortedMapFromRandRecords(count) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if sm.Len() != count { 39 | t.Fatalf("TestLen failed: invalid SortedMap length. Expected: %v, Had: %v.", count, sm.Len()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /testing_utils_test.go: -------------------------------------------------------------------------------- 1 | package sortedmap 2 | 3 | import ( 4 | "fmt" 5 | mrand "math/rand" 6 | "time" 7 | 8 | "github.com/umpc/go-sortedmap/asc" 9 | ) 10 | 11 | func init() { 12 | mrand.Seed(time.Now().UnixNano()) 13 | } 14 | 15 | func randStr(n int) string { 16 | const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_+=~[]{}|:;<>,./?" 17 | result := make([]byte, n) 18 | 19 | for i := 0; i < n; i++ { 20 | result[i] = chars[mrand.Intn(len(chars))] 21 | } 22 | 23 | return string(result) 24 | } 25 | 26 | func randRecord() Record { 27 | year := mrand.Intn(2129) 28 | if year < 1 { 29 | year++ 30 | } 31 | mth := time.Month(mrand.Intn(12)) 32 | if mth < 1 { 33 | mth++ 34 | } 35 | day := mrand.Intn(28) 36 | if day < 1 { 37 | day++ 38 | } 39 | return Record{ 40 | Key: randStr(42), 41 | Val: time.Date(year, mth, day, 0, 0, 0, 0, time.UTC), 42 | } 43 | } 44 | 45 | func randRecords(n int) []Record { 46 | records := make([]Record, n) 47 | for i := range records { 48 | records[i] = randRecord() 49 | } 50 | return records 51 | } 52 | 53 | func verifyRecords(ch <-chan Record, reverse bool) error { 54 | previousRec := Record{} 55 | 56 | if ch != nil { 57 | for rec := range ch { 58 | if previousRec.Key != nil { 59 | switch reverse { 60 | case false: 61 | if previousRec.Val.(time.Time).After(rec.Val.(time.Time)) { 62 | return fmt.Errorf("%v %v", 63 | unsortedErr, 64 | fmt.Sprintf("prev: %+v, current: %+v.", previousRec, rec), 65 | ) 66 | } 67 | case true: 68 | if previousRec.Val.(time.Time).Before(rec.Val.(time.Time)) { 69 | return fmt.Errorf("%v %v", 70 | unsortedErr, 71 | fmt.Sprintf("prev: %+v, current: %+v.", previousRec, rec), 72 | ) 73 | } 74 | } 75 | } 76 | previousRec = rec 77 | } 78 | } else { 79 | return fmt.Errorf("Channel was nil.") 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func newSortedMapFromRandRecords(n int) (*SortedMap, []Record, error) { 86 | records := randRecords(n) 87 | sm := New(0, asc.Time) 88 | sm.BatchReplace(records) 89 | 90 | iterCh, err := sm.IterCh() 91 | if err != nil { 92 | return sm, records, err 93 | } 94 | defer iterCh.Close() 95 | 96 | return sm, records, verifyRecords(iterCh.Records(), false) 97 | } 98 | 99 | func newRandSortedMapWithKeys(n int) (*SortedMap, []Record, []interface{}, error) { 100 | sm, records, err := newSortedMapFromRandRecords(n) 101 | if err != nil { 102 | return nil, nil, nil, err 103 | } 104 | keys := make([]interface{}, n) 105 | for n, rec := range records { 106 | keys[n] = rec.Key 107 | } 108 | return sm, records, keys, err 109 | } 110 | --------------------------------------------------------------------------------