├── testdata └── foo │ ├── a.txt │ ├── c.txt │ └── bar │ └── b.txt ├── go.mod ├── list_test.go ├── .gitignore ├── .travis.yml ├── fibonacci.go ├── filesystem_examples_test.go ├── .github └── workflows │ └── build_and_test.yml ├── LICENSE ├── lru_example_test.go ├── query_test.go ├── lru_cache_test.go ├── doc.go ├── stack_test.go ├── filesystem.go ├── dictionary_examples_test.go ├── stack.go ├── queue.go ├── linkedlist_examples_test.go ├── lru_cache.go ├── filesystem_test.go ├── list.go ├── dictionary_test.go ├── queue_test.go ├── dictionary.go ├── README.md ├── linkedlist_test.go ├── query_examples_test.go ├── linkedlist.go └── query.go /testdata/foo/a.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/foo/c.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/foo/bar/b.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/marstr/collection/v2 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /list_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import "fmt" 4 | 5 | func ExampleList_AddAt() { 6 | subject := NewList(0, 1, 4, 5, 6) 7 | subject.AddAt(2, 2, 3) 8 | fmt.Println(subject) 9 | // Output: [0 1 2 3 4 5 6] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11 5 | - 1.x 6 | 7 | env: 8 | - GO111MODULE=on 9 | 10 | matrix: 11 | fast_finish: true 12 | 13 | install: 14 | - go get -u golang.org/x/lint/golint 15 | 16 | script: 17 | - go test -v -cover 18 | - test -z "$(golint | tee /dev/stderr)" 19 | - test -z "$(go vet | tee /dev/stderr | grep error)" 20 | - test -z "$(gofmt -l *.go | tee /dev/stderr)" -------------------------------------------------------------------------------- /fibonacci.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import "context" 4 | 5 | type fibonacciGenerator struct{} 6 | 7 | // Fibonacci is an Enumerable which will dynamically generate the fibonacci sequence. 8 | var Fibonacci Enumerable[uint] = fibonacciGenerator{} 9 | 10 | func (gen fibonacciGenerator) Enumerate(ctx context.Context) Enumerator[uint] { 11 | retval := make(chan uint) 12 | 13 | go func() { 14 | defer close(retval) 15 | var a, b uint = 0, 1 16 | 17 | for { 18 | select { 19 | case retval <- a: 20 | a, b = b, a+b 21 | case <-ctx.Done(): 22 | return 23 | } 24 | } 25 | }() 26 | 27 | return retval 28 | } 29 | -------------------------------------------------------------------------------- /filesystem_examples_test.go: -------------------------------------------------------------------------------- 1 | package collection_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/marstr/collection/v2" 7 | "path" 8 | ) 9 | 10 | func ExampleDirectory_Enumerate() { 11 | traverser := collection.Directory{ 12 | Location: ".", 13 | Options: collection.DirectoryOptionsExcludeDirectories, 14 | } 15 | 16 | ctx, cancel := context.WithCancel(context.Background()) 17 | defer cancel() 18 | 19 | fileNames := collection.Select[string](traverser, path.Base) 20 | 21 | filesOfInterest := collection.Where(fileNames, func(subject string) bool { 22 | return subject == "filesystem_examples_test.go" 23 | }) 24 | 25 | for entry := range filesOfInterest.Enumerate(ctx) { 26 | fmt.Println(entry) 27 | } 28 | 29 | // Output: filesystem_examples_test.go 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main, v1 ] 7 | pull_request: 8 | branches: [ main, v1 ] 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | stable: false 20 | go-version: ^1.18.0-beta1 21 | id: go 22 | 23 | - name: Check out code into the Go module directory 24 | uses: actions/checkout@v2 25 | 26 | - name: Get dependencies 27 | run: | 28 | go get -v -t -d ./... 29 | if [ -f Gopkg.toml ]; then 30 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 31 | dep ensure 32 | fi 33 | 34 | - name: Build 35 | run: go build -v . 36 | 37 | - name: Test 38 | run: go test -v . 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Martin Strobel 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lru_example_test.go: -------------------------------------------------------------------------------- 1 | package collection_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/marstr/collection/v2" 7 | ) 8 | 9 | func ExampleLRUCache() { 10 | subject := collection.NewLRUCache[int, string](3) 11 | subject.Put(1, "one") 12 | subject.Put(2, "two") 13 | subject.Put(3, "three") 14 | subject.Put(4, "four") 15 | fmt.Println(subject.Get(1)) 16 | fmt.Println(subject.Get(4)) 17 | // Output: 18 | // false 19 | // four true 20 | } 21 | 22 | func ExampleLRUCache_Enumerate() { 23 | subject := collection.NewLRUCache[int, string](3) 24 | subject.Put(1, "one") 25 | subject.Put(2, "two") 26 | subject.Put(3, "three") 27 | subject.Put(4, "four") 28 | 29 | for key := range subject.Enumerate(context.Background()) { 30 | fmt.Println(key) 31 | } 32 | 33 | // Output: 34 | // four 35 | // three 36 | // two 37 | } 38 | 39 | func ExampleLRUCache_EnumerateKeys() { 40 | subject := collection.NewLRUCache[int, string](3) 41 | subject.Put(1, "one") 42 | subject.Put(2, "two") 43 | subject.Put(3, "three") 44 | subject.Put(4, "four") 45 | 46 | for key := range subject.EnumerateKeys(context.Background()) { 47 | fmt.Println(key) 48 | } 49 | 50 | // Output: 51 | // 4 52 | // 3 53 | // 2 54 | } 55 | -------------------------------------------------------------------------------- /query_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func Test_Empty(t *testing.T) { 10 | if Any(Empty[int]()) { 11 | t.Log("empty should not have any elements") 12 | t.Fail() 13 | } 14 | 15 | if CountAll(Empty[int]()) != 0 { 16 | t.Log("empty should have counted to zero elements") 17 | t.Fail() 18 | } 19 | 20 | alwaysTrue := func(x int) bool { 21 | return true 22 | } 23 | 24 | if Count(Empty[int](), alwaysTrue) != 0 { 25 | t.Log("empty should have counted to zero even when discriminating") 26 | t.Fail() 27 | } 28 | } 29 | 30 | func BenchmarkEnumerator_Sum(b *testing.B) { 31 | var nums EnumerableSlice[int] = getInitializedSequentialArray[int]() 32 | ctx, cancel := context.WithCancel(context.Background()) 33 | defer cancel() 34 | 35 | b.ResetTimer() 36 | for i := 0; i < b.N; i++ { 37 | slowNums := Select[int, int](nums, sleepIdentity[int]) 38 | for range slowNums.Enumerate(ctx) { 39 | // Intentionally Left Blank 40 | } 41 | } 42 | } 43 | 44 | func sleepIdentity[T any](val T) T { 45 | time.Sleep(2 * time.Millisecond) 46 | return val 47 | } 48 | 49 | func getInitializedSequentialArray[T ~int]() []T { 50 | rawNums := make([]T, 1000) 51 | for i := range rawNums { 52 | rawNums[i] = T(i + 1) 53 | } 54 | return rawNums 55 | } 56 | -------------------------------------------------------------------------------- /lru_cache_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import "testing" 4 | 5 | func TestLRUCache_Put_replace(t *testing.T) { 6 | const key = 1 7 | const firstPut = "first" 8 | const secondPut = "second" 9 | 10 | subject := NewLRUCache[int, string](10) 11 | subject.Put(key, firstPut) 12 | subject.Put(key, secondPut) 13 | 14 | want := secondPut 15 | got, ok := subject.Get(key) 16 | if !ok { 17 | t.Logf("key should have been present") 18 | t.Fail() 19 | } 20 | 21 | if got != want { 22 | t.Logf("Unexpected result\n\tgot: %s\n\twant: %s", got, want) 23 | t.Fail() 24 | } 25 | } 26 | 27 | func TestLRUCache_Remove_empty(t *testing.T) { 28 | subject := NewLRUCache[int, int](10) 29 | got := subject.Remove(7) 30 | if got != false { 31 | t.Fail() 32 | } 33 | } 34 | 35 | func TestLRUCache_Remove_present(t *testing.T) { 36 | const key = 10 37 | subject := NewLRUCache[int, string](6) 38 | subject.Put(key, "ten") 39 | ok := subject.Remove(key) 40 | if !ok { 41 | t.Fail() 42 | } 43 | 44 | _, ok = subject.Get(key) 45 | if ok { 46 | t.Fail() 47 | } 48 | } 49 | 50 | func TestLRUCache_Remove_notPresent(t *testing.T) { 51 | const key1 = 10 52 | const key2 = key1 + 1 53 | subject := NewLRUCache[int, string](6) 54 | subject.Put(key2, "eleven") 55 | ok := subject.Remove(key1) 56 | if ok { 57 | t.Fail() 58 | } 59 | 60 | _, ok = subject.Get(key2) 61 | if !ok { 62 | t.Fail() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package collection seeks to provide an expressive and readable way of working with basic data structures in Go. 2 | // 3 | // As a former .NET developer, I deeply missed writing programs in the style of Linq. Doing so enables concurrent/ 4 | // parallel reactive programs to be written in a snap. Go's functional nature enables us to have a very similar, if more 5 | // verbose, experience. 6 | // 7 | // Take for example the scenario of printing the number of Go source files in a directory. Using this package, 8 | // this takes only a few lines: 9 | // 10 | // myDir := collection.Directory{ 11 | // Location: "./", 12 | // } 13 | // 14 | // results := myDir.Enumerate(context.Background()).Where(func(x interface{}) bool { 15 | // return strings.HasSuffix(x.(string), ".go") 16 | // }) 17 | // 18 | // fmt.Println(results.CountAll()) 19 | // 20 | // A directory is a collection of filesystem entries, so we're able to iterate through them using the "Enumerate" 21 | // function. From there, we filter on only file names that end with ".go". Finally, we print the number of entries that 22 | // were encountered. 23 | // 24 | // This is a trivial example, but imagine building more elaborate pipelines. Maybe take advantage of the 25 | // `SelectParallel` function which allows multiple goroutines to process a single transform at once, with their results 26 | // being funnelled into the next phase of the pipeline. Suddenly, injecting new steps can be transparent. 27 | package collection 28 | -------------------------------------------------------------------------------- /stack_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestStack_NewStack_FromEmpty(t *testing.T) { 9 | subject := NewStack[string]() 10 | subject.Push("alfa") 11 | subject.Push("bravo") 12 | subject.Push("charlie") 13 | 14 | if result, ok := subject.Pop(); result != "charlie" || ok != true { 15 | t.Logf("got: %s %v\nwant: %s %v", result, ok, "charlie", true) 16 | t.Fail() 17 | } 18 | if result, ok := subject.Pop(); result != "bravo" || ok != true { 19 | t.Logf("got: %s %v\nwant: %s %v", result, ok, "bravo", true) 20 | t.Fail() 21 | } 22 | if result, ok := subject.Pop(); result != "alfa" || ok != true { 23 | t.Logf("got: %s %v\nwant: %s %v", result, ok, "alfa", true) 24 | t.Fail() 25 | } 26 | if !subject.IsEmpty() { 27 | t.Log("subject should have been empty.") 28 | t.Fail() 29 | } 30 | } 31 | 32 | func ExampleNewStack() { 33 | subject := NewStack(1, 2, 3) 34 | for !subject.IsEmpty() { 35 | val, _ := subject.Pop() 36 | fmt.Println(val) 37 | } 38 | // Output: 39 | // 3 40 | // 2 41 | // 1 42 | } 43 | 44 | func TestStack_Push_NonConstructor(t *testing.T) { 45 | subject := &Stack[int]{} 46 | 47 | sizeAssertion := func(want uint) { 48 | if got := subject.Size(); got != want { 49 | t.Logf("got: %d\nwant:%d\n", got, want) 50 | t.Fail() 51 | } 52 | } 53 | 54 | sizeAssertion(0) 55 | subject.Push(1) 56 | sizeAssertion(1) 57 | subject.Push(2) 58 | sizeAssertion(2) 59 | 60 | if result, ok := subject.Pop(); !ok { 61 | t.Logf("Pop is not ok") 62 | t.Fail() 63 | } else if result != 2 { 64 | t.Logf("got: %d\nwant: %d", result, 2) 65 | t.Fail() 66 | } 67 | } 68 | 69 | func TestStack_Pop_NonConstructorEmpty(t *testing.T) { 70 | subject := &Stack[string]{} 71 | 72 | if result, ok := subject.Pop(); ok { 73 | t.Logf("Pop should not have been okay") 74 | t.Fail() 75 | } else if result != "" { 76 | t.Logf("got: %v\nwant: %v", result, nil) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /filesystem.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | // DirectoryOptions is a means of configuring a `Directory` instance to including various children in its enumeration without 10 | // supplying a `Where` clause later. 11 | type DirectoryOptions uint 12 | 13 | // These constants define all of the supported options for configuring a `Directory` 14 | const ( 15 | DirectoryOptionsExcludeFiles = 1 << iota 16 | DirectoryOptionsExcludeDirectories 17 | DirectoryOptionsRecursive 18 | ) 19 | 20 | // Directory treats a filesystem path as a collection of filesystem entries, specifically a collection of directories and files. 21 | type Directory struct { 22 | Location string 23 | Options DirectoryOptions 24 | } 25 | 26 | func (d Directory) applyOptions(loc string, info os.FileInfo) bool { 27 | if info.IsDir() && (d.Options&DirectoryOptionsExcludeDirectories) != 0 { 28 | return false 29 | } 30 | 31 | if !info.IsDir() && d.Options&DirectoryOptionsExcludeFiles != 0 { 32 | return false 33 | } 34 | 35 | return true 36 | } 37 | 38 | // Enumerate lists the items in a `Directory` 39 | func (d Directory) Enumerate(ctx context.Context) Enumerator[string] { 40 | results := make(chan string) 41 | 42 | go func() { 43 | defer close(results) 44 | 45 | filepath.Walk(d.Location, func(currentLocation string, info os.FileInfo, openErr error) (err error) { 46 | if openErr != nil { 47 | err = openErr 48 | return 49 | } 50 | 51 | if d.Location == currentLocation { 52 | return 53 | } 54 | 55 | if info.IsDir() && d.Options&DirectoryOptionsRecursive == 0 { 56 | err = filepath.SkipDir 57 | } 58 | 59 | if d.applyOptions(currentLocation, info) { 60 | select { 61 | case results <- currentLocation: 62 | // Intentionally Left Blank 63 | case <-ctx.Done(): 64 | err = ctx.Err() 65 | } 66 | } 67 | 68 | return 69 | }) 70 | }() 71 | 72 | return results 73 | } 74 | -------------------------------------------------------------------------------- /dictionary_examples_test.go: -------------------------------------------------------------------------------- 1 | package collection_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/marstr/collection/v2" 9 | ) 10 | 11 | func ExampleDictionary_Add() { 12 | subject := &collection.Dictionary{} 13 | 14 | const example = "hello" 15 | fmt.Println(subject.Contains(example)) 16 | fmt.Println(subject.Size()) 17 | subject.Add(example) 18 | fmt.Println(subject.Contains(example)) 19 | fmt.Println(subject.Size()) 20 | 21 | // Output: 22 | // false 23 | // 0 24 | // true 25 | // 1 26 | } 27 | 28 | func ExampleDictionary_Clear() { 29 | subject := &collection.Dictionary{} 30 | 31 | subject.Add("hello") 32 | subject.Add("world") 33 | 34 | fmt.Println(subject.Size()) 35 | fmt.Println(collection.CountAll[string](subject)) 36 | 37 | subject.Clear() 38 | 39 | fmt.Println(subject.Size()) 40 | fmt.Println(collection.Any[string](subject)) 41 | 42 | // Output: 43 | // 2 44 | // 2 45 | // 0 46 | // false 47 | } 48 | 49 | func ExampleDictionary_Enumerate() { 50 | subject := &collection.Dictionary{} 51 | subject.Add("world") 52 | subject.Add("hello") 53 | 54 | upperCase := collection.Select[string](subject, strings.ToUpper) 55 | 56 | for word := range subject.Enumerate(context.Background()) { 57 | fmt.Println(word) 58 | } 59 | 60 | for word := range upperCase.Enumerate(context.Background()) { 61 | fmt.Println(word) 62 | } 63 | 64 | // Output: 65 | // hello 66 | // world 67 | // HELLO 68 | // WORLD 69 | } 70 | 71 | func ExampleDictionary_Remove() { 72 | const world = "world" 73 | subject := &collection.Dictionary{} 74 | subject.Add("hello") 75 | subject.Add(world) 76 | 77 | fmt.Println(subject.Size()) 78 | fmt.Println(collection.CountAll[string](subject)) 79 | 80 | subject.Remove(world) 81 | 82 | fmt.Println(subject.Size()) 83 | fmt.Println(collection.CountAll[string](subject)) 84 | fmt.Println(collection.Any[string](subject)) 85 | 86 | // Output: 87 | // 2 88 | // 2 89 | // 1 90 | // 1 91 | // true 92 | } 93 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | // Stack implements a basic FILO structure. 9 | type Stack[T any] struct { 10 | underlyer *LinkedList[T] 11 | key sync.RWMutex 12 | } 13 | 14 | // NewStack instantiates a new FILO structure. 15 | func NewStack[T any](entries ...T) *Stack[T] { 16 | retval := &Stack[T]{} 17 | retval.underlyer = NewLinkedList[T]() 18 | 19 | for _, entry := range entries { 20 | retval.Push(entry) 21 | } 22 | return retval 23 | } 24 | 25 | // Enumerate peeks at each element in the stack without mutating it. 26 | func (stack *Stack[T]) Enumerate(ctx context.Context) Enumerator[T] { 27 | stack.key.RLock() 28 | defer stack.key.RUnlock() 29 | 30 | return stack.underlyer.Enumerate(ctx) 31 | } 32 | 33 | // IsEmpty tests the Stack to determine if it is populate or not. 34 | func (stack *Stack[T]) IsEmpty() bool { 35 | stack.key.RLock() 36 | defer stack.key.RUnlock() 37 | return stack.underlyer == nil || stack.underlyer.IsEmpty() 38 | } 39 | 40 | // Push adds an entry to the top of the Stack. 41 | func (stack *Stack[T]) Push(entry T) { 42 | stack.key.Lock() 43 | defer stack.key.Unlock() 44 | 45 | if nil == stack.underlyer { 46 | stack.underlyer = NewLinkedList[T]() 47 | } 48 | stack.underlyer.AddFront(entry) 49 | } 50 | 51 | // Pop returns the entry at the top of the Stack then removes it. 52 | func (stack *Stack[T]) Pop() (T, bool) { 53 | stack.key.Lock() 54 | defer stack.key.Unlock() 55 | 56 | if nil == stack.underlyer { 57 | return *new(T), false 58 | } 59 | return stack.underlyer.RemoveFront() 60 | } 61 | 62 | // Peek returns the entry at the top of the Stack without removing it. 63 | func (stack *Stack[T]) Peek() (T, bool) { 64 | stack.key.RLock() 65 | defer stack.key.RUnlock() 66 | return stack.underlyer.PeekFront() 67 | } 68 | 69 | // Size returns the number of entries populating the Stack. 70 | func (stack *Stack[T]) Size() uint { 71 | stack.key.RLock() 72 | defer stack.key.RUnlock() 73 | if stack.underlyer == nil { 74 | return 0 75 | } 76 | return stack.underlyer.Length() 77 | } 78 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | // Queue implements a basic FIFO structure. 9 | type Queue[T any] struct { 10 | underlyer *LinkedList[T] 11 | key sync.RWMutex 12 | } 13 | 14 | // NewQueue instantiates a new FIFO structure. 15 | func NewQueue[T any](entries ...T) *Queue[T] { 16 | retval := &Queue[T]{ 17 | underlyer: NewLinkedList[T](entries...), 18 | } 19 | return retval 20 | } 21 | 22 | // Add places an item at the back of the Queue. 23 | func (q *Queue[T]) Add(entry T) { 24 | q.key.Lock() 25 | defer q.key.Unlock() 26 | if nil == q.underlyer { 27 | q.underlyer = NewLinkedList[T]() 28 | } 29 | q.underlyer.AddBack(entry) 30 | } 31 | 32 | // Enumerate peeks at each element of this queue without mutating it. 33 | func (q *Queue[T]) Enumerate(ctx context.Context) Enumerator[T] { 34 | q.key.RLock() 35 | defer q.key.RUnlock() 36 | return q.underlyer.Enumerate(ctx) 37 | } 38 | 39 | // IsEmpty tests the Queue to determine if it is populate or not. 40 | func (q *Queue[T]) IsEmpty() bool { 41 | q.key.RLock() 42 | defer q.key.RUnlock() 43 | return q.underlyer == nil || q.underlyer.IsEmpty() 44 | } 45 | 46 | // Length returns the number of items in the Queue. 47 | func (q *Queue[T]) Length() uint { 48 | q.key.RLock() 49 | defer q.key.RUnlock() 50 | if nil == q.underlyer { 51 | return 0 52 | } 53 | return q.underlyer.length 54 | } 55 | 56 | // Next removes and returns the next item in the Queue. 57 | func (q *Queue[T]) Next() (T, bool) { 58 | q.key.Lock() 59 | defer q.key.Unlock() 60 | if q.underlyer == nil { 61 | return *new(T), false 62 | } 63 | return q.underlyer.RemoveFront() 64 | } 65 | 66 | // Peek returns the next item in the Queue without removing it. 67 | func (q *Queue[T]) Peek() (T, bool) { 68 | q.key.RLock() 69 | defer q.key.RUnlock() 70 | if q.underlyer == nil { 71 | return *new(T), false 72 | } 73 | return q.underlyer.PeekFront() 74 | } 75 | 76 | // ToSlice converts a Queue into a slice. 77 | func (q *Queue[T]) ToSlice() []T { 78 | q.key.RLock() 79 | defer q.key.RUnlock() 80 | 81 | if q.underlyer == nil { 82 | return []T{} 83 | } 84 | return q.underlyer.ToSlice() 85 | } 86 | -------------------------------------------------------------------------------- /linkedlist_examples_test.go: -------------------------------------------------------------------------------- 1 | package collection_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/marstr/collection/v2" 8 | ) 9 | 10 | func ExampleLinkedList_AddFront() { 11 | subject := collection.NewLinkedList(2, 3) 12 | subject.AddFront(1) 13 | result, _ := subject.PeekFront() 14 | fmt.Println(result) 15 | // Output: 1 16 | } 17 | 18 | func ExampleLinkedList_AddBack() { 19 | subject := collection.NewLinkedList(2, 3, 5) 20 | subject.AddBack(8) 21 | result, _ := subject.PeekBack() 22 | fmt.Println(result) 23 | fmt.Println(subject.Length()) 24 | // Output: 25 | // 8 26 | // 4 27 | } 28 | 29 | func ExampleLinkedList_Enumerate() { 30 | subject := collection.NewLinkedList(2, 3, 5, 8) 31 | results := collection.Select[int](subject, func(a int) int { 32 | return -1 * a 33 | }) 34 | 35 | for entry := range results.Enumerate(context.Background()) { 36 | fmt.Println(entry) 37 | } 38 | // Output: 39 | // -2 40 | // -3 41 | // -5 42 | // -8 43 | } 44 | 45 | func ExampleLinkedList_Get() { 46 | subject := collection.NewLinkedList(2, 3, 5, 8) 47 | val, _ := subject.Get(2) 48 | fmt.Println(val) 49 | // Output: 5 50 | } 51 | 52 | func ExampleNewLinkedList() { 53 | subject1 := collection.NewLinkedList('a', 'b', 'c', 'd', 'e') 54 | fmt.Println(subject1.Length()) 55 | 56 | slice := []interface{}{1, 2, 3, 4, 5, 6} 57 | subject2 := collection.NewLinkedList(slice...) 58 | fmt.Println(subject2.Length()) 59 | // Output: 60 | // 5 61 | // 6 62 | } 63 | 64 | func ExampleLinkedList_Sort() { 65 | // Sorti sorts into ascending order, this example demonstrates sorting 66 | // into descending order. 67 | subject := collection.NewLinkedList(2, 4, 3, 5, 7, 7) 68 | subject.Sort(func(a, b int) (int, error) { 69 | return b - a, nil 70 | }) 71 | fmt.Println(subject) 72 | // Output: [7 7 5 4 3 2] 73 | } 74 | 75 | func ExampleLinkedList_String() { 76 | subject1 := collection.NewLinkedList[int]() 77 | for i := 0; i < 20; i++ { 78 | subject1.AddBack(i) 79 | } 80 | fmt.Println(subject1) 81 | 82 | subject2 := collection.NewLinkedList[int](1, 2, 3) 83 | fmt.Println(subject2) 84 | // Output: 85 | // [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...] 86 | // [1 2 3] 87 | } 88 | 89 | func ExampleLinkedList_Swap() { 90 | subject := collection.NewLinkedList(2, 3, 5, 8, 13) 91 | subject.Swap(1, 3) 92 | fmt.Println(subject) 93 | // Output: [2 8 5 3 13] 94 | } 95 | -------------------------------------------------------------------------------- /lru_cache.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | // LRUCache hosts up to a given number of items. When more are presented, the least recently used item 9 | // is evicted from the cache. 10 | type LRUCache[K comparable, V any] struct { 11 | capacity uint 12 | entries map[K]*lruEntry[K, V] 13 | touched *LinkedList[*lruEntry[K, V]] 14 | key sync.RWMutex 15 | } 16 | 17 | type lruEntry[K any, V any] struct { 18 | Node *llNode[*lruEntry[K, V]] 19 | Key K 20 | Value V 21 | } 22 | 23 | // NewLRUCache creates an empty cache, which will accommodate the given number of items. 24 | func NewLRUCache[K comparable, V any](capacity uint) *LRUCache[K, V] { 25 | return &LRUCache[K, V]{ 26 | capacity: capacity, 27 | entries: make(map[K]*lruEntry[K, V], capacity+1), 28 | touched: NewLinkedList[*lruEntry[K, V]](), 29 | } 30 | } 31 | 32 | // Put adds a value to the cache. The added value may be expelled without warning. 33 | func (lru *LRUCache[K, V]) Put(key K, value V) { 34 | lru.key.Lock() 35 | defer lru.key.Unlock() 36 | 37 | entry, ok := lru.entries[key] 38 | if ok { 39 | lru.touched.removeNode(entry.Node) 40 | } else { 41 | entry = &lruEntry[K, V]{ 42 | Node: &llNode[*lruEntry[K, V]]{}, 43 | Key: key, 44 | } 45 | } 46 | 47 | entry.Node.payload = entry 48 | entry.Value = value 49 | lru.touched.addNodeFront(entry.Node) 50 | lru.entries[key] = entry 51 | 52 | if lru.touched.Length() > lru.capacity { 53 | removed, ok := lru.touched.RemoveBack() 54 | if ok { 55 | delete(lru.entries, removed.Key) 56 | } 57 | } 58 | } 59 | 60 | // Get retrieves a cached value, if it is still present. 61 | func (lru *LRUCache[K, V]) Get(key K) (V, bool) { 62 | lru.key.RLock() 63 | defer lru.key.RUnlock() 64 | 65 | entry, ok := lru.entries[key] 66 | if !ok { 67 | return *new(V), false 68 | } 69 | 70 | lru.touched.removeNode(entry.Node) 71 | lru.touched.addNodeFront(entry.Node) 72 | return entry.Node.payload.Value, true 73 | } 74 | 75 | // Remove explicitly takes an item out of the cache. 76 | func (lru *LRUCache[K, V]) Remove(key K) bool { 77 | lru.key.RLock() 78 | defer lru.key.RUnlock() 79 | 80 | entry, ok := lru.entries[key] 81 | if !ok { 82 | return false 83 | } 84 | 85 | lru.touched.removeNode(entry.Node) 86 | delete(lru.entries, key) 87 | return true 88 | } 89 | 90 | // Enumerate lists each value in the cache. 91 | func (lru *LRUCache[K, V]) Enumerate(ctx context.Context) Enumerator[V] { 92 | retval := make(chan V) 93 | 94 | nested := lru.touched.Enumerate(ctx) 95 | 96 | go func() { 97 | lru.key.RLock() 98 | defer lru.key.RUnlock() 99 | defer close(retval) 100 | 101 | for entry := range nested { 102 | select { 103 | case retval <- entry.Value: 104 | // Intentionally Left Blank 105 | case <-ctx.Done(): 106 | return 107 | } 108 | } 109 | }() 110 | 111 | return retval 112 | } 113 | 114 | // EnumerateKeys lists each key in the cache. 115 | func (lru *LRUCache[K, V]) EnumerateKeys(ctx context.Context) Enumerator[K] { 116 | retval := make(chan K) 117 | 118 | nested := lru.touched.Enumerate(ctx) 119 | 120 | go func() { 121 | lru.key.RLock() 122 | defer lru.key.RUnlock() 123 | defer close(retval) 124 | 125 | for entry := range nested { 126 | select { 127 | case retval <- entry.Key: 128 | // Intentionally Left Blank 129 | case <-ctx.Done(): 130 | return 131 | } 132 | } 133 | }() 134 | 135 | return retval 136 | } 137 | -------------------------------------------------------------------------------- /filesystem_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "path/filepath" 8 | "testing" 9 | ) 10 | 11 | func TestEnumerateDirectoryOptions_UniqueBits(t *testing.T) { 12 | isPowerOfTwo := func(subject float64) bool { 13 | a := math.Abs(math.Log2(subject)) 14 | b := math.Floor(a) 15 | 16 | return a-b < .0000001 17 | } 18 | 19 | if !isPowerOfTwo(64) { 20 | t.Log("isPowerOfTwo decided 64 is not a power of two.") 21 | t.FailNow() 22 | } 23 | 24 | if isPowerOfTwo(91) { 25 | t.Log("isPowerOfTwo decided 91 is a power of two.") 26 | t.FailNow() 27 | } 28 | 29 | seen := make(map[DirectoryOptions]struct{}) 30 | 31 | declared := []DirectoryOptions{ 32 | DirectoryOptionsExcludeFiles, 33 | DirectoryOptionsExcludeDirectories, 34 | DirectoryOptionsRecursive, 35 | } 36 | 37 | for _, option := range declared { 38 | if _, ok := seen[option]; ok { 39 | t.Logf("Option: %d has already been declared.", option) 40 | t.Fail() 41 | } 42 | seen[option] = struct{}{} 43 | 44 | if !isPowerOfTwo(float64(option)) { 45 | t.Logf("Option should have been a power of two, got %g instead.", float64(option)) 46 | t.Fail() 47 | } 48 | } 49 | } 50 | 51 | func TestDirectory_Enumerate(t *testing.T) { 52 | subject := Directory{ 53 | Location: filepath.Join(".", "testdata", "foo"), 54 | } 55 | 56 | testCases := []struct { 57 | options DirectoryOptions 58 | expected map[string]struct{} 59 | }{ 60 | { 61 | options: 0, 62 | expected: map[string]struct{}{ 63 | filepath.Join("testdata", "foo", "a.txt"): {}, 64 | filepath.Join("testdata", "foo", "c.txt"): {}, 65 | filepath.Join("testdata", "foo", "bar"): {}, 66 | }, 67 | }, 68 | { 69 | options: DirectoryOptionsExcludeFiles, 70 | expected: map[string]struct{}{ 71 | filepath.Join("testdata", "foo", "bar"): {}, 72 | }, 73 | }, 74 | { 75 | options: DirectoryOptionsExcludeDirectories, 76 | expected: map[string]struct{}{ 77 | filepath.Join("testdata", "foo", "a.txt"): {}, 78 | filepath.Join("testdata", "foo", "c.txt"): {}, 79 | }, 80 | }, 81 | { 82 | options: DirectoryOptionsRecursive, 83 | expected: map[string]struct{}{ 84 | filepath.Join("testdata", "foo", "bar"): {}, 85 | filepath.Join("testdata", "foo", "bar", "b.txt"): {}, 86 | filepath.Join("testdata", "foo", "a.txt"): {}, 87 | filepath.Join("testdata", "foo", "c.txt"): {}, 88 | }, 89 | }, 90 | { 91 | options: DirectoryOptionsExcludeFiles | DirectoryOptionsRecursive, 92 | expected: map[string]struct{}{ 93 | filepath.Join("testdata", "foo", "bar"): {}, 94 | }, 95 | }, 96 | { 97 | options: DirectoryOptionsRecursive | DirectoryOptionsExcludeDirectories, 98 | expected: map[string]struct{}{ 99 | filepath.Join("testdata", "foo", "a.txt"): {}, 100 | filepath.Join("testdata", "foo", "bar", "b.txt"): {}, 101 | filepath.Join("testdata", "foo", "c.txt"): {}, 102 | }, 103 | }, 104 | { 105 | options: DirectoryOptionsExcludeDirectories | DirectoryOptionsExcludeFiles, 106 | expected: map[string]struct{}{}, 107 | }, 108 | { 109 | options: DirectoryOptionsExcludeFiles | DirectoryOptionsRecursive | DirectoryOptionsExcludeDirectories, 110 | expected: map[string]struct{}{}, 111 | }, 112 | } 113 | 114 | for _, tc := range testCases { 115 | subject.Options = tc.options 116 | t.Run(fmt.Sprintf("%d", uint(tc.options)), func(t *testing.T) { 117 | for entry := range subject.Enumerate(context.Background()) { 118 | if _, ok := tc.expected[entry]; !ok { 119 | t.Logf("unexpected result: %q", entry) 120 | t.Fail() 121 | } 122 | delete(tc.expected, entry) 123 | } 124 | 125 | if len(tc.expected) != 0 { 126 | for unseenFile := range tc.expected { 127 | t.Logf("missing file: %q", unseenFile) 128 | } 129 | t.Fail() 130 | } 131 | }) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /list.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | // List is a dynamically sized list akin to List in the .NET world, 11 | // ArrayList in the Java world, or vector in the C++ world. 12 | type List[T any] struct { 13 | underlyer []T 14 | key sync.RWMutex 15 | } 16 | 17 | // NewList creates a new list which contains the elements provided. 18 | func NewList[T any](entries ...T) *List[T] { 19 | return &List[T]{ 20 | underlyer: entries, 21 | } 22 | } 23 | 24 | // Add appends an entry to the logical end of the List. 25 | func (l *List[T]) Add(entries ...T) { 26 | l.key.Lock() 27 | defer l.key.Unlock() 28 | l.underlyer = append(l.underlyer, entries...) 29 | } 30 | 31 | // AddAt injects values beginning at `pos`. If multiple values 32 | // are provided in `entries` they are placed in the same order 33 | // they are provided. 34 | func (l *List[T]) AddAt(pos uint, entries ...T) { 35 | l.key.Lock() 36 | defer l.key.Unlock() 37 | 38 | l.underlyer = append(l.underlyer[:pos], append(entries, l.underlyer[pos:]...)...) 39 | } 40 | 41 | // Enumerate lists each element present in the collection 42 | func (l *List[T]) Enumerate(ctx context.Context) Enumerator[T] { 43 | retval := make(chan T) 44 | 45 | go func() { 46 | l.key.RLock() 47 | defer l.key.RUnlock() 48 | defer close(retval) 49 | 50 | for _, entry := range l.underlyer { 51 | select { 52 | case retval <- entry: 53 | // Intentionally Left Blank 54 | case <-ctx.Done(): 55 | return 56 | } 57 | } 58 | }() 59 | 60 | return retval 61 | } 62 | 63 | // Get retreives the value stored in a particular position of the list. 64 | // If no item exists at the given position, the second parameter will be 65 | // returned as false. 66 | func (l *List[T]) Get(pos uint) (T, bool) { 67 | l.key.RLock() 68 | defer l.key.RUnlock() 69 | 70 | if pos > uint(len(l.underlyer)) { 71 | return *new(T), false 72 | } 73 | return l.underlyer[pos], true 74 | } 75 | 76 | // IsEmpty tests to see if this List has any elements present. 77 | func (l *List[T]) IsEmpty() bool { 78 | l.key.RLock() 79 | defer l.key.RUnlock() 80 | return len(l.underlyer) == 0 81 | } 82 | 83 | // Length returns the number of elements in the List. 84 | func (l *List[T]) Length() uint { 85 | l.key.RLock() 86 | defer l.key.RUnlock() 87 | return uint(len(l.underlyer)) 88 | } 89 | 90 | // Remove retreives a value from this List and shifts all other values. 91 | func (l *List[T]) Remove(pos uint) (T, bool) { 92 | l.key.Lock() 93 | defer l.key.Unlock() 94 | 95 | if pos > uint(len(l.underlyer)) { 96 | return *new(T), false 97 | } 98 | retval := l.underlyer[pos] 99 | l.underlyer = append(l.underlyer[:pos], l.underlyer[pos+1:]...) 100 | return retval, true 101 | } 102 | 103 | // Set updates the value stored at a given position in the List. 104 | func (l *List[T]) Set(pos uint, val T) bool { 105 | l.key.Lock() 106 | defer l.key.Unlock() 107 | var retval bool 108 | count := uint(len(l.underlyer)) 109 | if pos > count { 110 | retval = false 111 | } else { 112 | l.underlyer[pos] = val 113 | retval = true 114 | } 115 | return retval 116 | } 117 | 118 | // String generates a textual representation of the List for the sake of debugging. 119 | func (l *List[T]) String() string { 120 | l.key.RLock() 121 | defer l.key.RUnlock() 122 | 123 | builder := bytes.NewBufferString("[") 124 | 125 | for i, entry := range l.underlyer { 126 | if i >= 15 { 127 | builder.WriteString("... ") 128 | break 129 | } 130 | builder.WriteString(fmt.Sprintf("%v ", entry)) 131 | } 132 | builder.Truncate(builder.Len() - 1) 133 | builder.WriteRune(']') 134 | return builder.String() 135 | } 136 | 137 | // Swap switches the values that are stored at positions `x` and `y` 138 | func (l *List[T]) Swap(x, y uint) bool { 139 | l.key.Lock() 140 | defer l.key.Unlock() 141 | return l.swap(x, y) 142 | } 143 | 144 | func (l *List[T]) swap(x, y uint) bool { 145 | count := uint(len(l.underlyer)) 146 | if x < count && y < count { 147 | temp := l.underlyer[x] 148 | l.underlyer[x] = l.underlyer[y] 149 | l.underlyer[y] = temp 150 | return true 151 | } 152 | return false 153 | } 154 | -------------------------------------------------------------------------------- /dictionary_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestDictionary_Enumerate(t *testing.T) { 10 | dictSets := [][]string{ 11 | {"alpha", "beta", "charlie"}, 12 | {"also", "always"}, 13 | {"canned", "beans"}, 14 | {"duplicated", "duplicated", "after"}, 15 | } 16 | 17 | for _, ds := range dictSets { 18 | t.Run("", func(t *testing.T) { 19 | subject := Dictionary{} 20 | expected := make(map[string]bool) 21 | added := 0 22 | for _, entry := range ds { 23 | if subject.Add(entry) { 24 | added++ 25 | } 26 | expected[entry] = false 27 | } 28 | 29 | expectedSize := len(expected) 30 | 31 | if added != expectedSize { 32 | t.Logf("`Add` returned true %d times, expected %d times", added, expectedSize) 33 | t.Fail() 34 | } 35 | 36 | if subjectSize := CountAll[string](subject); subjectSize != expectedSize { 37 | t.Logf("`CountAll` returned %d elements, expected %d", subjectSize, expectedSize) 38 | t.Fail() 39 | } 40 | 41 | prev := "" 42 | for result := range subject.Enumerate(context.Background()) { 43 | t.Log(result) 44 | if alreadySeen, ok := expected[result]; !ok { 45 | t.Log("An unadded value was returned") 46 | t.Fail() 47 | } else if alreadySeen { 48 | t.Logf("\"%s\" was duplicated", result) 49 | t.Fail() 50 | } 51 | 52 | if stringle(result, prev) { 53 | t.Logf("Results \"%s\" and \"%s\" were not alphabetized.", prev, result) 54 | t.Fail() 55 | } 56 | prev = result 57 | 58 | expected[result] = true 59 | } 60 | }) 61 | } 62 | } 63 | 64 | func TestDictionary_Add(t *testing.T) { 65 | subject := Dictionary{} 66 | 67 | subject.Add("word") 68 | 69 | if rootChildrenCount := len(subject.root.Children); rootChildrenCount != 1 { 70 | t.Logf("The root should only have one child, got %d instead.", rootChildrenCount) 71 | t.Fail() 72 | } 73 | 74 | if retreived, ok := subject.root.Children['w']; ok { 75 | leaf := retreived.Navigate("ord") 76 | if leaf == nil { 77 | t.Log("Unable to navigate from `w`") 78 | t.Fail() 79 | } else if !leaf.IsWord { 80 | t.Log("leaf should have been a word") 81 | t.Fail() 82 | } 83 | } else { 84 | t.Log("Root doesn't have child for `w`") 85 | t.Fail() 86 | } 87 | } 88 | 89 | func TestTrieNode_Navigate(t *testing.T) { 90 | leaf := trieNode{ 91 | IsWord: true, 92 | } 93 | subject := trieNode{ 94 | Children: map[rune]*trieNode{ 95 | 'a': { 96 | Children: map[rune]*trieNode{ 97 | 'b': { 98 | Children: map[rune]*trieNode{ 99 | 'c': &leaf, 100 | }, 101 | }, 102 | }, 103 | }, 104 | }, 105 | } 106 | 107 | testCases := []struct { 108 | address string 109 | expected *trieNode 110 | }{ 111 | {"abc", &leaf}, 112 | {"abd", nil}, 113 | {"", &subject}, 114 | {"a", subject.Children['a']}, 115 | } 116 | 117 | for _, tc := range testCases { 118 | t.Run("", func(t *testing.T) { 119 | if result := subject.Navigate(tc.address); result != tc.expected { 120 | t.Logf("got: %v want: %v", result, tc.expected) 121 | t.Fail() 122 | } 123 | }) 124 | } 125 | } 126 | 127 | func Test_stringle(t *testing.T) { 128 | testCases := []struct { 129 | left string 130 | right string 131 | expected bool 132 | }{ 133 | {"a", "b", true}, 134 | {"b", "a", false}, 135 | {"a", "a", true}, 136 | {"alpha", "b", true}, 137 | {"a", "beta", true}, 138 | {"alpha", "alpha", true}, 139 | {"alpha", "alphabet", true}, 140 | {"alphabet", "alpha", false}, 141 | {"", "a", true}, 142 | {"", "", true}, 143 | } 144 | 145 | for _, tc := range testCases { 146 | t.Run(strings.Join([]string{tc.left, tc.right}, ","), func(t *testing.T) { 147 | if got := stringle(tc.left, tc.right); got != tc.expected { 148 | t.Logf("got: %v want: %v", got, tc.expected) 149 | t.Fail() 150 | } 151 | }) 152 | } 153 | } 154 | 155 | func stringle(left, right string) bool { 156 | other := []byte(right) 157 | for i, letter := range []byte(left) { 158 | if i >= len(other) { 159 | return false 160 | } 161 | 162 | if letter > other[i] { 163 | return false 164 | } else if letter < other[i] { 165 | break 166 | } 167 | } 168 | return true 169 | } 170 | -------------------------------------------------------------------------------- /queue_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func ExampleQueue_Add() { 9 | subject := &Queue[int]{} 10 | subject.Add(1) 11 | subject.Add(2) 12 | res, _ := subject.Peek() 13 | fmt.Println(res) 14 | // Output: 1 15 | } 16 | 17 | func ExampleNewQueue() { 18 | empty := NewQueue[int]() 19 | fmt.Println(empty.Length()) 20 | 21 | populated := NewQueue(1, 2, 3, 5, 8, 13) 22 | fmt.Println(populated.Length()) 23 | // Output: 24 | // 0 25 | // 6 26 | } 27 | 28 | func ExampleQueue_IsEmpty() { 29 | empty := NewQueue[int]() 30 | fmt.Println(empty.IsEmpty()) 31 | 32 | populated := NewQueue(1, 2, 3, 5, 8, 13) 33 | fmt.Println(populated.IsEmpty()) 34 | // Output: 35 | // true 36 | // false 37 | } 38 | 39 | func ExampleQueue_Next() { 40 | subject := NewQueue(1, 2, 3, 5, 8, 13) 41 | for !subject.IsEmpty() { 42 | val, _ := subject.Next() 43 | fmt.Println(val) 44 | } 45 | // Output: 46 | // 1 47 | // 2 48 | // 3 49 | // 5 50 | // 8 51 | // 13 52 | } 53 | 54 | func TestQueue_Length(t *testing.T) { 55 | empty := NewQueue[int]() 56 | if count := empty.Length(); count != 0 { 57 | t.Logf("got: %d\nwant: %d", count, 0) 58 | t.Fail() 59 | } 60 | 61 | // Not the type magic number you're thinking of! 62 | // https://en.wikipedia.org/wiki/1729_(number) 63 | single := NewQueue(1729) 64 | if count := single.Length(); count != 1 { 65 | t.Logf("got: %d\nwant: %d", count, 1) 66 | t.Fail() 67 | } 68 | 69 | expectedMany := []interface{}{'a', 'b', 'c', 'd', 'e', 'e', 'f', 'g'} 70 | many := NewQueue(expectedMany...) 71 | if count := many.Length(); count != uint(len(expectedMany)) { 72 | t.Logf("got: %d\nwant: %d", count, len(expectedMany)) 73 | } 74 | } 75 | 76 | func TestQueue_Length_NonConstructed(t *testing.T) { 77 | subject := &Queue[int]{} 78 | if got := subject.Length(); got != 0 { 79 | t.Logf("got: %d\nwant: %d", got, 0) 80 | t.Fail() 81 | } 82 | } 83 | 84 | func TestQueue_Next_NonConstructed(t *testing.T) { 85 | const expected = 0 86 | subject := &Queue[int]{} 87 | if got, ok := subject.Next(); ok { 88 | t.Logf("Next should not have been ok") 89 | t.Fail() 90 | } else if got != expected { 91 | t.Logf("got: %v\nwant: %v", got, expected) 92 | t.Fail() 93 | } 94 | } 95 | 96 | func TestQueue_Peek_DoesntRemove(t *testing.T) { 97 | expected := []interface{}{1, 2, 3} 98 | subject := NewQueue(expected...) 99 | if result, ok := subject.Peek(); !ok { 100 | t.Logf("no item present") 101 | t.Fail() 102 | } else if result != expected[0] { 103 | t.Logf("got: %d\nwant: %d", result, 1) 104 | t.Fail() 105 | } else if count := subject.Length(); count != uint(len(expected)) { 106 | t.Logf("got: %d\nwant: %d", count, len(expected)) 107 | } 108 | } 109 | 110 | func TestQueue_Peek_NonConstructed(t *testing.T) { 111 | const expected = 0 112 | subject := &Queue[int]{} 113 | if got, ok := subject.Peek(); ok { 114 | t.Logf("Peek should not have been ok") 115 | t.Fail() 116 | } else if got != expected { 117 | t.Logf("got: %v\nwant: %v", got, expected) 118 | t.Fail() 119 | } 120 | } 121 | 122 | func TestQueue_ToSlice(t *testing.T) { 123 | subject := NewQueue(0, 1, 1, 2, 3, 5) 124 | expectedSliceString := "[0 1 1 2 3 5]" 125 | if result := subject.ToSlice(); len(result) != 6 { 126 | t.Logf("got: %d\nwant: %d", len(result), 6) 127 | t.Fail() 128 | } else if fmt.Sprintf("%v", result) != expectedSliceString { 129 | t.Logf("got:\n%v\nwant:\n%s\n", result, expectedSliceString) 130 | t.Fail() 131 | } 132 | } 133 | 134 | func TestQueue_ToSlice_Empty(t *testing.T) { 135 | subject := NewQueue[int]() 136 | result := subject.ToSlice() 137 | 138 | if len(result) != 0 { 139 | t.Logf("result should have been empty") 140 | t.Fail() 141 | } 142 | expectedStr := "[]" 143 | resultStr := fmt.Sprintf("%v", result) 144 | if resultStr != expectedStr { 145 | t.Logf("got:\n%s\nwant:\n%s", resultStr, expectedStr) 146 | t.Fail() 147 | } 148 | } 149 | 150 | func TestQueue_ToSlice_NotConstructed(t *testing.T) { 151 | subject := &Queue[int]{} 152 | result := subject.ToSlice() 153 | 154 | if len(result) != 0 { 155 | t.Logf("result should have been empty") 156 | t.Fail() 157 | } 158 | expectedStr := "[]" 159 | resultStr := fmt.Sprintf("%v", result) 160 | if resultStr != expectedStr { 161 | t.Logf("got:\n%s\nwant:\n%s", resultStr, expectedStr) 162 | t.Fail() 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /dictionary.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "sort" 6 | ) 7 | 8 | type trieNode struct { 9 | IsWord bool 10 | Children map[rune]*trieNode 11 | } 12 | 13 | func (node trieNode) Population() uint { 14 | var sum uint 15 | 16 | for _, child := range node.Children { 17 | sum += child.Population() 18 | } 19 | 20 | if node.IsWord { 21 | sum++ 22 | } 23 | 24 | return sum 25 | } 26 | 27 | func (node *trieNode) Navigate(word string) *trieNode { 28 | cursor := node 29 | for len(word) > 0 && cursor != nil { 30 | if next, ok := cursor.Children[rune(word[0])]; ok { 31 | cursor = next 32 | word = word[1:] 33 | } else { 34 | return nil 35 | } 36 | } 37 | return cursor 38 | } 39 | 40 | // Dictionary is a list of words. It is implemented as a Trie for memory efficiency. 41 | type Dictionary struct { 42 | root *trieNode 43 | size int64 44 | } 45 | 46 | // Add inserts a word into the dictionary, and returns whether or not that word was a new word. 47 | // 48 | // Time complexity: O(m) where 'm' is the length of word. 49 | func (dict *Dictionary) Add(word string) (wasAdded bool) { 50 | if dict.root == nil { 51 | dict.root = &trieNode{} 52 | } 53 | 54 | cursor := dict.root 55 | 56 | for len(word) > 0 { 57 | if cursor.Children == nil { 58 | cursor.Children = make(map[rune]*trieNode) 59 | } 60 | 61 | nextLetter := rune(word[0]) 62 | 63 | next, ok := cursor.Children[nextLetter] 64 | if !ok { 65 | next = &trieNode{} 66 | cursor.Children[nextLetter] = next 67 | } 68 | cursor = next 69 | word = word[1:] 70 | } 71 | wasAdded = !cursor.IsWord 72 | if wasAdded { 73 | dict.size++ 74 | } 75 | cursor.IsWord = true 76 | return 77 | } 78 | 79 | // Clear removes all items from the dictionary. 80 | func (dict *Dictionary) Clear() { 81 | dict.root = nil 82 | dict.size = 0 83 | } 84 | 85 | // Contains searches the Dictionary to see if the specified word is present. 86 | // 87 | // Time complexity: O(m) where 'm' is the length of word. 88 | func (dict Dictionary) Contains(word string) bool { 89 | if dict.root == nil { 90 | return false 91 | } 92 | targetNode := dict.root.Navigate(word) 93 | return targetNode != nil && targetNode.IsWord 94 | } 95 | 96 | // Remove ensures that `word` is not in the Dictionary. Returns whether or not an item was removed. 97 | // 98 | // Time complexity: O(m) where 'm' is the length of word. 99 | func (dict *Dictionary) Remove(word string) (wasRemoved bool) { 100 | lastPos := len(word) - 1 101 | parent := dict.root.Navigate(word[:lastPos]) 102 | if parent == nil { 103 | return 104 | } 105 | 106 | lastLetter := rune(word[lastPos]) 107 | 108 | subject, ok := parent.Children[lastLetter] 109 | if !ok { 110 | return 111 | } 112 | 113 | wasRemoved = subject.IsWord 114 | 115 | if wasRemoved { 116 | dict.size-- 117 | } 118 | 119 | subject.IsWord = false 120 | if subject.Population() == 0 { 121 | delete(parent.Children, lastLetter) 122 | } 123 | return 124 | } 125 | 126 | // Size reports the number of words there are in the Dictionary. 127 | // 128 | // Time complexity: O(1) 129 | func (dict Dictionary) Size() int64 { 130 | return dict.size 131 | } 132 | 133 | // Enumerate lists each word in the Dictionary alphabetically. 134 | func (dict Dictionary) Enumerate(ctx context.Context) Enumerator[string] { 135 | if dict.root == nil { 136 | return Empty[string]().Enumerate(ctx) 137 | } 138 | return dict.root.Enumerate(ctx) 139 | } 140 | 141 | func (node trieNode) Enumerate(ctx context.Context) Enumerator[string] { 142 | var enumerateHelper func(trieNode, string) 143 | 144 | results := make(chan string) 145 | 146 | enumerateHelper = func(subject trieNode, prefix string) { 147 | if subject.IsWord { 148 | select { 149 | case results <- prefix: 150 | case <-ctx.Done(): 151 | return 152 | } 153 | } 154 | 155 | alphabetizedChildren := []rune{} 156 | for letter := range subject.Children { 157 | alphabetizedChildren = append(alphabetizedChildren, letter) 158 | } 159 | sort.Slice(alphabetizedChildren, func(i, j int) bool { 160 | return alphabetizedChildren[i] < alphabetizedChildren[j] 161 | }) 162 | 163 | for _, letter := range alphabetizedChildren { 164 | enumerateHelper(*subject.Children[letter], prefix+string(letter)) 165 | } 166 | } 167 | 168 | go func() { 169 | defer close(results) 170 | enumerateHelper(node, "") 171 | }() 172 | 173 | return results 174 | } 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # collection 2 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/marstr/collection/v2)](https://pkg.go.dev/github.com/marstr/collection/v2) [![Build and Test](https://github.com/marstr/collection/workflows/Build%20and%20Test/badge.svg)](https://github.com/marstr/collection/actions?query=workflow%3A"Build+and+Test") 3 | 4 | # Usage 5 | 6 | ## Available Data Structures: 7 | 8 | ### Dictionary 9 | This is a logical set of strings. It utilizes a prefix tree model to be very space efficient. 10 | 11 | ### LinkedList 12 | A collection that offers fast, consistent insertion time when adding to either the beginning or end. Accessing a random element is slower than other similar list data structures. 13 | 14 | ### List 15 | Similar to a C++ `Vector`, Java `ArrayList`, or C# `List` this is a wrapper over top of arrays that allows for quick random access, but somewhat slower insertion characteristics than a `LinkedList`. 16 | 17 | ### LRUCache 18 | This name is short for "Least Recently Used Cache". It holds a predetermined number of items, and as new items inserted, the least recently added or read item will be removed. This can be a useful way to build a tool that uses the proxy pattern to have quick access to the most useful items, and slower access to any other item. There is a memory cost for this, but it's often worth it. 19 | 20 | ### Queue 21 | Stores items without promising random access. The first thing you put in will be the first thing you get out. 22 | 23 | ### Stack 24 | Stores items without promising random access. The first thing you put in will be the last thing you get out. 25 | 26 | ## Querying Collections 27 | Inspired by .NET's Linq, querying data structures used in this library is a snap! Build Go pipelines quickly and easily which will apply lambdas as they query your data structures. 28 | 29 | ### Slices 30 | Converting between slices and a queryable structure is as trivial as it should be. 31 | ``` Go 32 | original := []string{"a", "b", "c"} 33 | subject := collection.AsEnumerable(original...) 34 | 35 | for entry := range subject.Enumerate(context.Background()) { 36 | fmt.Println(entry) 37 | } 38 | // Output: 39 | // a 40 | // b 41 | // c 42 | 43 | ``` 44 | 45 | ### Where 46 | ``` Go 47 | ctx, cancel := context.WithCancel(context.Background()) 48 | defer cancel() 49 | 50 | subject := collection.AsEnumerable[int](1, 2, 3, 4, 5, 6) 51 | filtered := collection.Where(subject, func(num int) bool{ 52 | return num > 3 53 | }) 54 | for entry := range filtered.Enumerate(ctx) { 55 | fmt.Println(entry) 56 | } 57 | // Output: 58 | // 4 59 | // 5 60 | // 6 61 | ``` 62 | ### Select 63 | ``` Go 64 | ctx, cancel := context.WithCancel(context.Background()) 65 | defer cancel() 66 | 67 | subject := collection.AsEnumerable[int](1, 2, 3, 4, 5, 6) 68 | updated := collection.Select[int](subject, func(num int) int { 69 | return num + 10 70 | }) 71 | for entry := range updated.Enumerate(ctx) { 72 | fmt.Println(entry) 73 | } 74 | 75 | // Output: 76 | // 11 77 | // 12 78 | // 13 79 | // 14 80 | // 15 81 | // 16 82 | ``` 83 | 84 | ## Queues 85 | ### Creating a Queue 86 | 87 | ``` Go 88 | ctx, cancel := context.WithCancel(context.Background()) 89 | defer cancel() 90 | 91 | subject := collection.NewQueue(1, 2, 3, 5, 8, 13, 21) 92 | selected := subject.Enumerate(ctx).Skip(3).Take(3) 93 | for entry := range selected { 94 | fmt.Println(entry) 95 | } 96 | 97 | // Output: 98 | // 5 99 | // 8 100 | // 13 101 | ``` 102 | 103 | ### Checking if a Queue is empty 104 | ``` Go 105 | populated := collection.NewQueue(1, 2, 3, 5, 8, 13) 106 | notPopulated := collection.NewQueue[int]() 107 | fmt.Println(populated.IsEmpty()) 108 | fmt.Println(notPopulated.IsEmpty()) 109 | // Output: 110 | // false 111 | // true 112 | ``` 113 | 114 | ## Other utilities 115 | 116 | ### Fibonacci 117 | This was added to test Enumerable types that have no logical conclusion. But it may prove useful other places, so it is available in the user-facing package and not hidden away in a test package. 118 | 119 | ### Filesystem 120 | Find the standard library's pattern for looking through a directory cumbersome? Use the collection querying mechanisms seen above to search a directory as a collection of files and child directories. 121 | 122 | # Versioning 123 | This library will conform to strict semantic versions as defined by [semver.org](http://semver.org/spec/v2.0.0.html)'s v2 specification. 124 | 125 | # Contributing 126 | I accept contributions! Please submit PRs to the `main` or `v1` branches. Remember to add tests! 127 | 128 | # F.A.Q. 129 | 130 | ## Should I use v1 or v2? 131 | 132 | If you are newly adopting this library, and are able to use Go 1.18 or newer, it is highly recommended that you use v2. 133 | 134 | V2 was primarily added to support Go generics when they were introduced in Go 1.18, but there were other breaking changes made because of the opportunity to do with the major version bump. 135 | 136 | Because it's not reasonable to expect everybody to adopt the newest versions of Go immediately as they're released, v1 of this library wil be activey supported until Go 1.17 is no longer supported by the Go team. After that community contributions to v1 will be entertained, but active development won't be ported to the `v1` branch. 137 | 138 | ## Why does `Enumerate` take a `context.Context`? 139 | 140 | Having a context associated with the enumeration allows for cancellation. This is valuable in some scenarios, where enumeration may be a time-consuming operation. For example, imagine an `Enumerable` that wraps a web API which returns results in pages. Injecting a context 141 | allows for you to add operation timeouts, and otherwise protect yourself from an operation that may not finish quickly enough for you (or at all.) 142 | 143 | However, under the covers an Enumerator[T] is a `<-chan T`. This decision means that a separate goroutine is used to publish to the channel while your goroutine reads from it. 144 | 145 | **That means if your code stops before all items in the Enumerator are read, a goroutine and all of the memory it's using will be leaked.** 146 | 147 | This is a known problem, and it's understood why it's not ideal. The workaround is easy - if there's ever a chance you won't enumerate all items, protect yourself by using the following pattern: 148 | 149 | ``` Go 150 | ctx, cancel := context.WithCancel(context.Background()) 151 | defer cancel() 152 | 153 | // ... 154 | 155 | for item := range myEnumerable.Enumerate(ctx) { 156 | // ... 157 | } 158 | ``` -------------------------------------------------------------------------------- /linkedlist_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestLinkedList_findLast_empty(t *testing.T) { 8 | if result := findLast[int](nil); result != nil { 9 | t.Logf("got: %v\nwant: %v", result, nil) 10 | } 11 | } 12 | 13 | func TestLinkedList_merge(t *testing.T) { 14 | testCases := []struct { 15 | Left *LinkedList[int] 16 | Right *LinkedList[int] 17 | Expected []int 18 | Comp Comparator[int] 19 | }{ 20 | { 21 | NewLinkedList[int](1, 3, 5), 22 | NewLinkedList[int](2, 4), 23 | []int{1, 2, 3, 4, 5}, 24 | UncheckedComparatori, 25 | }, 26 | { 27 | NewLinkedList[int](1, 2, 3), 28 | NewLinkedList[int](), 29 | []int{1, 2, 3}, 30 | UncheckedComparatori, 31 | }, 32 | { 33 | NewLinkedList[int](), 34 | NewLinkedList[int](1, 2, 3), 35 | []int{1, 2, 3}, 36 | UncheckedComparatori, 37 | }, 38 | { 39 | NewLinkedList[int](), 40 | NewLinkedList[int](), 41 | []int{}, 42 | UncheckedComparatori, 43 | }, 44 | { 45 | NewLinkedList[int](1), 46 | NewLinkedList[int](1), 47 | []int{1, 1}, 48 | UncheckedComparatori, 49 | }, 50 | { 51 | NewLinkedList(2), 52 | NewLinkedList(1), 53 | []int{1, 2}, 54 | UncheckedComparatori, 55 | }, 56 | { 57 | NewLinkedList(3), 58 | NewLinkedList[int](), 59 | []int{3}, 60 | UncheckedComparatori, 61 | }, 62 | { 63 | NewLinkedList[int](), 64 | NewLinkedList(10), 65 | []int{10}, 66 | UncheckedComparatori, 67 | }, 68 | } 69 | 70 | for _, tc := range testCases { 71 | t.Run("", func(t *testing.T) { 72 | result, err := merge(tc.Left.first, tc.Right.first, tc.Comp) 73 | if err != nil { 74 | t.Error(err) 75 | } 76 | 77 | i := 0 78 | for cursor := result; cursor != nil; cursor, i = cursor.next, i+1 { 79 | if cursor.payload != tc.Expected[i] { 80 | t.Logf("got: %d want: %d", cursor.payload, tc.Expected[i]) 81 | t.Fail() 82 | } 83 | } 84 | 85 | if expectedLength := len(tc.Expected); i != expectedLength { 86 | t.Logf("Unexpected length:\n\tgot: %d\n\twant: %d", i, expectedLength) 87 | t.Fail() 88 | } 89 | }) 90 | } 91 | } 92 | 93 | func UncheckedComparatori(a, b int) (int, error) { 94 | return a - b, nil 95 | } 96 | 97 | func TestLinkedList_RemoveBack_single(t *testing.T) { 98 | subject := NewLinkedList(1) 99 | subject.RemoveBack() 100 | if subject.Length() != 0 { 101 | t.Fail() 102 | } 103 | } 104 | 105 | func TestLinkedList_split_Even(t *testing.T) { 106 | subject := NewLinkedList(1, 2, 3, 4) 107 | 108 | left, right := split(subject.first) 109 | if left == nil { 110 | t.Logf("unexpected nil value for left") 111 | t.Fail() 112 | } else if left.payload != 1 { 113 | t.Logf("got: %d\nwant: %d", left.payload, 1) 114 | t.Fail() 115 | } 116 | 117 | if right == nil { 118 | t.Logf("unexpected nil for right") 119 | t.Fail() 120 | } else if right.payload != 3 { 121 | t.Logf("got: %d\nwant: %d", right.payload, 3) 122 | } 123 | } 124 | 125 | func TestLinkedList_split_Odd(t *testing.T) { 126 | subject := NewLinkedList(1, 2, 3, 4, 5) 127 | 128 | left, right := split(subject.first) 129 | 130 | if left == nil { 131 | t.Logf("unexpected nil value for left") 132 | t.Fail() 133 | } else if left.payload != 1 { 134 | t.Logf("got: %d\n want: %d", left.payload, 1) 135 | t.Fail() 136 | } else if last := findLast(left).payload; last != 2 { 137 | t.Logf("got:\n%d\nwant:\n%d", last, 2) 138 | t.Fail() 139 | } 140 | 141 | if right == nil { 142 | t.Logf("unexpected nil value for right") 143 | t.Fail() 144 | } else if right.payload != 3 { 145 | t.Logf("got:\n%d\nwant:\n%d", right.payload, 3) 146 | t.Fail() 147 | } else if last := findLast(right).payload; last != 5 { 148 | t.Logf("got:\n%d\nwant:%d", last, 5) 149 | } 150 | } 151 | 152 | func TestLinkedList_split_Empty(t *testing.T) { 153 | subject := NewLinkedList[*int]() 154 | 155 | left, right := split(subject.first) 156 | 157 | if left != nil { 158 | t.Logf("got: %v\nwant: %v", left, nil) 159 | t.Fail() 160 | } 161 | 162 | if right != nil { 163 | t.Logf("got: %v\nwant: %v", right, nil) 164 | t.Fail() 165 | } 166 | } 167 | 168 | func TestLinkedList_split_Single(t *testing.T) { 169 | subject := NewLinkedList(1) 170 | 171 | left, right := split(subject.first) 172 | 173 | if left == nil { 174 | t.Logf("unexpected nil value for left") 175 | t.Fail() 176 | } else if left.payload != 1 { 177 | t.Logf("got: %d\nwant: %d", left.payload, 1) 178 | t.Fail() 179 | } 180 | 181 | if right != nil { 182 | t.Logf("got: %v\nwant: %v", right, nil) 183 | t.Fail() 184 | } 185 | 186 | if last := findLast(left).payload; last != 1 { 187 | t.Logf("got:\n%d\nwant:\n%d", last, 1) 188 | t.Fail() 189 | } 190 | } 191 | 192 | func TestLinkedList_split_Double(t *testing.T) { 193 | subject := NewLinkedList(1, 2) 194 | left, right := split(subject.first) 195 | 196 | if left == nil { 197 | t.Logf("unexpected nil value for left") 198 | t.Fail() 199 | } else if left.payload != 1 { 200 | t.Logf("got: %d\nwant: %d", left.payload, 1) 201 | } 202 | 203 | if right == nil { 204 | t.Logf("unexpected nil value for right") 205 | t.Fail() 206 | } else if right.payload != 2 { 207 | t.Logf("got: %d\nwant: %d", right.payload, 2) 208 | } 209 | } 210 | 211 | func TestLinkedList_Swap_OutOfBounds(t *testing.T) { 212 | subject := NewLinkedList(2, 3) 213 | if err := subject.Swap(0, 8); err == nil { 214 | t.Log("swap should have failed on y") 215 | t.Fail() 216 | } 217 | 218 | if err := subject.Swap(11, 1); err == nil { 219 | t.Logf("swap shoud have failed on x") 220 | t.Fail() 221 | } 222 | 223 | if count := subject.Length(); count != 2 { 224 | t.Logf("got: %d\nwant: %d", count, 2) 225 | t.Fail() 226 | } 227 | 228 | wantStr := "[2 3]" 229 | gotStr := subject.String() 230 | if wantStr != gotStr { 231 | t.Logf("got: %s\nwant: %s", gotStr, wantStr) 232 | t.Fail() 233 | } 234 | } 235 | 236 | func TestLinkedList_Get_OutsideBounds(t *testing.T) { 237 | subject := NewLinkedList(2, 3, 5, 8, 13, 21) 238 | result, ok := subject.Get(10) 239 | if !(result == 0 && ok == false) { 240 | t.Logf("got: %v %v\nwant: %v %v", result, ok, nil, false) 241 | t.Fail() 242 | } 243 | } 244 | 245 | func TestLinkedList_removeNode(t *testing.T) { 246 | removeHead := func(t *testing.T) { 247 | subject := NewLinkedList(1, 2, 3) 248 | 249 | subject.removeNode(subject.first) 250 | 251 | if subject.length != 2 { 252 | t.Logf("got %d, want %d", subject.length, 2) 253 | t.Fail() 254 | } 255 | 256 | if first, ok := subject.Get(0); ok { 257 | if first != 2 { 258 | t.Logf("got %d, want %d", first, 2) 259 | t.Fail() 260 | } 261 | } else { 262 | t.Logf("no item at position 0!") 263 | t.Fail() 264 | } 265 | 266 | if second, ok := subject.Get(1); ok { 267 | if second != 3 { 268 | t.Logf("got %d, want %d", second, 3) 269 | t.Fail() 270 | } 271 | } else { 272 | t.Logf("no item at position 1!") 273 | t.Fail() 274 | } 275 | } 276 | 277 | removeTail := func(t *testing.T) { 278 | subject := NewLinkedList(1, 2, 3) 279 | 280 | subject.removeNode(subject.last) 281 | 282 | if subject.length != 2 { 283 | t.Logf("got %d, want %d", subject.length, 2) 284 | t.Fail() 285 | } 286 | 287 | if first, ok := subject.Get(0); ok { 288 | if first != 1 { 289 | t.Logf("got %d, want %d", first, 1) 290 | t.Fail() 291 | } 292 | } else { 293 | t.Logf("no item at position 0!") 294 | t.Fail() 295 | } 296 | 297 | if second, ok := subject.Get(1); ok { 298 | if second != 2 { 299 | t.Logf("got %d, want %d", second, 2) 300 | t.Fail() 301 | } 302 | } else { 303 | t.Logf("no item at position 1!") 304 | t.Fail() 305 | } 306 | } 307 | 308 | removeMiddle := func(t *testing.T) { 309 | subject := NewLinkedList(1, 2, 3) 310 | 311 | subject.removeNode(subject.first.next) 312 | 313 | if subject.length != 2 { 314 | t.Logf("got %d, want %d", subject.length, 2) 315 | t.Fail() 316 | } 317 | 318 | if first, ok := subject.Get(0); ok { 319 | if first != 1 { 320 | t.Logf("got %d, want %d", first, 1) 321 | t.Fail() 322 | } 323 | } else { 324 | t.Logf("no item at position 0!") 325 | t.Fail() 326 | } 327 | 328 | if second, ok := subject.Get(1); ok { 329 | if second != 3 { 330 | t.Logf("got %d, want %d", second, 3) 331 | t.Fail() 332 | } 333 | } else { 334 | t.Logf("no item at position 1!") 335 | t.Fail() 336 | } 337 | } 338 | 339 | t.Run("RemoveHead", removeHead) 340 | t.Run("RemoveTail", removeTail) 341 | t.Run("RemoveMiddle", removeMiddle) 342 | } 343 | -------------------------------------------------------------------------------- /query_examples_test.go: -------------------------------------------------------------------------------- 1 | package collection_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/marstr/collection/v2" 9 | ) 10 | 11 | func ExampleEnumerableSlice_Enumerate() { 12 | ctx, cancel := context.WithCancel(context.Background()) 13 | defer cancel() 14 | 15 | // When a single value is provided, and it is an array or slice, each value in the array or slice is treated as an enumerable value. 16 | originalInts := []int{1, 2, 3, 4, 5} 17 | wrappedInts := collection.EnumerableSlice[int](originalInts) 18 | 19 | for entry := range wrappedInts.Enumerate(ctx) { 20 | fmt.Print(entry) 21 | } 22 | fmt.Println() 23 | 24 | // It's easy to convert arrays to slices for these enumerations as well. 25 | originalStrings := [7]string{"red", "orange", "yellow", "green", "blue", "indigo", "violet"} 26 | wrappedStrings := collection.EnumerableSlice[string](originalStrings[:]) 27 | for entry := range wrappedStrings.Enumerate(ctx) { 28 | fmt.Println(entry) 29 | } 30 | // Output: 31 | // 12345 32 | // red 33 | // orange 34 | // yellow 35 | // green 36 | // blue 37 | // indigo 38 | // violet 39 | } 40 | 41 | func ExampleEnumerator_Count() { 42 | subject := collection.AsEnumerable("str1", "str1", "str2") 43 | count1 := subject.Enumerate(context.Background()).Count(func(a string) bool { 44 | return a == "str1" 45 | }) 46 | fmt.Println(count1) 47 | // Output: 2 48 | } 49 | 50 | func ExampleEnumerator_CountAll() { 51 | subject := collection.AsEnumerable('a', 'b', 'c', 'd', 'e') 52 | fmt.Println(subject.Enumerate(context.Background()).CountAll()) 53 | // Output: 5 54 | } 55 | 56 | func ExampleEnumerator_ElementAt() { 57 | ctx, cancel := context.WithCancel(context.Background()) 58 | defer cancel() 59 | // ElementAt leaves the Enumerator open, creating a memory leak unless remediated, 60 | // context.Context should be cancelled to indicate that no further reads are coming. 61 | fmt.Print(collection.Fibonacci.Enumerate(ctx).ElementAt(4)) 62 | // Output: 3 63 | } 64 | 65 | func ExampleFirst() { 66 | empty := collection.NewQueue[int]() 67 | notEmpty := collection.NewQueue(1, 2, 3, 4) 68 | 69 | fmt.Println(collection.First[int](empty)) 70 | fmt.Println(collection.First[int](notEmpty)) 71 | 72 | // Output: 73 | // 0 enumerator encountered no elements 74 | // 1 75 | } 76 | 77 | func ExampleLast() { 78 | subject := collection.NewList(1, 2, 3, 4) 79 | fmt.Println(collection.Last[int](subject)) 80 | // Output: 4 81 | } 82 | 83 | func ExampleEnumerator_Last() { 84 | subject := collection.AsEnumerable(1, 2, 3) 85 | fmt.Print(subject.Enumerate(context.Background()).Last()) 86 | //Output: 3 87 | } 88 | 89 | func ExampleMerge() { 90 | ctx, cancel := context.WithCancel(context.Background()) 91 | defer cancel() 92 | 93 | a := collection.AsEnumerable(1, 2, 4) 94 | b := collection.AsEnumerable(8, 16, 32) 95 | c := collection.Merge(a, b) 96 | sum := 0 97 | for x := range c.Enumerate(ctx) { 98 | sum += x 99 | } 100 | fmt.Println(sum) 101 | 102 | product := 1 103 | for y := range a.Enumerate(ctx) { 104 | product *= y 105 | } 106 | fmt.Println(product) 107 | // Output: 108 | // 63 109 | // 8 110 | } 111 | 112 | func ExampleEnumerator_Reverse() { 113 | a := collection.AsEnumerable(1, 2, 3).Enumerate(context.Background()) 114 | a = a.Reverse() 115 | fmt.Println(a.ToSlice()) 116 | // Output: [3 2 1] 117 | } 118 | 119 | func ExampleSelect() { 120 | const offset = 'a' - 1 121 | 122 | subject := collection.AsEnumerable[rune]('a', 'b', 'c') 123 | subject = collection.Select(subject, func(a rune) rune { 124 | return a - offset 125 | }) 126 | 127 | fmt.Println(collection.ToSlice(subject)) 128 | // Output: [1 2 3] 129 | } 130 | 131 | func ExampleSelectMany() { 132 | 133 | type BrewHouse struct { 134 | Name string 135 | Beers collection.Enumerable[string] 136 | } 137 | 138 | breweries := collection.AsEnumerable( 139 | BrewHouse{ 140 | "Mac & Jacks", 141 | collection.AsEnumerable( 142 | "African Amber", 143 | "Ibis IPA", 144 | ), 145 | }, 146 | BrewHouse{ 147 | "Post Doc", 148 | collection.AsEnumerable( 149 | "Prereq Pale", 150 | ), 151 | }, 152 | BrewHouse{ 153 | "Resonate", 154 | collection.AsEnumerable( 155 | "Comfortably Numb IPA", 156 | "Lithium Altbier", 157 | ), 158 | }, 159 | BrewHouse{ 160 | "Triplehorn", 161 | collection.AsEnumerable( 162 | "Samson", 163 | "Pepper Belly", 164 | ), 165 | }, 166 | ) 167 | 168 | ctx, cancel := context.WithCancel(context.Background()) 169 | defer cancel() 170 | 171 | beers := collection.SelectMany(breweries, func(brewer BrewHouse) collection.Enumerator[string] { 172 | return brewer.Beers.Enumerate(ctx) 173 | }) 174 | 175 | for beer := range beers.Enumerate(ctx) { 176 | fmt.Println(beer) 177 | } 178 | 179 | // Output: 180 | // African Amber 181 | // Ibis IPA 182 | // Prereq Pale 183 | // Comfortably Numb IPA 184 | // Lithium Altbier 185 | // Samson 186 | // Pepper Belly 187 | } 188 | 189 | func ExampleSkip() { 190 | trimmed := collection.Take(collection.Skip(collection.Fibonacci, 1), 3) 191 | for entry := range trimmed.Enumerate(context.Background()) { 192 | fmt.Println(entry) 193 | } 194 | // Output: 195 | // 1 196 | // 1 197 | // 2 198 | } 199 | 200 | func ExampleEnumerator_Skip() { 201 | subject := collection.AsEnumerable(1, 2, 3, 4, 5, 6, 7) 202 | skipped := subject.Enumerate(context.Background()).Skip(5) 203 | for entry := range skipped { 204 | fmt.Println(entry) 205 | } 206 | // Output: 207 | // 6 208 | // 7 209 | } 210 | 211 | func ExampleTake() { 212 | ctx, cancel := context.WithCancel(context.Background()) 213 | defer cancel() 214 | 215 | taken := collection.Take(collection.Fibonacci, 4) 216 | for entry := range taken.Enumerate(ctx) { 217 | fmt.Println(entry) 218 | } 219 | // Output: 220 | // 0 221 | // 1 222 | // 1 223 | // 2 224 | } 225 | 226 | func ExampleEnumerator_Take() { 227 | ctx, cancel := context.WithCancel(context.Background()) 228 | defer cancel() 229 | 230 | taken := collection.Fibonacci.Enumerate(ctx).Skip(4).Take(2) 231 | for entry := range taken { 232 | fmt.Println(entry) 233 | } 234 | // Output: 235 | // 3 236 | // 5 237 | } 238 | 239 | func ExampleTakeWhile() { 240 | taken := collection.TakeWhile(collection.Fibonacci, func(x, n uint) bool { 241 | return x < 10 242 | }) 243 | 244 | ctx, cancel := context.WithCancel(context.Background()) 245 | defer cancel() 246 | 247 | for entry := range taken.Enumerate(ctx) { 248 | fmt.Println(entry) 249 | } 250 | // Output: 251 | // 0 252 | // 1 253 | // 1 254 | // 2 255 | // 3 256 | // 5 257 | // 8 258 | } 259 | 260 | func ExampleEnumerator_TakeWhile() { 261 | ctx, cancel := context.WithCancel(context.Background()) 262 | defer cancel() 263 | taken := collection.Fibonacci.Enumerate(ctx).TakeWhile(func(x, n uint) bool { 264 | return x < 6 265 | }) 266 | for entry := range taken { 267 | fmt.Println(entry) 268 | } 269 | // Output: 270 | // 0 271 | // 1 272 | // 1 273 | // 2 274 | // 3 275 | // 5 276 | } 277 | 278 | func ExampleEnumerator_Tee() { 279 | ctx, cancel := context.WithCancel(context.Background()) 280 | defer cancel() 281 | 282 | base := collection.AsEnumerable(1, 2, 4) 283 | left, right := base.Enumerate(ctx).Tee() 284 | var wg sync.WaitGroup 285 | wg.Add(2) 286 | 287 | product := 1 288 | go func() { 289 | for x := range left { 290 | product *= x 291 | } 292 | wg.Done() 293 | }() 294 | 295 | sum := 0 296 | go func() { 297 | for x := range right { 298 | sum += x 299 | } 300 | wg.Done() 301 | }() 302 | 303 | wg.Wait() 304 | 305 | fmt.Printf("Sum: %d\n", sum) 306 | fmt.Printf("Product: %d\n", product) 307 | // Output: 308 | // Sum: 7 309 | // Product: 8 310 | } 311 | 312 | func ExampleUCount() { 313 | subject := collection.NewStack[any](9, 'a', "str1") 314 | result := collection.UCount[interface{}](subject, func(a interface{}) bool { 315 | _, ok := a.(string) 316 | return ok 317 | }) 318 | fmt.Println(result) 319 | // Output: 1 320 | } 321 | 322 | func ExampleEnumerator_UCount() { 323 | subject := collection.EnumerableSlice[string]([]string{"str1", "str1", "str2"}) 324 | count1 := subject.Enumerate(context.Background()).UCount(func(a string) bool { 325 | return a == "str1" 326 | }) 327 | fmt.Println(count1) 328 | // Output: 2 329 | } 330 | 331 | func ExampleUCountAll() { 332 | subject := collection.NewStack(8, 9, 10, 11) 333 | fmt.Println(collection.UCountAll[int](subject)) 334 | // Output: 4 335 | } 336 | 337 | func ExampleEnumerator_UCountAll() { 338 | subject := collection.EnumerableSlice[any]([]interface{}{'a', 2, "str1"}) 339 | fmt.Println(subject.Enumerate(context.Background()).UCountAll()) 340 | // Output: 3 341 | } 342 | 343 | func ExampleEnumerator_Where() { 344 | ctx, cancel := context.WithCancel(context.Background()) 345 | defer cancel() 346 | 347 | results := collection.Fibonacci.Enumerate(ctx).Where(func(a uint) bool { 348 | return a > 8 349 | }).Take(3) 350 | fmt.Println(results.ToSlice()) 351 | // Output: [13 21 34] 352 | } 353 | 354 | func ExampleWhere() { 355 | nums := collection.EnumerableSlice[int]([]int{1, 2, 3, 4, 5}) 356 | results := collection.Where[int](nums, func(a int) bool { 357 | return a < 3 358 | }) 359 | fmt.Println(collection.ToSlice(results)) 360 | // Output: [1 2] 361 | } 362 | -------------------------------------------------------------------------------- /linkedlist.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "sync" 9 | ) 10 | 11 | // LinkedList encapsulates a list where each entry is aware of only the next entry in the list. 12 | type LinkedList[T any] struct { 13 | first *llNode[T] 14 | last *llNode[T] 15 | length uint 16 | key sync.RWMutex 17 | } 18 | 19 | type llNode[T any] struct { 20 | payload T 21 | next *llNode[T] 22 | prev *llNode[T] 23 | } 24 | 25 | // Comparator is a function which evaluates two values to determine their relation to one another. 26 | // - Zero is returned when `a` and `b` are equal. 27 | // - Positive numbers are returned when `a` is greater than `b`. 28 | // - Negative numbers are returned when `a` is less than `b`. 29 | type Comparator[T any] func(a, b T) (int, error) 30 | 31 | // A collection of errors that may be thrown by functions in this file. 32 | var ( 33 | ErrUnexpectedType = errors.New("value was of an unexpected type") 34 | ) 35 | 36 | // NewLinkedList instantiates a new LinkedList with the entries provided. 37 | func NewLinkedList[T any](entries ...T) *LinkedList[T] { 38 | list := &LinkedList[T]{} 39 | 40 | for _, entry := range entries { 41 | list.AddBack(entry) 42 | } 43 | 44 | return list 45 | } 46 | 47 | // AddBack creates an entry in the LinkedList that is logically at the back of the list. 48 | func (list *LinkedList[T]) AddBack(entry T) { 49 | list.key.Lock() 50 | defer list.key.Unlock() 51 | 52 | toAppend := &llNode[T]{ 53 | payload: entry, 54 | } 55 | 56 | list.addNodeBack(toAppend) 57 | } 58 | 59 | func (list *LinkedList[T]) addNodeBack(node *llNode[T]) { 60 | 61 | list.length++ 62 | 63 | node.prev = list.last 64 | 65 | if list.first == nil { 66 | list.first = node 67 | list.last = node 68 | return 69 | } 70 | 71 | list.last.next = node 72 | list.last = node 73 | } 74 | 75 | // AddFront creates an entry in the LinkedList that is logically at the front of the list. 76 | func (list *LinkedList[T]) AddFront(entry T) { 77 | toAppend := &llNode[T]{ 78 | payload: entry, 79 | } 80 | 81 | list.key.Lock() 82 | defer list.key.Unlock() 83 | 84 | list.addNodeFront(toAppend) 85 | } 86 | 87 | func (list *LinkedList[T]) addNodeFront(node *llNode[T]) { 88 | list.length++ 89 | 90 | node.next = list.first 91 | if list.first == nil { 92 | list.last = node 93 | } else { 94 | list.first.prev = node 95 | } 96 | 97 | list.first = node 98 | } 99 | 100 | // Enumerate creates a new instance of Enumerable which can be executed on. 101 | func (list *LinkedList[T]) Enumerate(ctx context.Context) Enumerator[T] { 102 | retval := make(chan T) 103 | 104 | go func() { 105 | list.key.RLock() 106 | defer list.key.RUnlock() 107 | defer close(retval) 108 | 109 | current := list.first 110 | for current != nil { 111 | select { 112 | case retval <- current.payload: 113 | // Intentionally Left Blank 114 | case <-ctx.Done(): 115 | return 116 | } 117 | current = current.next 118 | } 119 | }() 120 | 121 | return retval 122 | } 123 | 124 | // Get finds the value from the LinkedList. 125 | // pos is expressed as a zero-based index begining from the 'front' of the list. 126 | func (list *LinkedList[T]) Get(pos uint) (T, bool) { 127 | list.key.RLock() 128 | defer list.key.RUnlock() 129 | node, ok := get(list.first, pos) 130 | if ok { 131 | return node.payload, true 132 | } 133 | return *new(T), false 134 | } 135 | 136 | // IsEmpty tests the list to determine if it is populate or not. 137 | func (list *LinkedList[T]) IsEmpty() bool { 138 | list.key.RLock() 139 | defer list.key.RUnlock() 140 | 141 | return list.first == nil 142 | } 143 | 144 | // Length returns the number of elements present in the LinkedList. 145 | func (list *LinkedList[T]) Length() uint { 146 | list.key.RLock() 147 | defer list.key.RUnlock() 148 | 149 | return list.length 150 | } 151 | 152 | // PeekBack returns the entry logicall stored at the back of the list without removing it. 153 | func (list *LinkedList[T]) PeekBack() (T, bool) { 154 | list.key.RLock() 155 | defer list.key.RUnlock() 156 | 157 | if list.last == nil { 158 | return *new(T), false 159 | } 160 | return list.last.payload, true 161 | } 162 | 163 | // PeekFront returns the entry logically stored at the front of this list without removing it. 164 | func (list *LinkedList[T]) PeekFront() (T, bool) { 165 | list.key.RLock() 166 | defer list.key.RUnlock() 167 | 168 | if list.first == nil { 169 | return *new(T), false 170 | } 171 | return list.first.payload, true 172 | } 173 | 174 | // RemoveFront returns the entry logically stored at the front of this list and removes it. 175 | func (list *LinkedList[T]) RemoveFront() (T, bool) { 176 | list.key.Lock() 177 | defer list.key.Unlock() 178 | 179 | if list.first == nil { 180 | return *new(T), false 181 | } 182 | 183 | retval := list.first.payload 184 | 185 | list.first = list.first.next 186 | list.length-- 187 | 188 | if list.length == 0 { 189 | list.last = nil 190 | } 191 | 192 | return retval, true 193 | } 194 | 195 | // RemoveBack returns the entry logically stored at the back of this list and removes it. 196 | func (list *LinkedList[T]) RemoveBack() (T, bool) { 197 | list.key.Lock() 198 | defer list.key.Unlock() 199 | 200 | if list.last == nil { 201 | return *new(T), false 202 | } 203 | 204 | retval := list.last.payload 205 | list.length-- 206 | 207 | if list.length == 0 { 208 | list.first = nil 209 | } else { 210 | list.last = list.last.prev 211 | list.last.next = nil 212 | } 213 | return retval, true 214 | } 215 | 216 | // removeNode 217 | func (list *LinkedList[T]) removeNode(target *llNode[T]) { 218 | if target == nil { 219 | return 220 | } 221 | 222 | if target.next != nil { 223 | target.next.prev = target.prev 224 | } 225 | 226 | if target.prev != nil { 227 | target.prev.next = target.next 228 | } 229 | 230 | list.length-- 231 | 232 | if list.length == 0 { 233 | list.first = nil 234 | list.last = nil 235 | return 236 | } 237 | 238 | if list.first == target { 239 | list.first = target.next 240 | } 241 | 242 | if list.last == target { 243 | list.last = target.prev 244 | } 245 | } 246 | 247 | // Sort rearranges the positions of the entries in this list so that they are 248 | // ascending. 249 | func (list *LinkedList[T]) Sort(comparator Comparator[T]) error { 250 | list.key.Lock() 251 | defer list.key.Unlock() 252 | var err error 253 | list.first, err = mergeSort(list.first, comparator) 254 | if err != nil { 255 | return err 256 | } 257 | list.last = findLast(list.first) 258 | return err 259 | } 260 | 261 | // String prints upto the first fifteen elements of the list in string format. 262 | func (list *LinkedList[T]) String() string { 263 | list.key.RLock() 264 | defer list.key.RUnlock() 265 | 266 | builder := bytes.NewBufferString("[") 267 | current := list.first 268 | for i := 0; i < 15 && current != nil; i++ { 269 | builder.WriteString(fmt.Sprintf("%v ", current.payload)) 270 | current = current.next 271 | } 272 | if current == nil || current.next == nil { 273 | builder.Truncate(builder.Len() - 1) 274 | } else { 275 | builder.WriteString("...") 276 | } 277 | builder.WriteRune(']') 278 | return builder.String() 279 | } 280 | 281 | // Swap switches the positions in which two values are stored in this list. 282 | // x and y represent the indexes of the items that should be swapped. 283 | func (list *LinkedList[T]) Swap(x, y uint) error { 284 | list.key.Lock() 285 | defer list.key.Unlock() 286 | 287 | var xNode, yNode *llNode[T] 288 | if temp, ok := get(list.first, x); ok { 289 | xNode = temp 290 | } else { 291 | return fmt.Errorf("index out of bounds 'x', wanted less than %d got %d", list.length, x) 292 | } 293 | if temp, ok := get(list.first, y); ok { 294 | yNode = temp 295 | } else { 296 | return fmt.Errorf("index out of bounds 'y', wanted less than %d got %d", list.length, y) 297 | } 298 | 299 | temp := xNode.payload 300 | xNode.payload = yNode.payload 301 | yNode.payload = temp 302 | return nil 303 | } 304 | 305 | // ToSlice converts the contents of the LinkedList into a slice. 306 | func (list *LinkedList[T]) ToSlice() []T { 307 | return list.Enumerate(context.Background()).ToSlice() 308 | } 309 | 310 | func findLast[T any](head *llNode[T]) *llNode[T] { 311 | if head == nil { 312 | return nil 313 | } 314 | current := head 315 | for current.next != nil { 316 | current = current.next 317 | } 318 | return current 319 | } 320 | 321 | func get[T any](head *llNode[T], pos uint) (*llNode[T], bool) { 322 | for i := uint(0); i < pos; i++ { 323 | if head == nil { 324 | return nil, false 325 | } 326 | head = head.next 327 | } 328 | return head, true 329 | } 330 | 331 | // merge takes two sorted lists and merges them into one sorted list. 332 | // Behavior is undefined when you pass a non-sorted list as `left` or `right` 333 | func merge[T any](left, right *llNode[T], comparator Comparator[T]) (first *llNode[T], err error) { 334 | curLeft := left 335 | curRight := right 336 | 337 | var last *llNode[T] 338 | 339 | appendResults := func(updated *llNode[T]) { 340 | if last == nil { 341 | last = updated 342 | } else { 343 | last.next = updated 344 | last = last.next 345 | } 346 | if first == nil { 347 | first = last 348 | } 349 | } 350 | 351 | for curLeft != nil && curRight != nil { 352 | var res int 353 | if res, err = comparator(curLeft.payload, curRight.payload); nil != err { 354 | break // Don't return, stitch the remaining elements back on. 355 | } else if res < 0 { 356 | appendResults(curLeft) 357 | curLeft = curLeft.next 358 | } else { 359 | appendResults(curRight) 360 | curRight = curRight.next 361 | } 362 | } 363 | 364 | if curLeft != nil { 365 | appendResults(curLeft) 366 | } 367 | if curRight != nil { 368 | appendResults(curRight) 369 | } 370 | return 371 | } 372 | 373 | func mergeSort[T any](head *llNode[T], comparator Comparator[T]) (*llNode[T], error) { 374 | if head == nil { 375 | return nil, nil 376 | } 377 | 378 | left, right := split(head) 379 | 380 | repair := func(left, right *llNode[T]) *llNode[T] { 381 | lastLeft := findLast(left) 382 | lastLeft.next = right 383 | return left 384 | } 385 | 386 | var err error 387 | if left != nil && left.next != nil { 388 | left, err = mergeSort(left, comparator) 389 | if err != nil { 390 | return repair(left, right), err 391 | } 392 | } 393 | if right != nil && right.next != nil { 394 | right, err = mergeSort(right, comparator) 395 | if err != nil { 396 | return repair(left, right), err 397 | } 398 | } 399 | 400 | return merge(left, right, comparator) 401 | } 402 | 403 | // split breaks a list in half. 404 | func split[T any](head *llNode[T]) (left, right *llNode[T]) { 405 | left = head 406 | if head == nil || head.next == nil { 407 | return 408 | } 409 | right = head 410 | sprinter := head 411 | prev := head 412 | for sprinter != nil && sprinter.next != nil { 413 | prev = right 414 | right = right.next 415 | sprinter = sprinter.next.next 416 | } 417 | prev.next = nil 418 | return 419 | } 420 | -------------------------------------------------------------------------------- /query.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "runtime" 7 | "sync" 8 | ) 9 | 10 | // Enumerable offers a means of easily converting into a channel. It is most 11 | // useful for types where mutability is not in question. 12 | type Enumerable[T any] interface { 13 | Enumerate(ctx context.Context) Enumerator[T] 14 | } 15 | 16 | // Enumerator exposes a new syntax for querying familiar data structures. 17 | type Enumerator[T any] <-chan T 18 | 19 | // Predicate defines an interface for funcs that make some logical test. 20 | type Predicate[T any] func(T) bool 21 | 22 | // Transform defines a function which takes a value, and returns some value based on the original. 23 | type Transform[T any, E any] func(T) E 24 | 25 | // Unfolder defines a function which takes a single value, and exposes many of them as an Enumerator 26 | type Unfolder[T any, E any] func(T) Enumerator[E] 27 | 28 | type emptyEnumerable[T any] struct{} 29 | 30 | var ( 31 | errNoElements = errors.New("enumerator encountered no elements") 32 | errMultipleElements = errors.New("enumerator encountered multiple elements") 33 | ) 34 | 35 | // IsErrorNoElements determines whethr or not the given error is the result of no values being 36 | // returned when one or more were expected. 37 | func IsErrorNoElements(err error) bool { 38 | return err == errNoElements 39 | } 40 | 41 | // IsErrorMultipleElements determines whether or not the given error is the result of multiple values 42 | // being returned when one or zero were expected. 43 | func IsErrorMultipleElements(err error) bool { 44 | return err == errMultipleElements 45 | } 46 | 47 | // Identity returns a trivial Transform which applies no operation on the value. 48 | func Identity[T any]() Transform[T, T] { 49 | return func(value T) T { 50 | return value 51 | } 52 | } 53 | 54 | func Empty[T any]() Enumerable[T] { 55 | return &emptyEnumerable[T]{} 56 | } 57 | 58 | func (e emptyEnumerable[T]) Enumerate(ctx context.Context) Enumerator[T] { 59 | results := make(chan T) 60 | close(results) 61 | return results 62 | } 63 | 64 | // All tests whether or not all items present in an Enumerable meet a criteria. 65 | func All[T any](subject Enumerable[T], p Predicate[T]) bool { 66 | ctx, cancel := context.WithCancel(context.Background()) 67 | defer cancel() 68 | 69 | return subject.Enumerate(ctx).All(p) 70 | } 71 | 72 | // All tests whether or not all items present meet a criteria. 73 | func (iter Enumerator[T]) All(p Predicate[T]) bool { 74 | for entry := range iter { 75 | if !p(entry) { 76 | return false 77 | } 78 | } 79 | return true 80 | } 81 | 82 | // Any tests an Enumerable to see if there are any elements present. 83 | func Any[T any](iterator Enumerable[T]) bool { 84 | ctx, cancel := context.WithCancel(context.Background()) 85 | defer cancel() 86 | 87 | for range iterator.Enumerate(ctx) { 88 | return true 89 | } 90 | return false 91 | } 92 | 93 | // Anyp tests an Enumerable to see if there are any elements present that meet a criteria. 94 | func Anyp[T any](iterator Enumerable[T], p Predicate[T]) bool { 95 | ctx, cancel := context.WithCancel(context.Background()) 96 | defer cancel() 97 | 98 | for element := range iterator.Enumerate(ctx) { 99 | if p(element) { 100 | return true 101 | } 102 | } 103 | return false 104 | } 105 | 106 | type EnumerableSlice[T any] []T 107 | 108 | func (f EnumerableSlice[T]) Enumerate(ctx context.Context) Enumerator[T] { 109 | results := make(chan T) 110 | 111 | go func() { 112 | defer close(results) 113 | for _, entry := range f { 114 | select { 115 | case results <- entry: 116 | // Intentionally Left Blank 117 | case <-ctx.Done(): 118 | return 119 | } 120 | } 121 | }() 122 | 123 | return results 124 | } 125 | 126 | // AsEnumerable allows for easy conversion of a slice to a re-usable Enumerable object. 127 | func AsEnumerable[T any](entries ...T) Enumerable[T] { 128 | return EnumerableSlice[T](entries) 129 | } 130 | 131 | // AsEnumerable stores the results of an Enumerator so the results can be enumerated over repeatedly. 132 | func (iter Enumerator[T]) AsEnumerable() Enumerable[T] { 133 | return EnumerableSlice[T](iter.ToSlice()) 134 | } 135 | 136 | // Count iterates over a list and keeps a running tally of the number of elements which satisfy a predicate. 137 | func Count[T any](iter Enumerable[T], p Predicate[T]) int { 138 | return iter.Enumerate(context.Background()).Count(p) 139 | } 140 | 141 | // Count iterates over a list and keeps a running tally of the number of elements 142 | // satisfy a predicate. 143 | func (iter Enumerator[T]) Count(p Predicate[T]) int { 144 | tally := 0 145 | for entry := range iter { 146 | if p(entry) { 147 | tally++ 148 | } 149 | } 150 | return tally 151 | } 152 | 153 | // CountAll iterates over a list and keeps a running tally of how many it's seen. 154 | func CountAll[T any](iter Enumerable[T]) int { 155 | return iter.Enumerate(context.Background()).CountAll() 156 | } 157 | 158 | // CountAll iterates over a list and keeps a running tally of how many it's seen. 159 | func (iter Enumerator[T]) CountAll() int { 160 | tally := 0 161 | for range iter { 162 | tally++ 163 | } 164 | return tally 165 | } 166 | 167 | // Discard reads an enumerator to the end but does nothing with it. 168 | // This method should be used in circumstances when it doesn't make sense to explicitly cancel the Enumeration. 169 | func (iter Enumerator[T]) Discard() { 170 | for range iter { 171 | // Intentionally Left Blank 172 | } 173 | } 174 | 175 | // ElementAt retreives an item at a particular position in an Enumerator. 176 | func ElementAt[T any](iter Enumerable[T], n uint) T { 177 | ctx, cancel := context.WithCancel(context.Background()) 178 | defer cancel() 179 | 180 | return iter.Enumerate(ctx).ElementAt(n) 181 | } 182 | 183 | // ElementAt retreives an item at a particular position in an Enumerator. 184 | func (iter Enumerator[T]) ElementAt(n uint) T { 185 | for i := uint(0); i < n; i++ { 186 | <-iter 187 | } 188 | return <-iter 189 | } 190 | 191 | // First retrieves just the first item in the list, or returns an error if there are no elements in the array. 192 | func First[T any](subject Enumerable[T]) (retval T, err error) { 193 | ctx, cancel := context.WithCancel(context.Background()) 194 | defer cancel() 195 | 196 | err = errNoElements 197 | var isOpen bool 198 | 199 | if retval, isOpen = <-subject.Enumerate(ctx); isOpen { 200 | err = nil 201 | } 202 | 203 | return 204 | } 205 | 206 | // Last retreives the item logically behind all other elements in the list. 207 | func Last[T any](iter Enumerable[T]) T { 208 | return iter.Enumerate(context.Background()).Last() 209 | } 210 | 211 | // Last retreives the item logically behind all other elements in the list. 212 | func (iter Enumerator[T]) Last() (retval T) { 213 | for retval = range iter { 214 | // Intentionally Left Blank 215 | } 216 | return 217 | } 218 | 219 | type merger[T any] struct { 220 | originals []Enumerable[T] 221 | } 222 | 223 | func (m merger[T]) Enumerate(ctx context.Context) Enumerator[T] { 224 | retval := make(chan T) 225 | 226 | var wg sync.WaitGroup 227 | wg.Add(len(m.originals)) 228 | for _, item := range m.originals { 229 | go func(input Enumerable[T]) { 230 | defer wg.Done() 231 | for value := range input.Enumerate(ctx) { 232 | retval <- value 233 | } 234 | }(item) 235 | } 236 | 237 | go func() { 238 | wg.Wait() 239 | close(retval) 240 | }() 241 | return retval 242 | } 243 | 244 | // Merge takes the results as it receives them from several channels and directs 245 | // them into a single channel. 246 | func Merge[T any](channels ...Enumerable[T]) Enumerable[T] { 247 | return merger[T]{ 248 | originals: channels, 249 | } 250 | } 251 | 252 | // Merge takes the results of this Enumerator and others, and funnels them into 253 | // a single Enumerator. The order of in which they will be combined is non-deterministic. 254 | func (iter Enumerator[T]) Merge(others ...Enumerator[T]) Enumerator[T] { 255 | retval := make(chan T) 256 | 257 | var wg sync.WaitGroup 258 | wg.Add(len(others) + 1) 259 | 260 | funnel := func(prevResult Enumerator[T]) { 261 | for entry := range prevResult { 262 | retval <- entry 263 | } 264 | wg.Done() 265 | } 266 | 267 | go funnel(iter) 268 | for _, item := range others { 269 | go funnel(item) 270 | } 271 | 272 | go func() { 273 | wg.Wait() 274 | close(retval) 275 | }() 276 | return retval 277 | } 278 | 279 | type parallelSelecter[T any, E any] struct { 280 | original Enumerable[T] 281 | operation Transform[T, E] 282 | } 283 | 284 | func (ps parallelSelecter[T, E]) Enumerate(ctx context.Context) Enumerator[E] { 285 | iter := ps.original.Enumerate(ctx) 286 | if cpus := runtime.NumCPU(); cpus != 1 { 287 | intermediate := splitN(iter, ps.operation, uint(cpus)) 288 | return intermediate[0].Merge(intermediate[1:]...) 289 | } 290 | 291 | return Select(ps.original, ps.operation).Enumerate(ctx) 292 | } 293 | 294 | // ParallelSelect creates an Enumerable which will use all logically available CPUs to 295 | // execute a Transform. 296 | func ParallelSelect[T any, E any](original Enumerable[T], operation Transform[T, E]) Enumerable[E] { 297 | return parallelSelecter[T, E]{ 298 | original: original, 299 | operation: operation, 300 | } 301 | } 302 | 303 | // ParallelSelect will execute a Transform across all logical CPUs available to the current process. 304 | // 305 | // This is commented out, because Go 1.18 adds support for generics, but disallows methods from having type parameters 306 | // not declared by their receivers. 307 | // 308 | //func (iter Enumerator[T]) ParallelSelect[E any](operation Transform[T, E]) Enumerator[E] { 309 | // if cpus := runtime.NumCPU(); cpus != 1 { 310 | // intermediate := iter.splitN(operation, uint(cpus)) 311 | // return intermediate[0].Merge(intermediate[1:]...) 312 | // } 313 | // return iter 314 | //} 315 | 316 | type reverser[T any] struct { 317 | original Enumerable[T] 318 | } 319 | 320 | // Reverse will enumerate all values of an enumerable, store them in a Stack, then replay them all. 321 | func Reverse[T any](original Enumerable[T]) Enumerable[T] { 322 | return reverser[T]{ 323 | original: original, 324 | } 325 | } 326 | 327 | func (r reverser[T]) Enumerate(ctx context.Context) Enumerator[T] { 328 | return r.original.Enumerate(ctx).Reverse() 329 | } 330 | 331 | // Reverse returns items in the opposite order it encountered them in. 332 | func (iter Enumerator[T]) Reverse() Enumerator[T] { 333 | cache := NewStack[T]() 334 | for entry := range iter { 335 | cache.Push(entry) 336 | } 337 | 338 | retval := make(chan T) 339 | 340 | go func() { 341 | for !cache.IsEmpty() { 342 | val, _ := cache.Pop() 343 | retval <- val 344 | } 345 | close(retval) 346 | }() 347 | return retval 348 | } 349 | 350 | type selecter[T any, E any] struct { 351 | original Enumerable[T] 352 | transform Transform[T, E] 353 | } 354 | 355 | func (s selecter[T, E]) Enumerate(ctx context.Context) Enumerator[E] { 356 | retval := make(chan E) 357 | 358 | go func() { 359 | defer close(retval) 360 | 361 | for item := range s.original.Enumerate(ctx) { 362 | select { 363 | case retval <- s.transform(item): 364 | // Intentionally Left Blank 365 | case <-ctx.Done(): 366 | return 367 | } 368 | } 369 | }() 370 | 371 | return retval 372 | } 373 | 374 | // Select creates a reusable stream of transformed values. 375 | func Select[T any, E any](subject Enumerable[T], transform Transform[T, E]) Enumerable[E] { 376 | return selecter[T, E]{ 377 | original: subject, 378 | transform: transform, 379 | } 380 | } 381 | 382 | // Select iterates over a list and returns a transformed item. 383 | // 384 | // This is commented out because Go 1.18 added support for 385 | // 386 | //func (iter Enumerator[T]) Select[E any](transform Transform[T, E]) Enumerator[E] { 387 | // retval := make(chan interface{}) 388 | // 389 | // go func() { 390 | // for item := range iter { 391 | // retval <- transform(item) 392 | // } 393 | // close(retval) 394 | // }() 395 | // 396 | // return retval 397 | //} 398 | 399 | type selectManyer[T any, E any] struct { 400 | original Enumerable[T] 401 | toMany Unfolder[T, E] 402 | } 403 | 404 | func (s selectManyer[T, E]) Enumerate(ctx context.Context) Enumerator[E] { 405 | retval := make(chan E) 406 | 407 | go func() { 408 | for parent := range s.original.Enumerate(ctx) { 409 | for child := range s.toMany(parent) { 410 | retval <- child 411 | } 412 | } 413 | close(retval) 414 | }() 415 | return retval 416 | } 417 | 418 | // SelectMany allows for unfolding of values. 419 | func SelectMany[T any, E any](subject Enumerable[T], toMany Unfolder[T, E]) Enumerable[E] { 420 | return selectManyer[T, E]{ 421 | original: subject, 422 | toMany: toMany, 423 | } 424 | } 425 | 426 | //// SelectMany allows for flattening of data structures. 427 | //func (iter Enumerator[T]) SelectMany[E any](lister Unfolder[T, E]) Enumerator[E] { 428 | // retval := make(chan E) 429 | // 430 | // go func() { 431 | // for parent := range iter { 432 | // for child := range lister(parent) { 433 | // retval <- child 434 | // } 435 | // } 436 | // close(retval) 437 | // }() 438 | // 439 | // return retval 440 | //} 441 | 442 | // Single retreives the only element from a list, or returns nil and an error. 443 | func Single[T any](iter Enumerable[T]) (retval T, err error) { 444 | ctx, cancel := context.WithCancel(context.Background()) 445 | defer cancel() 446 | err = errNoElements 447 | 448 | firstPass := true 449 | for entry := range iter.Enumerate(ctx) { 450 | if firstPass { 451 | retval = entry 452 | err = nil 453 | } else { 454 | retval = *new(T) 455 | err = errMultipleElements 456 | break 457 | } 458 | firstPass = false 459 | } 460 | return 461 | } 462 | 463 | // Singlep retrieces the only element from a list that matches a criteria. If 464 | // no match is found, or two or more are found, `Singlep` returns nil and an 465 | // error. 466 | func Singlep[T any](iter Enumerable[T], pred Predicate[T]) (retval T, err error) { 467 | iter = Where(iter, pred) 468 | return Single(iter) 469 | } 470 | 471 | type skipper[T any] struct { 472 | original Enumerable[T] 473 | skipCount uint 474 | } 475 | 476 | func (s skipper[T]) Enumerate(ctx context.Context) Enumerator[T] { 477 | return s.original.Enumerate(ctx).Skip(s.skipCount) 478 | } 479 | 480 | // Skip creates a reusable stream which will skip the first `n` elements before iterating 481 | // over the rest of the elements in an Enumerable. 482 | func Skip[T any](subject Enumerable[T], n uint) Enumerable[T] { 483 | return skipper[T]{ 484 | original: subject, 485 | skipCount: n, 486 | } 487 | } 488 | 489 | // Skip retreives all elements after the first 'n' elements. 490 | func (iter Enumerator[T]) Skip(n uint) Enumerator[T] { 491 | results := make(chan T) 492 | 493 | go func() { 494 | defer close(results) 495 | 496 | i := uint(0) 497 | for entry := range iter { 498 | if i < n { 499 | i++ 500 | continue 501 | } 502 | results <- entry 503 | } 504 | }() 505 | 506 | return results 507 | } 508 | 509 | // splitN creates N Enumerators, each will be a subset of the original Enumerator and will have 510 | // distinct populations from one another. 511 | func splitN[T any, E any](iter Enumerator[T], operation Transform[T, E], n uint) []Enumerator[E] { 512 | results, cast := make([]chan E, n), make([]Enumerator[E], n) 513 | 514 | for i := uint(0); i < n; i++ { 515 | results[i] = make(chan E) 516 | cast[i] = results[i] 517 | } 518 | 519 | go func() { 520 | for i := uint(0); i < n; i++ { 521 | go func(addr uint) { 522 | defer close(results[addr]) 523 | for { 524 | read, ok := <-iter 525 | if !ok { 526 | return 527 | } 528 | results[addr] <- operation(read) 529 | } 530 | }(i) 531 | } 532 | }() 533 | 534 | return cast 535 | } 536 | 537 | type taker[T any] struct { 538 | original Enumerable[T] 539 | n uint 540 | } 541 | 542 | func (t taker[T]) Enumerate(ctx context.Context) Enumerator[T] { 543 | return t.original.Enumerate(ctx).Take(t.n) 544 | } 545 | 546 | // Take retreives just the first `n` elements from an Enumerable. 547 | func Take[T any](subject Enumerable[T], n uint) Enumerable[T] { 548 | return taker[T]{ 549 | original: subject, 550 | n: n, 551 | } 552 | } 553 | 554 | // Take retreives just the first 'n' elements from an Enumerator. 555 | func (iter Enumerator[T]) Take(n uint) Enumerator[T] { 556 | results := make(chan T) 557 | 558 | go func() { 559 | defer close(results) 560 | i := uint(0) 561 | for entry := range iter { 562 | if i >= n { 563 | return 564 | } 565 | i++ 566 | results <- entry 567 | } 568 | }() 569 | 570 | return results 571 | } 572 | 573 | type takeWhiler[T any] struct { 574 | original Enumerable[T] 575 | criteria func(T, uint) bool 576 | } 577 | 578 | func (tw takeWhiler[T]) Enumerate(ctx context.Context) Enumerator[T] { 579 | return tw.original.Enumerate(ctx).TakeWhile(tw.criteria) 580 | } 581 | 582 | // TakeWhile creates a reusable stream which will halt once some criteria is no longer met. 583 | func TakeWhile[T any](subject Enumerable[T], criteria func(T, uint) bool) Enumerable[T] { 584 | return takeWhiler[T]{ 585 | original: subject, 586 | criteria: criteria, 587 | } 588 | } 589 | 590 | // TakeWhile continues returning items as long as 'criteria' holds true. 591 | func (iter Enumerator[T]) TakeWhile(criteria func(T, uint) bool) Enumerator[T] { 592 | results := make(chan T) 593 | 594 | go func() { 595 | defer close(results) 596 | i := uint(0) 597 | for entry := range iter { 598 | if !criteria(entry, i) { 599 | return 600 | } 601 | i++ 602 | results <- entry 603 | } 604 | }() 605 | 606 | return results 607 | } 608 | 609 | // Tee creates two Enumerators which will have identical contents as one another. 610 | func (iter Enumerator[T]) Tee() (Enumerator[T], Enumerator[T]) { 611 | left, right := make(chan T), make(chan T) 612 | 613 | go func() { 614 | for entry := range iter { 615 | left <- entry 616 | right <- entry 617 | } 618 | close(left) 619 | close(right) 620 | }() 621 | 622 | return left, right 623 | } 624 | 625 | // ToSlice places all iterated over values in a Slice for easy consumption. 626 | func ToSlice[T any](iter Enumerable[T]) []T { 627 | return iter.Enumerate(context.Background()).ToSlice() 628 | } 629 | 630 | // ToSlice places all iterated over values in a Slice for easy consumption. 631 | func (iter Enumerator[T]) ToSlice() []T { 632 | retval := make([]T, 0) 633 | for entry := range iter { 634 | retval = append(retval, entry) 635 | } 636 | return retval 637 | } 638 | 639 | type wherer[T any] struct { 640 | original Enumerable[T] 641 | filter Predicate[T] 642 | } 643 | 644 | func (w wherer[T]) Enumerate(ctx context.Context) Enumerator[T] { 645 | retval := make(chan T) 646 | 647 | go func() { 648 | defer close(retval) 649 | for entry := range w.original.Enumerate(ctx) { 650 | if w.filter(entry) { 651 | retval <- entry 652 | } 653 | } 654 | }() 655 | 656 | return retval 657 | } 658 | 659 | // Where creates a reusable means of filtering a stream. 660 | func Where[T any](original Enumerable[T], p Predicate[T]) Enumerable[T] { 661 | return wherer[T]{ 662 | original: original, 663 | filter: p, 664 | } 665 | } 666 | 667 | // Where iterates over a list and returns only the elements that satisfy a 668 | // predicate. 669 | func (iter Enumerator[T]) Where(predicate Predicate[T]) Enumerator[T] { 670 | retval := make(chan T) 671 | go func() { 672 | for item := range iter { 673 | if predicate(item) { 674 | retval <- item 675 | } 676 | } 677 | close(retval) 678 | }() 679 | 680 | return retval 681 | } 682 | 683 | // UCount iterates over a list and keeps a running tally of the number of elements 684 | // satisfy a predicate. 685 | func UCount[T any](iter Enumerable[T], p Predicate[T]) uint { 686 | return iter.Enumerate(context.Background()).UCount(p) 687 | } 688 | 689 | // UCount iterates over a list and keeps a running tally of the number of elements 690 | // satisfy a predicate. 691 | func (iter Enumerator[T]) UCount(p Predicate[T]) uint { 692 | tally := uint(0) 693 | for entry := range iter { 694 | if p(entry) { 695 | tally++ 696 | } 697 | } 698 | return tally 699 | } 700 | 701 | // UCountAll iterates over a list and keeps a running tally of how many it's seen. 702 | func UCountAll[T any](iter Enumerable[T]) uint { 703 | return iter.Enumerate(context.Background()).UCountAll() 704 | } 705 | 706 | // UCountAll iterates over a list and keeps a running tally of how many it's seen. 707 | func (iter Enumerator[T]) UCountAll() uint { 708 | tally := uint(0) 709 | for range iter { 710 | tally++ 711 | } 712 | return tally 713 | } 714 | --------------------------------------------------------------------------------