Skip to content

Commit 7a904e8

Browse files
committed
initial commit
0 parents  commit 7a904e8

File tree

13 files changed

+651
-0
lines changed

13 files changed

+651
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# SortedMap
2+
3+
Sorted Map is a small library that provides a value-sorted ```map[string]interface``` type and methods combined from Go 1 map and slice primitives.
4+
5+
This data structure allows for roughly constant-time reads and for efficiently iterating over only a subsection of the stored values. While this works for the project it was built for, because of the structure's reliance on a sorted slice of keys, worst-case delete operations are roughly ```O(n)```, where ```n``` is the number of items in the collection.
6+
7+
## Example Usage
8+
9+
```go
10+
package main
11+
12+
import (
13+
"fmt"
14+
"time"
15+
mrand "math/rand"
16+
17+
"github.com/umpc/sortedmap"
18+
)
19+
20+
func main() {
21+
mrand.Seed(time.Now().UTC().UnixNano())
22+
23+
// Example records:
24+
records := make([]*sortedmap.Record, 5)
25+
for i := range records {
26+
year := mrand.Intn(2018)
27+
if year < 1970 {
28+
year = 1970
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+
39+
hour := mrand.Intn(23)
40+
min := mrand.Intn(59)
41+
sec := mrand.Intn(59)
42+
43+
t := time.Date(year, mth, day, hour, min, sec, 0, time.UTC)
44+
records[i] = &sortedmap.Record{
45+
Key: t.Format(time.RFC3339),
46+
// Val: mrand.Intn(34334534561),
47+
Val: t,
48+
}
49+
}
50+
51+
// Only one type can be used at a time, though handling for multiple types is still shown here:
52+
sm := sortedmap.New(func(idx map[string]interface{}, sorted []string, i int, val interface{}) bool {
53+
switch val.(type) {
54+
// case int:
55+
// return val.(int) < idx[sorted[i]].(int)
56+
case time.Time:
57+
return val.(time.Time).Before(idx[sorted[i]].(time.Time))
58+
default:
59+
return false
60+
}
61+
})
62+
63+
// Insert:
64+
sm.BatchReplace(records...)
65+
66+
// Ordered iteration up until a given time:
67+
for rec := range sm.IterBefore(time.Now()) {
68+
fmt.Printf("%+v\n", rec)
69+
}
70+
}
71+
```
72+
73+
## License
74+
75+
The source code is available under the [MIT License](https://opensource.org/licenses/MIT).

defaults.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package sortedmap
2+
3+
import "time"
4+
5+
func defaultSortLessFn(idx map[string]interface{}, sorted []string, i int, val interface{}) bool {
6+
return idx[sorted[i]].(time.Time).Before(val.(time.Time))
7+
}
8+
9+
func setDefaults(lessFn SortLessFunc) SortLessFunc {
10+
if lessFn == nil {
11+
lessFn = defaultSortLessFn
12+
}
13+
return lessFn
14+
}

delete.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package sortedmap
2+
3+
func (sm *SortedMap) delete(key string) bool {
4+
if _, ok := sm.idx[key]; ok {
5+
delete(sm.idx, key)
6+
7+
smLen := len(sm.sorted)
8+
deleted := 0
9+
10+
for i := 0; i < smLen - deleted; i++ {
11+
if sm.sorted[i] == key {
12+
sm.sorted = deleteString(sm.sorted, i)
13+
deleted++
14+
}
15+
}
16+
17+
return true
18+
}
19+
return false
20+
}
21+
22+
// Delete removes a value from the collection, using the given key.
23+
// 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.
24+
func (sm *SortedMap) Delete(key string) bool {
25+
return sm.delete(key)
26+
}
27+
28+
// BatchDelete removes values from the collection, using the given keys, returning a slice of the results.
29+
func (sm *SortedMap) BatchDelete(keys ...string) []bool {
30+
results := make([]bool, len(keys))
31+
for i, key := range keys {
32+
results[i] = sm.delete(key)
33+
}
34+
return results
35+
}

get.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package sortedmap
2+
3+
// Get retrieves a value from the collection, using the given key.
4+
func (sm *SortedMap) Get(key string) (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 ...string) ([]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+
}

has.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package sortedmap
2+
3+
// Has checks if the key exists in the collection.
4+
func (sm *SortedMap) Has(key string) bool {
5+
_, ok := sm.idx[key]
6+
return ok
7+
}
8+
9+
// Has checks if the keys exist in the collection and returns a slice containing the results.
10+
func (sm *SortedMap) BatchHas(keys ...string) []bool {
11+
results := make([]bool, len(keys))
12+
for i, key := range keys {
13+
_, results[i] = sm.idx[key]
14+
}
15+
return results
16+
}

insert.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package sortedmap
2+
3+
func (sm *SortedMap) insert(key string, val interface{}) bool {
4+
if _, ok := sm.idx[key]; !ok {
5+
sm.idx[key] = val
6+
sm.sorted = sm.insertSort(key, val)
7+
return true
8+
}
9+
return false
10+
}
11+
12+
// 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.
13+
// If the key already exists, the value will not be inserted. Use Replace for the alternative functionality.
14+
func (sm *SortedMap) Insert(key string, val interface{}) bool {
15+
return sm.insert(key, val)
16+
}
17+
18+
// BatchInsert adds all given records to the collection and returns a slice containing each record's insert status.
19+
// If a key already exists, the value will not be inserted. Use BatchReplace for the alternative functionality.
20+
func (sm *SortedMap) BatchInsert(recs ...*Record) []bool {
21+
results := make([]bool, len(recs))
22+
for i, rec := range recs {
23+
results[i] = sm.insert(rec.Key, rec.Val)
24+
}
25+
return results
26+
}

insertsort.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package sortedmap
2+
3+
import "sort"
4+
5+
func (sm *SortedMap) insertSort(key string, val interface{}) []string {
6+
smLen := len(sm.sorted)
7+
if smLen == 0 {
8+
return []string{key}
9+
}
10+
i := sort.Search(smLen, func(i int) bool {
11+
return sm.lessFn(sm.idx, sm.sorted, i, val)
12+
})
13+
return insertString(sm.sorted, i, key)
14+
}

iter.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package sortedmap
2+
3+
import "sort"
4+
5+
func (sm *SortedMap) iter(bufSize int) <-chan Record {
6+
ch := make(chan Record, bufSize)
7+
8+
go func(ch chan Record) {
9+
var (
10+
key string
11+
smLen = len(sm.sorted)
12+
)
13+
for i := 0; i < smLen; i++ {
14+
key = sm.sorted[i]
15+
ch <- Record{
16+
Key: key,
17+
Val: sm.idx[key],
18+
}
19+
}
20+
close(ch)
21+
}(ch)
22+
23+
return ch
24+
}
25+
26+
func (sm *SortedMap) iterUntil(bufSize int, val interface{}) <-chan Record {
27+
ch := make(chan Record, bufSize)
28+
29+
go func(ch chan Record) {
30+
var (
31+
key string
32+
smLen = len(sm.sorted)
33+
)
34+
for i := 0; i < smLen; i++ {
35+
if sm.lessFn(sm.idx, sm.sorted, i, val) {
36+
break
37+
}
38+
key = sm.sorted[i]
39+
ch <- Record{
40+
Key: key,
41+
Val: sm.idx[key],
42+
}
43+
}
44+
close(ch)
45+
}(ch)
46+
47+
return ch
48+
}
49+
50+
func (sm *SortedMap) iterAfter(bufSize int, val interface{}) <-chan Record {
51+
ch := make(chan Record, bufSize)
52+
53+
go func(ch chan Record) {
54+
var (
55+
key string
56+
smLen = len(sm.sorted)
57+
)
58+
i := sort.Search(smLen, func(i int) bool {
59+
return sm.lessFn(sm.idx, sm.sorted, i, val)
60+
})
61+
for; i < smLen; i++ {
62+
key = sm.sorted[i]
63+
ch <- Record{
64+
Key: key,
65+
Val: sm.idx[key],
66+
}
67+
}
68+
close(ch)
69+
}(ch)
70+
71+
return ch
72+
}
73+
74+
// Iter returns an unbuffered channel that sorted records can be read from and processed.
75+
func (sm *SortedMap) Iter() <-chan Record {
76+
return sm.iter(0)
77+
}
78+
79+
// IterUntil returns an unbuffered channel that sorted records can be read from and processed.
80+
// IterUntil starts at the lowest value in the collection and sends all values until reaching the given value.
81+
func (sm *SortedMap) IterUntil(val interface{}) <-chan Record {
82+
return sm.iterUntil(0, val)
83+
}
84+
85+
// IterAfter returns an unbuffered channel that sorted records can be read from and processed.
86+
// IterAfter starts at the given value and sends all values until reaching the end of the collection.
87+
func (sm *SortedMap) IterAfter(val interface{}) <-chan Record {
88+
return sm.iterAfter(0, val)
89+
}
90+
91+
// BufferedIter returns a buffered channel that sorted records can be read from and processed.
92+
func (sm *SortedMap) BufferedIter(bufSize int) <-chan Record {
93+
return sm.iter(bufSize)
94+
}
95+
96+
// BufferedIterUntil returns a buffered channel that sorted records can be read from and processed.
97+
// BufferedIterUntil starts at the lowest value in the collection and sends all values until reaching the given value.
98+
func (sm *SortedMap) BufferedIterUntil(bufSize int, val interface{}) <-chan Record {
99+
return sm.iterUntil(bufSize, val)
100+
}
101+
102+
// BufferedIterAfter returns a buffered channel that sorted records can be read from and processed.
103+
// BufferedIterAfter starts at the given value and sends all values until reaching the end of the collection.
104+
func (sm *SortedMap) BufferedIterAfter(bufSize int, val interface{}) <-chan Record {
105+
return sm.iterAfter(bufSize, val)
106+
}

replace.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package sortedmap
2+
3+
func (sm *SortedMap) replace(key string, val interface{}) {
4+
if _, ok := sm.idx[key]; ok {
5+
sm.delete(key)
6+
}
7+
sm.idx[key] = val
8+
sm.sorted = sm.insertSort(key, val)
9+
}
10+
11+
// Replace uses the provided 'less than' function to insert sort.
12+
// Even if the key already exists, the value will be inserted. Use Insert for the alternative functionality.
13+
func (sm *SortedMap) Replace(key string, 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. Use BatchInsert for the alternative functionality.
19+
func (sm *SortedMap) BatchReplace(recs ...*Record) {
20+
for _, rec := range recs {
21+
sm.replace(rec.Key, rec.Val)
22+
}
23+
}
24+
25+
// ChReplace reads records from a channel and adds all given records to the collection.
26+
// Even if a key already exists, the value will be inserted. If a clearly efficient alternative to this function is proposed, it will likely be accepted and merged.
27+
func (sm *SortedMap) ChReplace(ch <-chan *Record) {
28+
for rec := range ch {
29+
sm.replace(rec.Key, rec.Val)
30+
}
31+
}

0 commit comments

Comments
 (0)