pax_global_header00006660000000000000000000000064150314300250014503gustar00rootroot0000000000000052 comment=2019a12b5b81014a603a447dc4be6e0057dc9565 collection-2.0.0/000077500000000000000000000000001503143002500136355ustar00rootroot00000000000000collection-2.0.0/.github/000077500000000000000000000000001503143002500151755ustar00rootroot00000000000000collection-2.0.0/.github/workflows/000077500000000000000000000000001503143002500172325ustar00rootroot00000000000000collection-2.0.0/.github/workflows/build_and_test.yml000066400000000000000000000013411503143002500227340ustar00rootroot00000000000000name: Build and Test on: workflow_dispatch: push: branches: [ main, v1 ] pull_request: branches: [ main, v1 ] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: stable: false go-version: ^1.18.0-beta1 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | go get -v -t -d ./... if [ -f Gopkg.toml ]; then curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure fi - name: Build run: go build -v . - name: Test run: go test -v . collection-2.0.0/.gitignore000066400000000000000000000004121503143002500156220ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof collection-2.0.0/.travis.yml000066400000000000000000000004651503143002500157530ustar00rootroot00000000000000language: go go: - 1.11 - 1.x env: - GO111MODULE=on matrix: fast_finish: true install: - go get -u golang.org/x/lint/golint script: - go test -v -cover - test -z "$(golint | tee /dev/stderr)" - test -z "$(go vet | tee /dev/stderr | grep error)" - test -z "$(gofmt -l *.go | tee /dev/stderr)"collection-2.0.0/LICENSE000066400000000000000000000020571503143002500146460ustar00rootroot00000000000000MIT License Copyright (c) 2017 Martin Strobel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. collection-2.0.0/README.md000066400000000000000000000132151503143002500151160ustar00rootroot00000000000000# collection [![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") # Usage ## Available Data Structures: ### Dictionary This is a logical set of strings. It utilizes a prefix tree model to be very space efficient. ### LinkedList 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. ### List 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`. ### LRUCache 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. ### Queue Stores items without promising random access. The first thing you put in will be the first thing you get out. ### Stack Stores items without promising random access. The first thing you put in will be the last thing you get out. ## Querying Collections 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. ### Slices Converting between slices and a queryable structure is as trivial as it should be. ``` Go original := []string{"a", "b", "c"} subject := collection.AsEnumerable(original...) for entry := range subject.Enumerate(context.Background()) { fmt.Println(entry) } // Output: // a // b // c ``` ### Where ``` Go ctx, cancel := context.WithCancel(context.Background()) defer cancel() subject := collection.AsEnumerable[int](1, 2, 3, 4, 5, 6) filtered := collection.Where(subject, func(num int) bool{ return num > 3 }) for entry := range filtered.Enumerate(ctx) { fmt.Println(entry) } // Output: // 4 // 5 // 6 ``` ### Select ``` Go ctx, cancel := context.WithCancel(context.Background()) defer cancel() subject := collection.AsEnumerable[int](1, 2, 3, 4, 5, 6) updated := collection.Select[int](subject, func(num int) int { return num + 10 }) for entry := range updated.Enumerate(ctx) { fmt.Println(entry) } // Output: // 11 // 12 // 13 // 14 // 15 // 16 ``` ## Queues ### Creating a Queue ``` Go ctx, cancel := context.WithCancel(context.Background()) defer cancel() subject := collection.NewQueue(1, 2, 3, 5, 8, 13, 21) selected := subject.Enumerate(ctx).Skip(3).Take(3) for entry := range selected { fmt.Println(entry) } // Output: // 5 // 8 // 13 ``` ### Checking if a Queue is empty ``` Go populated := collection.NewQueue(1, 2, 3, 5, 8, 13) notPopulated := collection.NewQueue[int]() fmt.Println(populated.IsEmpty()) fmt.Println(notPopulated.IsEmpty()) // Output: // false // true ``` ## Other utilities ### Fibonacci 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. ### Filesystem 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. # Versioning This library will conform to strict semantic versions as defined by [semver.org](http://semver.org/spec/v2.0.0.html)'s v2 specification. # Contributing I accept contributions! Please submit PRs to the `main` or `v1` branches. Remember to add tests! # F.A.Q. ## Should I use v1 or v2? 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. 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. 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. ## Why does `Enumerate` take a `context.Context`? 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 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.) 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. **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.** 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: ``` Go ctx, cancel := context.WithCancel(context.Background()) defer cancel() // ... for item := range myEnumerable.Enumerate(ctx) { // ... } ```collection-2.0.0/dictionary.go000066400000000000000000000067751503143002500163500ustar00rootroot00000000000000package collection import ( "context" "sort" ) type trieNode struct { IsWord bool Children map[rune]*trieNode } func (node trieNode) Population() uint { var sum uint for _, child := range node.Children { sum += child.Population() } if node.IsWord { sum++ } return sum } func (node *trieNode) Navigate(word string) *trieNode { cursor := node for len(word) > 0 && cursor != nil { if next, ok := cursor.Children[rune(word[0])]; ok { cursor = next word = word[1:] } else { return nil } } return cursor } // Dictionary is a list of words. It is implemented as a Trie for memory efficiency. type Dictionary struct { root *trieNode size int64 } // Add inserts a word into the dictionary, and returns whether or not that word was a new word. // // Time complexity: O(m) where 'm' is the length of word. func (dict *Dictionary) Add(word string) (wasAdded bool) { if dict.root == nil { dict.root = &trieNode{} } cursor := dict.root for len(word) > 0 { if cursor.Children == nil { cursor.Children = make(map[rune]*trieNode) } nextLetter := rune(word[0]) next, ok := cursor.Children[nextLetter] if !ok { next = &trieNode{} cursor.Children[nextLetter] = next } cursor = next word = word[1:] } wasAdded = !cursor.IsWord if wasAdded { dict.size++ } cursor.IsWord = true return } // Clear removes all items from the dictionary. func (dict *Dictionary) Clear() { dict.root = nil dict.size = 0 } // Contains searches the Dictionary to see if the specified word is present. // // Time complexity: O(m) where 'm' is the length of word. func (dict Dictionary) Contains(word string) bool { if dict.root == nil { return false } targetNode := dict.root.Navigate(word) return targetNode != nil && targetNode.IsWord } // Remove ensures that `word` is not in the Dictionary. Returns whether or not an item was removed. // // Time complexity: O(m) where 'm' is the length of word. func (dict *Dictionary) Remove(word string) (wasRemoved bool) { lastPos := len(word) - 1 parent := dict.root.Navigate(word[:lastPos]) if parent == nil { return } lastLetter := rune(word[lastPos]) subject, ok := parent.Children[lastLetter] if !ok { return } wasRemoved = subject.IsWord if wasRemoved { dict.size-- } subject.IsWord = false if subject.Population() == 0 { delete(parent.Children, lastLetter) } return } // Size reports the number of words there are in the Dictionary. // // Time complexity: O(1) func (dict Dictionary) Size() int64 { return dict.size } // Enumerate lists each word in the Dictionary alphabetically. func (dict Dictionary) Enumerate(ctx context.Context) Enumerator[string] { if dict.root == nil { return Empty[string]().Enumerate(ctx) } return dict.root.Enumerate(ctx) } func (node trieNode) Enumerate(ctx context.Context) Enumerator[string] { var enumerateHelper func(trieNode, string) results := make(chan string) enumerateHelper = func(subject trieNode, prefix string) { if subject.IsWord { select { case results <- prefix: case <-ctx.Done(): return } } alphabetizedChildren := []rune{} for letter := range subject.Children { alphabetizedChildren = append(alphabetizedChildren, letter) } sort.Slice(alphabetizedChildren, func(i, j int) bool { return alphabetizedChildren[i] < alphabetizedChildren[j] }) for _, letter := range alphabetizedChildren { enumerateHelper(*subject.Children[letter], prefix+string(letter)) } } go func() { defer close(results) enumerateHelper(node, "") }() return results } collection-2.0.0/dictionary_examples_test.go000066400000000000000000000030361503143002500212700ustar00rootroot00000000000000package collection_test import ( "context" "fmt" "strings" "github.com/marstr/collection/v2" ) func ExampleDictionary_Add() { subject := &collection.Dictionary{} const example = "hello" fmt.Println(subject.Contains(example)) fmt.Println(subject.Size()) subject.Add(example) fmt.Println(subject.Contains(example)) fmt.Println(subject.Size()) // Output: // false // 0 // true // 1 } func ExampleDictionary_Clear() { subject := &collection.Dictionary{} subject.Add("hello") subject.Add("world") fmt.Println(subject.Size()) fmt.Println(collection.CountAll[string](subject)) subject.Clear() fmt.Println(subject.Size()) fmt.Println(collection.Any[string](subject)) // Output: // 2 // 2 // 0 // false } func ExampleDictionary_Enumerate() { subject := &collection.Dictionary{} subject.Add("world") subject.Add("hello") upperCase := collection.Select[string](subject, strings.ToUpper) for word := range subject.Enumerate(context.Background()) { fmt.Println(word) } for word := range upperCase.Enumerate(context.Background()) { fmt.Println(word) } // Output: // hello // world // HELLO // WORLD } func ExampleDictionary_Remove() { const world = "world" subject := &collection.Dictionary{} subject.Add("hello") subject.Add(world) fmt.Println(subject.Size()) fmt.Println(collection.CountAll[string](subject)) subject.Remove(world) fmt.Println(subject.Size()) fmt.Println(collection.CountAll[string](subject)) fmt.Println(collection.Any[string](subject)) // Output: // 2 // 2 // 1 // 1 // true } collection-2.0.0/dictionary_test.go000066400000000000000000000065671503143002500174060ustar00rootroot00000000000000package collection import ( "context" "strings" "testing" ) func TestDictionary_Enumerate(t *testing.T) { dictSets := [][]string{ {"alpha", "beta", "charlie"}, {"also", "always"}, {"canned", "beans"}, {"duplicated", "duplicated", "after"}, } for _, ds := range dictSets { t.Run("", func(t *testing.T) { subject := Dictionary{} expected := make(map[string]bool) added := 0 for _, entry := range ds { if subject.Add(entry) { added++ } expected[entry] = false } expectedSize := len(expected) if added != expectedSize { t.Logf("`Add` returned true %d times, expected %d times", added, expectedSize) t.Fail() } if subjectSize := CountAll[string](subject); subjectSize != expectedSize { t.Logf("`CountAll` returned %d elements, expected %d", subjectSize, expectedSize) t.Fail() } prev := "" for result := range subject.Enumerate(context.Background()) { t.Log(result) if alreadySeen, ok := expected[result]; !ok { t.Log("An unadded value was returned") t.Fail() } else if alreadySeen { t.Logf("\"%s\" was duplicated", result) t.Fail() } if stringle(result, prev) { t.Logf("Results \"%s\" and \"%s\" were not alphabetized.", prev, result) t.Fail() } prev = result expected[result] = true } }) } } func TestDictionary_Add(t *testing.T) { subject := Dictionary{} subject.Add("word") if rootChildrenCount := len(subject.root.Children); rootChildrenCount != 1 { t.Logf("The root should only have one child, got %d instead.", rootChildrenCount) t.Fail() } if retreived, ok := subject.root.Children['w']; ok { leaf := retreived.Navigate("ord") if leaf == nil { t.Log("Unable to navigate from `w`") t.Fail() } else if !leaf.IsWord { t.Log("leaf should have been a word") t.Fail() } } else { t.Log("Root doesn't have child for `w`") t.Fail() } } func TestTrieNode_Navigate(t *testing.T) { leaf := trieNode{ IsWord: true, } subject := trieNode{ Children: map[rune]*trieNode{ 'a': { Children: map[rune]*trieNode{ 'b': { Children: map[rune]*trieNode{ 'c': &leaf, }, }, }, }, }, } testCases := []struct { address string expected *trieNode }{ {"abc", &leaf}, {"abd", nil}, {"", &subject}, {"a", subject.Children['a']}, } for _, tc := range testCases { t.Run("", func(t *testing.T) { if result := subject.Navigate(tc.address); result != tc.expected { t.Logf("got: %v want: %v", result, tc.expected) t.Fail() } }) } } func Test_stringle(t *testing.T) { testCases := []struct { left string right string expected bool }{ {"a", "b", true}, {"b", "a", false}, {"a", "a", true}, {"alpha", "b", true}, {"a", "beta", true}, {"alpha", "alpha", true}, {"alpha", "alphabet", true}, {"alphabet", "alpha", false}, {"", "a", true}, {"", "", true}, } for _, tc := range testCases { t.Run(strings.Join([]string{tc.left, tc.right}, ","), func(t *testing.T) { if got := stringle(tc.left, tc.right); got != tc.expected { t.Logf("got: %v want: %v", got, tc.expected) t.Fail() } }) } } func stringle(left, right string) bool { other := []byte(right) for i, letter := range []byte(left) { if i >= len(other) { return false } if letter > other[i] { return false } else if letter < other[i] { break } } return true } collection-2.0.0/doc.go000066400000000000000000000025501503143002500147330ustar00rootroot00000000000000// Package collection seeks to provide an expressive and readable way of working with basic data structures in Go. // // As a former .NET developer, I deeply missed writing programs in the style of Linq. Doing so enables concurrent/ // parallel reactive programs to be written in a snap. Go's functional nature enables us to have a very similar, if more // verbose, experience. // // Take for example the scenario of printing the number of Go source files in a directory. Using this package, // this takes only a few lines: // // myDir := collection.Directory{ // Location: "./", // } // // results := myDir.Enumerate(context.Background()).Where(func(x interface{}) bool { // return strings.HasSuffix(x.(string), ".go") // }) // // fmt.Println(results.CountAll()) // // A directory is a collection of filesystem entries, so we're able to iterate through them using the "Enumerate" // function. From there, we filter on only file names that end with ".go". Finally, we print the number of entries that // were encountered. // // This is a trivial example, but imagine building more elaborate pipelines. Maybe take advantage of the // `SelectParallel` function which allows multiple goroutines to process a single transform at once, with their results // being funnelled into the next phase of the pipeline. Suddenly, injecting new steps can be transparent. package collection collection-2.0.0/fibonacci.go000066400000000000000000000007701503143002500161050ustar00rootroot00000000000000package collection import "context" type fibonacciGenerator struct{} // Fibonacci is an Enumerable which will dynamically generate the fibonacci sequence. var Fibonacci Enumerable[uint] = fibonacciGenerator{} func (gen fibonacciGenerator) Enumerate(ctx context.Context) Enumerator[uint] { retval := make(chan uint) go func() { defer close(retval) var a, b uint = 0, 1 for { select { case retval <- a: a, b = b, a+b case <-ctx.Done(): return } } }() return retval } collection-2.0.0/filesystem.go000066400000000000000000000031561503143002500163550ustar00rootroot00000000000000package collection import ( "context" "os" "path/filepath" ) // DirectoryOptions is a means of configuring a `Directory` instance to including various children in its enumeration without // supplying a `Where` clause later. type DirectoryOptions uint // These constants define all of the supported options for configuring a `Directory` const ( DirectoryOptionsExcludeFiles = 1 << iota DirectoryOptionsExcludeDirectories DirectoryOptionsRecursive ) // Directory treats a filesystem path as a collection of filesystem entries, specifically a collection of directories and files. type Directory struct { Location string Options DirectoryOptions } func (d Directory) applyOptions(loc string, info os.FileInfo) bool { if info.IsDir() && (d.Options&DirectoryOptionsExcludeDirectories) != 0 { return false } if !info.IsDir() && d.Options&DirectoryOptionsExcludeFiles != 0 { return false } return true } // Enumerate lists the items in a `Directory` func (d Directory) Enumerate(ctx context.Context) Enumerator[string] { results := make(chan string) go func() { defer close(results) filepath.Walk(d.Location, func(currentLocation string, info os.FileInfo, openErr error) (err error) { if openErr != nil { err = openErr return } if d.Location == currentLocation { return } if info.IsDir() && d.Options&DirectoryOptionsRecursive == 0 { err = filepath.SkipDir } if d.applyOptions(currentLocation, info) { select { case results <- currentLocation: // Intentionally Left Blank case <-ctx.Done(): err = ctx.Err() } } return }) }() return results } collection-2.0.0/filesystem_examples_test.go000066400000000000000000000011761503143002500213120ustar00rootroot00000000000000package collection_test import ( "context" "fmt" "github.com/marstr/collection/v2" "path" ) func ExampleDirectory_Enumerate() { traverser := collection.Directory{ Location: ".", Options: collection.DirectoryOptionsExcludeDirectories, } ctx, cancel := context.WithCancel(context.Background()) defer cancel() fileNames := collection.Select[string](traverser, path.Base) filesOfInterest := collection.Where(fileNames, func(subject string) bool { return subject == "filesystem_examples_test.go" }) for entry := range filesOfInterest.Enumerate(ctx) { fmt.Println(entry) } // Output: filesystem_examples_test.go } collection-2.0.0/filesystem_test.go000066400000000000000000000063411503143002500174130ustar00rootroot00000000000000package collection import ( "context" "fmt" "math" "path/filepath" "testing" ) func TestEnumerateDirectoryOptions_UniqueBits(t *testing.T) { isPowerOfTwo := func(subject float64) bool { a := math.Abs(math.Log2(subject)) b := math.Floor(a) return a-b < .0000001 } if !isPowerOfTwo(64) { t.Log("isPowerOfTwo decided 64 is not a power of two.") t.FailNow() } if isPowerOfTwo(91) { t.Log("isPowerOfTwo decided 91 is a power of two.") t.FailNow() } seen := make(map[DirectoryOptions]struct{}) declared := []DirectoryOptions{ DirectoryOptionsExcludeFiles, DirectoryOptionsExcludeDirectories, DirectoryOptionsRecursive, } for _, option := range declared { if _, ok := seen[option]; ok { t.Logf("Option: %d has already been declared.", option) t.Fail() } seen[option] = struct{}{} if !isPowerOfTwo(float64(option)) { t.Logf("Option should have been a power of two, got %g instead.", float64(option)) t.Fail() } } } func TestDirectory_Enumerate(t *testing.T) { subject := Directory{ Location: filepath.Join(".", "testdata", "foo"), } testCases := []struct { options DirectoryOptions expected map[string]struct{} }{ { options: 0, expected: map[string]struct{}{ filepath.Join("testdata", "foo", "a.txt"): {}, filepath.Join("testdata", "foo", "c.txt"): {}, filepath.Join("testdata", "foo", "bar"): {}, }, }, { options: DirectoryOptionsExcludeFiles, expected: map[string]struct{}{ filepath.Join("testdata", "foo", "bar"): {}, }, }, { options: DirectoryOptionsExcludeDirectories, expected: map[string]struct{}{ filepath.Join("testdata", "foo", "a.txt"): {}, filepath.Join("testdata", "foo", "c.txt"): {}, }, }, { options: DirectoryOptionsRecursive, expected: map[string]struct{}{ filepath.Join("testdata", "foo", "bar"): {}, filepath.Join("testdata", "foo", "bar", "b.txt"): {}, filepath.Join("testdata", "foo", "a.txt"): {}, filepath.Join("testdata", "foo", "c.txt"): {}, }, }, { options: DirectoryOptionsExcludeFiles | DirectoryOptionsRecursive, expected: map[string]struct{}{ filepath.Join("testdata", "foo", "bar"): {}, }, }, { options: DirectoryOptionsRecursive | DirectoryOptionsExcludeDirectories, expected: map[string]struct{}{ filepath.Join("testdata", "foo", "a.txt"): {}, filepath.Join("testdata", "foo", "bar", "b.txt"): {}, filepath.Join("testdata", "foo", "c.txt"): {}, }, }, { options: DirectoryOptionsExcludeDirectories | DirectoryOptionsExcludeFiles, expected: map[string]struct{}{}, }, { options: DirectoryOptionsExcludeFiles | DirectoryOptionsRecursive | DirectoryOptionsExcludeDirectories, expected: map[string]struct{}{}, }, } for _, tc := range testCases { subject.Options = tc.options t.Run(fmt.Sprintf("%d", uint(tc.options)), func(t *testing.T) { for entry := range subject.Enumerate(context.Background()) { if _, ok := tc.expected[entry]; !ok { t.Logf("unexpected result: %q", entry) t.Fail() } delete(tc.expected, entry) } if len(tc.expected) != 0 { for unseenFile := range tc.expected { t.Logf("missing file: %q", unseenFile) } t.Fail() } }) } } collection-2.0.0/go.mod000066400000000000000000000000601503143002500147370ustar00rootroot00000000000000module github.com/marstr/collection/v2 go 1.18 collection-2.0.0/linkedlist.go000066400000000000000000000214021503143002500163250ustar00rootroot00000000000000package collection import ( "bytes" "context" "errors" "fmt" "sync" ) // LinkedList encapsulates a list where each entry is aware of only the next entry in the list. type LinkedList[T any] struct { first *llNode[T] last *llNode[T] length uint key sync.RWMutex } type llNode[T any] struct { payload T next *llNode[T] prev *llNode[T] } // Comparator is a function which evaluates two values to determine their relation to one another. // - Zero is returned when `a` and `b` are equal. // - Positive numbers are returned when `a` is greater than `b`. // - Negative numbers are returned when `a` is less than `b`. type Comparator[T any] func(a, b T) (int, error) // A collection of errors that may be thrown by functions in this file. var ( ErrUnexpectedType = errors.New("value was of an unexpected type") ) // NewLinkedList instantiates a new LinkedList with the entries provided. func NewLinkedList[T any](entries ...T) *LinkedList[T] { list := &LinkedList[T]{} for _, entry := range entries { list.AddBack(entry) } return list } // AddBack creates an entry in the LinkedList that is logically at the back of the list. func (list *LinkedList[T]) AddBack(entry T) { list.key.Lock() defer list.key.Unlock() toAppend := &llNode[T]{ payload: entry, } list.addNodeBack(toAppend) } func (list *LinkedList[T]) addNodeBack(node *llNode[T]) { list.length++ node.prev = list.last if list.first == nil { list.first = node list.last = node return } list.last.next = node list.last = node } // AddFront creates an entry in the LinkedList that is logically at the front of the list. func (list *LinkedList[T]) AddFront(entry T) { toAppend := &llNode[T]{ payload: entry, } list.key.Lock() defer list.key.Unlock() list.addNodeFront(toAppend) } func (list *LinkedList[T]) addNodeFront(node *llNode[T]) { list.length++ node.next = list.first if list.first == nil { list.last = node } else { list.first.prev = node } list.first = node } // Enumerate creates a new instance of Enumerable which can be executed on. func (list *LinkedList[T]) Enumerate(ctx context.Context) Enumerator[T] { retval := make(chan T) go func() { list.key.RLock() defer list.key.RUnlock() defer close(retval) current := list.first for current != nil { select { case retval <- current.payload: // Intentionally Left Blank case <-ctx.Done(): return } current = current.next } }() return retval } // Get finds the value from the LinkedList. // pos is expressed as a zero-based index begining from the 'front' of the list. func (list *LinkedList[T]) Get(pos uint) (T, bool) { list.key.RLock() defer list.key.RUnlock() node, ok := get(list.first, pos) if ok { return node.payload, true } return *new(T), false } // IsEmpty tests the list to determine if it is populate or not. func (list *LinkedList[T]) IsEmpty() bool { list.key.RLock() defer list.key.RUnlock() return list.first == nil } // Length returns the number of elements present in the LinkedList. func (list *LinkedList[T]) Length() uint { list.key.RLock() defer list.key.RUnlock() return list.length } // PeekBack returns the entry logicall stored at the back of the list without removing it. func (list *LinkedList[T]) PeekBack() (T, bool) { list.key.RLock() defer list.key.RUnlock() if list.last == nil { return *new(T), false } return list.last.payload, true } // PeekFront returns the entry logically stored at the front of this list without removing it. func (list *LinkedList[T]) PeekFront() (T, bool) { list.key.RLock() defer list.key.RUnlock() if list.first == nil { return *new(T), false } return list.first.payload, true } // RemoveFront returns the entry logically stored at the front of this list and removes it. func (list *LinkedList[T]) RemoveFront() (T, bool) { list.key.Lock() defer list.key.Unlock() if list.first == nil { return *new(T), false } retval := list.first.payload list.first = list.first.next list.length-- if list.length == 0 { list.last = nil } return retval, true } // RemoveBack returns the entry logically stored at the back of this list and removes it. func (list *LinkedList[T]) RemoveBack() (T, bool) { list.key.Lock() defer list.key.Unlock() if list.last == nil { return *new(T), false } retval := list.last.payload list.length-- if list.length == 0 { list.first = nil } else { list.last = list.last.prev list.last.next = nil } return retval, true } // removeNode func (list *LinkedList[T]) removeNode(target *llNode[T]) { if target == nil { return } if target.next != nil { target.next.prev = target.prev } if target.prev != nil { target.prev.next = target.next } list.length-- if list.length == 0 { list.first = nil list.last = nil return } if list.first == target { list.first = target.next } if list.last == target { list.last = target.prev } } // Sort rearranges the positions of the entries in this list so that they are // ascending. func (list *LinkedList[T]) Sort(comparator Comparator[T]) error { list.key.Lock() defer list.key.Unlock() var err error list.first, err = mergeSort(list.first, comparator) if err != nil { return err } list.last = findLast(list.first) return err } // String prints upto the first fifteen elements of the list in string format. func (list *LinkedList[T]) String() string { list.key.RLock() defer list.key.RUnlock() builder := bytes.NewBufferString("[") current := list.first for i := 0; i < 15 && current != nil; i++ { builder.WriteString(fmt.Sprintf("%v ", current.payload)) current = current.next } if current == nil || current.next == nil { builder.Truncate(builder.Len() - 1) } else { builder.WriteString("...") } builder.WriteRune(']') return builder.String() } // Swap switches the positions in which two values are stored in this list. // x and y represent the indexes of the items that should be swapped. func (list *LinkedList[T]) Swap(x, y uint) error { list.key.Lock() defer list.key.Unlock() var xNode, yNode *llNode[T] if temp, ok := get(list.first, x); ok { xNode = temp } else { return fmt.Errorf("index out of bounds 'x', wanted less than %d got %d", list.length, x) } if temp, ok := get(list.first, y); ok { yNode = temp } else { return fmt.Errorf("index out of bounds 'y', wanted less than %d got %d", list.length, y) } temp := xNode.payload xNode.payload = yNode.payload yNode.payload = temp return nil } // ToSlice converts the contents of the LinkedList into a slice. func (list *LinkedList[T]) ToSlice() []T { return list.Enumerate(context.Background()).ToSlice() } func findLast[T any](head *llNode[T]) *llNode[T] { if head == nil { return nil } current := head for current.next != nil { current = current.next } return current } func get[T any](head *llNode[T], pos uint) (*llNode[T], bool) { for i := uint(0); i < pos; i++ { if head == nil { return nil, false } head = head.next } return head, true } // merge takes two sorted lists and merges them into one sorted list. // Behavior is undefined when you pass a non-sorted list as `left` or `right` func merge[T any](left, right *llNode[T], comparator Comparator[T]) (first *llNode[T], err error) { curLeft := left curRight := right var last *llNode[T] appendResults := func(updated *llNode[T]) { if last == nil { last = updated } else { last.next = updated last = last.next } if first == nil { first = last } } for curLeft != nil && curRight != nil { var res int if res, err = comparator(curLeft.payload, curRight.payload); nil != err { break // Don't return, stitch the remaining elements back on. } else if res < 0 { appendResults(curLeft) curLeft = curLeft.next } else { appendResults(curRight) curRight = curRight.next } } if curLeft != nil { appendResults(curLeft) } if curRight != nil { appendResults(curRight) } return } func mergeSort[T any](head *llNode[T], comparator Comparator[T]) (*llNode[T], error) { if head == nil { return nil, nil } left, right := split(head) repair := func(left, right *llNode[T]) *llNode[T] { lastLeft := findLast(left) lastLeft.next = right return left } var err error if left != nil && left.next != nil { left, err = mergeSort(left, comparator) if err != nil { return repair(left, right), err } } if right != nil && right.next != nil { right, err = mergeSort(right, comparator) if err != nil { return repair(left, right), err } } return merge(left, right, comparator) } // split breaks a list in half. func split[T any](head *llNode[T]) (left, right *llNode[T]) { left = head if head == nil || head.next == nil { return } right = head sprinter := head prev := head for sprinter != nil && sprinter.next != nil { prev = right right = right.next sprinter = sprinter.next.next } prev.next = nil return } collection-2.0.0/linkedlist_examples_test.go000066400000000000000000000036371503143002500212740ustar00rootroot00000000000000package collection_test import ( "context" "fmt" "github.com/marstr/collection/v2" ) func ExampleLinkedList_AddFront() { subject := collection.NewLinkedList(2, 3) subject.AddFront(1) result, _ := subject.PeekFront() fmt.Println(result) // Output: 1 } func ExampleLinkedList_AddBack() { subject := collection.NewLinkedList(2, 3, 5) subject.AddBack(8) result, _ := subject.PeekBack() fmt.Println(result) fmt.Println(subject.Length()) // Output: // 8 // 4 } func ExampleLinkedList_Enumerate() { subject := collection.NewLinkedList(2, 3, 5, 8) results := collection.Select[int](subject, func(a int) int { return -1 * a }) for entry := range results.Enumerate(context.Background()) { fmt.Println(entry) } // Output: // -2 // -3 // -5 // -8 } func ExampleLinkedList_Get() { subject := collection.NewLinkedList(2, 3, 5, 8) val, _ := subject.Get(2) fmt.Println(val) // Output: 5 } func ExampleNewLinkedList() { subject1 := collection.NewLinkedList('a', 'b', 'c', 'd', 'e') fmt.Println(subject1.Length()) slice := []interface{}{1, 2, 3, 4, 5, 6} subject2 := collection.NewLinkedList(slice...) fmt.Println(subject2.Length()) // Output: // 5 // 6 } func ExampleLinkedList_Sort() { // Sorti sorts into ascending order, this example demonstrates sorting // into descending order. subject := collection.NewLinkedList(2, 4, 3, 5, 7, 7) subject.Sort(func(a, b int) (int, error) { return b - a, nil }) fmt.Println(subject) // Output: [7 7 5 4 3 2] } func ExampleLinkedList_String() { subject1 := collection.NewLinkedList[int]() for i := 0; i < 20; i++ { subject1.AddBack(i) } fmt.Println(subject1) subject2 := collection.NewLinkedList[int](1, 2, 3) fmt.Println(subject2) // Output: // [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...] // [1 2 3] } func ExampleLinkedList_Swap() { subject := collection.NewLinkedList(2, 3, 5, 8, 13) subject.Swap(1, 3) fmt.Println(subject) // Output: [2 8 5 3 13] } collection-2.0.0/linkedlist_test.go000066400000000000000000000153121503143002500173670ustar00rootroot00000000000000package collection import ( "testing" ) func TestLinkedList_findLast_empty(t *testing.T) { if result := findLast[int](nil); result != nil { t.Logf("got: %v\nwant: %v", result, nil) } } func TestLinkedList_merge(t *testing.T) { testCases := []struct { Left *LinkedList[int] Right *LinkedList[int] Expected []int Comp Comparator[int] }{ { NewLinkedList[int](1, 3, 5), NewLinkedList[int](2, 4), []int{1, 2, 3, 4, 5}, UncheckedComparatori, }, { NewLinkedList[int](1, 2, 3), NewLinkedList[int](), []int{1, 2, 3}, UncheckedComparatori, }, { NewLinkedList[int](), NewLinkedList[int](1, 2, 3), []int{1, 2, 3}, UncheckedComparatori, }, { NewLinkedList[int](), NewLinkedList[int](), []int{}, UncheckedComparatori, }, { NewLinkedList[int](1), NewLinkedList[int](1), []int{1, 1}, UncheckedComparatori, }, { NewLinkedList(2), NewLinkedList(1), []int{1, 2}, UncheckedComparatori, }, { NewLinkedList(3), NewLinkedList[int](), []int{3}, UncheckedComparatori, }, { NewLinkedList[int](), NewLinkedList(10), []int{10}, UncheckedComparatori, }, } for _, tc := range testCases { t.Run("", func(t *testing.T) { result, err := merge(tc.Left.first, tc.Right.first, tc.Comp) if err != nil { t.Error(err) } i := 0 for cursor := result; cursor != nil; cursor, i = cursor.next, i+1 { if cursor.payload != tc.Expected[i] { t.Logf("got: %d want: %d", cursor.payload, tc.Expected[i]) t.Fail() } } if expectedLength := len(tc.Expected); i != expectedLength { t.Logf("Unexpected length:\n\tgot: %d\n\twant: %d", i, expectedLength) t.Fail() } }) } } func UncheckedComparatori(a, b int) (int, error) { return a - b, nil } func TestLinkedList_RemoveBack_single(t *testing.T) { subject := NewLinkedList(1) subject.RemoveBack() if subject.Length() != 0 { t.Fail() } } func TestLinkedList_split_Even(t *testing.T) { subject := NewLinkedList(1, 2, 3, 4) left, right := split(subject.first) if left == nil { t.Logf("unexpected nil value for left") t.Fail() } else if left.payload != 1 { t.Logf("got: %d\nwant: %d", left.payload, 1) t.Fail() } if right == nil { t.Logf("unexpected nil for right") t.Fail() } else if right.payload != 3 { t.Logf("got: %d\nwant: %d", right.payload, 3) } } func TestLinkedList_split_Odd(t *testing.T) { subject := NewLinkedList(1, 2, 3, 4, 5) left, right := split(subject.first) if left == nil { t.Logf("unexpected nil value for left") t.Fail() } else if left.payload != 1 { t.Logf("got: %d\n want: %d", left.payload, 1) t.Fail() } else if last := findLast(left).payload; last != 2 { t.Logf("got:\n%d\nwant:\n%d", last, 2) t.Fail() } if right == nil { t.Logf("unexpected nil value for right") t.Fail() } else if right.payload != 3 { t.Logf("got:\n%d\nwant:\n%d", right.payload, 3) t.Fail() } else if last := findLast(right).payload; last != 5 { t.Logf("got:\n%d\nwant:%d", last, 5) } } func TestLinkedList_split_Empty(t *testing.T) { subject := NewLinkedList[*int]() left, right := split(subject.first) if left != nil { t.Logf("got: %v\nwant: %v", left, nil) t.Fail() } if right != nil { t.Logf("got: %v\nwant: %v", right, nil) t.Fail() } } func TestLinkedList_split_Single(t *testing.T) { subject := NewLinkedList(1) left, right := split(subject.first) if left == nil { t.Logf("unexpected nil value for left") t.Fail() } else if left.payload != 1 { t.Logf("got: %d\nwant: %d", left.payload, 1) t.Fail() } if right != nil { t.Logf("got: %v\nwant: %v", right, nil) t.Fail() } if last := findLast(left).payload; last != 1 { t.Logf("got:\n%d\nwant:\n%d", last, 1) t.Fail() } } func TestLinkedList_split_Double(t *testing.T) { subject := NewLinkedList(1, 2) left, right := split(subject.first) if left == nil { t.Logf("unexpected nil value for left") t.Fail() } else if left.payload != 1 { t.Logf("got: %d\nwant: %d", left.payload, 1) } if right == nil { t.Logf("unexpected nil value for right") t.Fail() } else if right.payload != 2 { t.Logf("got: %d\nwant: %d", right.payload, 2) } } func TestLinkedList_Swap_OutOfBounds(t *testing.T) { subject := NewLinkedList(2, 3) if err := subject.Swap(0, 8); err == nil { t.Log("swap should have failed on y") t.Fail() } if err := subject.Swap(11, 1); err == nil { t.Logf("swap shoud have failed on x") t.Fail() } if count := subject.Length(); count != 2 { t.Logf("got: %d\nwant: %d", count, 2) t.Fail() } wantStr := "[2 3]" gotStr := subject.String() if wantStr != gotStr { t.Logf("got: %s\nwant: %s", gotStr, wantStr) t.Fail() } } func TestLinkedList_Get_OutsideBounds(t *testing.T) { subject := NewLinkedList(2, 3, 5, 8, 13, 21) result, ok := subject.Get(10) if !(result == 0 && ok == false) { t.Logf("got: %v %v\nwant: %v %v", result, ok, nil, false) t.Fail() } } func TestLinkedList_removeNode(t *testing.T) { removeHead := func(t *testing.T) { subject := NewLinkedList(1, 2, 3) subject.removeNode(subject.first) if subject.length != 2 { t.Logf("got %d, want %d", subject.length, 2) t.Fail() } if first, ok := subject.Get(0); ok { if first != 2 { t.Logf("got %d, want %d", first, 2) t.Fail() } } else { t.Logf("no item at position 0!") t.Fail() } if second, ok := subject.Get(1); ok { if second != 3 { t.Logf("got %d, want %d", second, 3) t.Fail() } } else { t.Logf("no item at position 1!") t.Fail() } } removeTail := func(t *testing.T) { subject := NewLinkedList(1, 2, 3) subject.removeNode(subject.last) if subject.length != 2 { t.Logf("got %d, want %d", subject.length, 2) t.Fail() } if first, ok := subject.Get(0); ok { if first != 1 { t.Logf("got %d, want %d", first, 1) t.Fail() } } else { t.Logf("no item at position 0!") t.Fail() } if second, ok := subject.Get(1); ok { if second != 2 { t.Logf("got %d, want %d", second, 2) t.Fail() } } else { t.Logf("no item at position 1!") t.Fail() } } removeMiddle := func(t *testing.T) { subject := NewLinkedList(1, 2, 3) subject.removeNode(subject.first.next) if subject.length != 2 { t.Logf("got %d, want %d", subject.length, 2) t.Fail() } if first, ok := subject.Get(0); ok { if first != 1 { t.Logf("got %d, want %d", first, 1) t.Fail() } } else { t.Logf("no item at position 0!") t.Fail() } if second, ok := subject.Get(1); ok { if second != 3 { t.Logf("got %d, want %d", second, 3) t.Fail() } } else { t.Logf("no item at position 1!") t.Fail() } } t.Run("RemoveHead", removeHead) t.Run("RemoveTail", removeTail) t.Run("RemoveMiddle", removeMiddle) } collection-2.0.0/list.go000066400000000000000000000066031503143002500151440ustar00rootroot00000000000000package collection import ( "bytes" "context" "fmt" "sync" ) // List is a dynamically sized list akin to List in the .NET world, // ArrayList in the Java world, or vector in the C++ world. type List[T any] struct { underlyer []T key sync.RWMutex } // NewList creates a new list which contains the elements provided. func NewList[T any](entries ...T) *List[T] { return &List[T]{ underlyer: entries, } } // Add appends an entry to the logical end of the List. func (l *List[T]) Add(entries ...T) { l.key.Lock() defer l.key.Unlock() l.underlyer = append(l.underlyer, entries...) } // AddAt injects values beginning at `pos`. If multiple values // are provided in `entries` they are placed in the same order // they are provided. func (l *List[T]) AddAt(pos uint, entries ...T) { l.key.Lock() defer l.key.Unlock() l.underlyer = append(l.underlyer[:pos], append(entries, l.underlyer[pos:]...)...) } // Enumerate lists each element present in the collection func (l *List[T]) Enumerate(ctx context.Context) Enumerator[T] { retval := make(chan T) go func() { l.key.RLock() defer l.key.RUnlock() defer close(retval) for _, entry := range l.underlyer { select { case retval <- entry: // Intentionally Left Blank case <-ctx.Done(): return } } }() return retval } // Get retreives the value stored in a particular position of the list. // If no item exists at the given position, the second parameter will be // returned as false. func (l *List[T]) Get(pos uint) (T, bool) { l.key.RLock() defer l.key.RUnlock() if pos > uint(len(l.underlyer)) { return *new(T), false } return l.underlyer[pos], true } // IsEmpty tests to see if this List has any elements present. func (l *List[T]) IsEmpty() bool { l.key.RLock() defer l.key.RUnlock() return len(l.underlyer) == 0 } // Length returns the number of elements in the List. func (l *List[T]) Length() uint { l.key.RLock() defer l.key.RUnlock() return uint(len(l.underlyer)) } // Remove retreives a value from this List and shifts all other values. func (l *List[T]) Remove(pos uint) (T, bool) { l.key.Lock() defer l.key.Unlock() if pos > uint(len(l.underlyer)) { return *new(T), false } retval := l.underlyer[pos] l.underlyer = append(l.underlyer[:pos], l.underlyer[pos+1:]...) return retval, true } // Set updates the value stored at a given position in the List. func (l *List[T]) Set(pos uint, val T) bool { l.key.Lock() defer l.key.Unlock() var retval bool count := uint(len(l.underlyer)) if pos > count { retval = false } else { l.underlyer[pos] = val retval = true } return retval } // String generates a textual representation of the List for the sake of debugging. func (l *List[T]) String() string { l.key.RLock() defer l.key.RUnlock() builder := bytes.NewBufferString("[") for i, entry := range l.underlyer { if i >= 15 { builder.WriteString("... ") break } builder.WriteString(fmt.Sprintf("%v ", entry)) } builder.Truncate(builder.Len() - 1) builder.WriteRune(']') return builder.String() } // Swap switches the values that are stored at positions `x` and `y` func (l *List[T]) Swap(x, y uint) bool { l.key.Lock() defer l.key.Unlock() return l.swap(x, y) } func (l *List[T]) swap(x, y uint) bool { count := uint(len(l.underlyer)) if x < count && y < count { temp := l.underlyer[x] l.underlyer[x] = l.underlyer[y] l.underlyer[y] = temp return true } return false } collection-2.0.0/list_test.go000066400000000000000000000002541503143002500161770ustar00rootroot00000000000000package collection import "fmt" func ExampleList_AddAt() { subject := NewList(0, 1, 4, 5, 6) subject.AddAt(2, 2, 3) fmt.Println(subject) // Output: [0 1 2 3 4 5 6] } collection-2.0.0/lru_cache.go000066400000000000000000000054141503143002500161150ustar00rootroot00000000000000package collection import ( "context" "sync" ) // LRUCache hosts up to a given number of items. When more are presented, the least recently used item // is evicted from the cache. type LRUCache[K comparable, V any] struct { capacity uint entries map[K]*lruEntry[K, V] touched *LinkedList[*lruEntry[K, V]] key sync.RWMutex } type lruEntry[K any, V any] struct { Node *llNode[*lruEntry[K, V]] Key K Value V } // NewLRUCache creates an empty cache, which will accommodate the given number of items. func NewLRUCache[K comparable, V any](capacity uint) *LRUCache[K, V] { return &LRUCache[K, V]{ capacity: capacity, entries: make(map[K]*lruEntry[K, V], capacity+1), touched: NewLinkedList[*lruEntry[K, V]](), } } // Put adds a value to the cache. The added value may be expelled without warning. func (lru *LRUCache[K, V]) Put(key K, value V) { lru.key.Lock() defer lru.key.Unlock() entry, ok := lru.entries[key] if ok { lru.touched.removeNode(entry.Node) } else { entry = &lruEntry[K, V]{ Node: &llNode[*lruEntry[K, V]]{}, Key: key, } } entry.Node.payload = entry entry.Value = value lru.touched.addNodeFront(entry.Node) lru.entries[key] = entry if lru.touched.Length() > lru.capacity { removed, ok := lru.touched.RemoveBack() if ok { delete(lru.entries, removed.Key) } } } // Get retrieves a cached value, if it is still present. func (lru *LRUCache[K, V]) Get(key K) (V, bool) { lru.key.RLock() defer lru.key.RUnlock() entry, ok := lru.entries[key] if !ok { return *new(V), false } lru.touched.removeNode(entry.Node) lru.touched.addNodeFront(entry.Node) return entry.Node.payload.Value, true } // Remove explicitly takes an item out of the cache. func (lru *LRUCache[K, V]) Remove(key K) bool { lru.key.RLock() defer lru.key.RUnlock() entry, ok := lru.entries[key] if !ok { return false } lru.touched.removeNode(entry.Node) delete(lru.entries, key) return true } // Enumerate lists each value in the cache. func (lru *LRUCache[K, V]) Enumerate(ctx context.Context) Enumerator[V] { retval := make(chan V) nested := lru.touched.Enumerate(ctx) go func() { lru.key.RLock() defer lru.key.RUnlock() defer close(retval) for entry := range nested { select { case retval <- entry.Value: // Intentionally Left Blank case <-ctx.Done(): return } } }() return retval } // EnumerateKeys lists each key in the cache. func (lru *LRUCache[K, V]) EnumerateKeys(ctx context.Context) Enumerator[K] { retval := make(chan K) nested := lru.touched.Enumerate(ctx) go func() { lru.key.RLock() defer lru.key.RUnlock() defer close(retval) for entry := range nested { select { case retval <- entry.Key: // Intentionally Left Blank case <-ctx.Done(): return } } }() return retval } collection-2.0.0/lru_cache_test.go000066400000000000000000000021321503143002500171460ustar00rootroot00000000000000package collection import "testing" func TestLRUCache_Put_replace(t *testing.T) { const key = 1 const firstPut = "first" const secondPut = "second" subject := NewLRUCache[int, string](10) subject.Put(key, firstPut) subject.Put(key, secondPut) want := secondPut got, ok := subject.Get(key) if !ok { t.Logf("key should have been present") t.Fail() } if got != want { t.Logf("Unexpected result\n\tgot: %s\n\twant: %s", got, want) t.Fail() } } func TestLRUCache_Remove_empty(t *testing.T) { subject := NewLRUCache[int, int](10) got := subject.Remove(7) if got != false { t.Fail() } } func TestLRUCache_Remove_present(t *testing.T) { const key = 10 subject := NewLRUCache[int, string](6) subject.Put(key, "ten") ok := subject.Remove(key) if !ok { t.Fail() } _, ok = subject.Get(key) if ok { t.Fail() } } func TestLRUCache_Remove_notPresent(t *testing.T) { const key1 = 10 const key2 = key1 + 1 subject := NewLRUCache[int, string](6) subject.Put(key2, "eleven") ok := subject.Remove(key1) if ok { t.Fail() } _, ok = subject.Get(key2) if !ok { t.Fail() } } collection-2.0.0/lru_example_test.go000066400000000000000000000017121503143002500175410ustar00rootroot00000000000000package collection_test import ( "context" "fmt" "github.com/marstr/collection/v2" ) func ExampleLRUCache() { subject := collection.NewLRUCache[int, string](3) subject.Put(1, "one") subject.Put(2, "two") subject.Put(3, "three") subject.Put(4, "four") fmt.Println(subject.Get(1)) fmt.Println(subject.Get(4)) // Output: // false // four true } func ExampleLRUCache_Enumerate() { subject := collection.NewLRUCache[int, string](3) subject.Put(1, "one") subject.Put(2, "two") subject.Put(3, "three") subject.Put(4, "four") for key := range subject.Enumerate(context.Background()) { fmt.Println(key) } // Output: // four // three // two } func ExampleLRUCache_EnumerateKeys() { subject := collection.NewLRUCache[int, string](3) subject.Put(1, "one") subject.Put(2, "two") subject.Put(3, "three") subject.Put(4, "four") for key := range subject.EnumerateKeys(context.Background()) { fmt.Println(key) } // Output: // 4 // 3 // 2 } collection-2.0.0/query.go000066400000000000000000000414071503143002500153370ustar00rootroot00000000000000package collection import ( "context" "errors" "runtime" "sync" ) // Enumerable offers a means of easily converting into a channel. It is most // useful for types where mutability is not in question. type Enumerable[T any] interface { Enumerate(ctx context.Context) Enumerator[T] } // Enumerator exposes a new syntax for querying familiar data structures. type Enumerator[T any] <-chan T // Predicate defines an interface for funcs that make some logical test. type Predicate[T any] func(T) bool // Transform defines a function which takes a value, and returns some value based on the original. type Transform[T any, E any] func(T) E // Unfolder defines a function which takes a single value, and exposes many of them as an Enumerator type Unfolder[T any, E any] func(T) Enumerator[E] type emptyEnumerable[T any] struct{} var ( errNoElements = errors.New("enumerator encountered no elements") errMultipleElements = errors.New("enumerator encountered multiple elements") ) // IsErrorNoElements determines whethr or not the given error is the result of no values being // returned when one or more were expected. func IsErrorNoElements(err error) bool { return err == errNoElements } // IsErrorMultipleElements determines whether or not the given error is the result of multiple values // being returned when one or zero were expected. func IsErrorMultipleElements(err error) bool { return err == errMultipleElements } // Identity returns a trivial Transform which applies no operation on the value. func Identity[T any]() Transform[T, T] { return func(value T) T { return value } } func Empty[T any]() Enumerable[T] { return &emptyEnumerable[T]{} } func (e emptyEnumerable[T]) Enumerate(ctx context.Context) Enumerator[T] { results := make(chan T) close(results) return results } // All tests whether or not all items present in an Enumerable meet a criteria. func All[T any](subject Enumerable[T], p Predicate[T]) bool { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return subject.Enumerate(ctx).All(p) } // All tests whether or not all items present meet a criteria. func (iter Enumerator[T]) All(p Predicate[T]) bool { for entry := range iter { if !p(entry) { return false } } return true } // Any tests an Enumerable to see if there are any elements present. func Any[T any](iterator Enumerable[T]) bool { ctx, cancel := context.WithCancel(context.Background()) defer cancel() for range iterator.Enumerate(ctx) { return true } return false } // Anyp tests an Enumerable to see if there are any elements present that meet a criteria. func Anyp[T any](iterator Enumerable[T], p Predicate[T]) bool { ctx, cancel := context.WithCancel(context.Background()) defer cancel() for element := range iterator.Enumerate(ctx) { if p(element) { return true } } return false } type EnumerableSlice[T any] []T func (f EnumerableSlice[T]) Enumerate(ctx context.Context) Enumerator[T] { results := make(chan T) go func() { defer close(results) for _, entry := range f { select { case results <- entry: // Intentionally Left Blank case <-ctx.Done(): return } } }() return results } // AsEnumerable allows for easy conversion of a slice to a re-usable Enumerable object. func AsEnumerable[T any](entries ...T) Enumerable[T] { return EnumerableSlice[T](entries) } // AsEnumerable stores the results of an Enumerator so the results can be enumerated over repeatedly. func (iter Enumerator[T]) AsEnumerable() Enumerable[T] { return EnumerableSlice[T](iter.ToSlice()) } // Count iterates over a list and keeps a running tally of the number of elements which satisfy a predicate. func Count[T any](iter Enumerable[T], p Predicate[T]) int { return iter.Enumerate(context.Background()).Count(p) } // Count iterates over a list and keeps a running tally of the number of elements // satisfy a predicate. func (iter Enumerator[T]) Count(p Predicate[T]) int { tally := 0 for entry := range iter { if p(entry) { tally++ } } return tally } // CountAll iterates over a list and keeps a running tally of how many it's seen. func CountAll[T any](iter Enumerable[T]) int { return iter.Enumerate(context.Background()).CountAll() } // CountAll iterates over a list and keeps a running tally of how many it's seen. func (iter Enumerator[T]) CountAll() int { tally := 0 for range iter { tally++ } return tally } // Discard reads an enumerator to the end but does nothing with it. // This method should be used in circumstances when it doesn't make sense to explicitly cancel the Enumeration. func (iter Enumerator[T]) Discard() { for range iter { // Intentionally Left Blank } } // ElementAt retreives an item at a particular position in an Enumerator. func ElementAt[T any](iter Enumerable[T], n uint) T { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return iter.Enumerate(ctx).ElementAt(n) } // ElementAt retreives an item at a particular position in an Enumerator. func (iter Enumerator[T]) ElementAt(n uint) T { for i := uint(0); i < n; i++ { <-iter } return <-iter } // First retrieves just the first item in the list, or returns an error if there are no elements in the array. func First[T any](subject Enumerable[T]) (retval T, err error) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() err = errNoElements var isOpen bool if retval, isOpen = <-subject.Enumerate(ctx); isOpen { err = nil } return } // Last retreives the item logically behind all other elements in the list. func Last[T any](iter Enumerable[T]) T { return iter.Enumerate(context.Background()).Last() } // Last retreives the item logically behind all other elements in the list. func (iter Enumerator[T]) Last() (retval T) { for retval = range iter { // Intentionally Left Blank } return } type merger[T any] struct { originals []Enumerable[T] } func (m merger[T]) Enumerate(ctx context.Context) Enumerator[T] { retval := make(chan T) var wg sync.WaitGroup wg.Add(len(m.originals)) for _, item := range m.originals { go func(input Enumerable[T]) { defer wg.Done() for value := range input.Enumerate(ctx) { retval <- value } }(item) } go func() { wg.Wait() close(retval) }() return retval } // Merge takes the results as it receives them from several channels and directs // them into a single channel. func Merge[T any](channels ...Enumerable[T]) Enumerable[T] { return merger[T]{ originals: channels, } } // Merge takes the results of this Enumerator and others, and funnels them into // a single Enumerator. The order of in which they will be combined is non-deterministic. func (iter Enumerator[T]) Merge(others ...Enumerator[T]) Enumerator[T] { retval := make(chan T) var wg sync.WaitGroup wg.Add(len(others) + 1) funnel := func(prevResult Enumerator[T]) { for entry := range prevResult { retval <- entry } wg.Done() } go funnel(iter) for _, item := range others { go funnel(item) } go func() { wg.Wait() close(retval) }() return retval } type parallelSelecter[T any, E any] struct { original Enumerable[T] operation Transform[T, E] } func (ps parallelSelecter[T, E]) Enumerate(ctx context.Context) Enumerator[E] { iter := ps.original.Enumerate(ctx) if cpus := runtime.NumCPU(); cpus != 1 { intermediate := splitN(iter, ps.operation, uint(cpus)) return intermediate[0].Merge(intermediate[1:]...) } return Select(ps.original, ps.operation).Enumerate(ctx) } // ParallelSelect creates an Enumerable which will use all logically available CPUs to // execute a Transform. func ParallelSelect[T any, E any](original Enumerable[T], operation Transform[T, E]) Enumerable[E] { return parallelSelecter[T, E]{ original: original, operation: operation, } } // ParallelSelect will execute a Transform across all logical CPUs available to the current process. // // This is commented out, because Go 1.18 adds support for generics, but disallows methods from having type parameters // not declared by their receivers. // //func (iter Enumerator[T]) ParallelSelect[E any](operation Transform[T, E]) Enumerator[E] { // if cpus := runtime.NumCPU(); cpus != 1 { // intermediate := iter.splitN(operation, uint(cpus)) // return intermediate[0].Merge(intermediate[1:]...) // } // return iter //} type reverser[T any] struct { original Enumerable[T] } // Reverse will enumerate all values of an enumerable, store them in a Stack, then replay them all. func Reverse[T any](original Enumerable[T]) Enumerable[T] { return reverser[T]{ original: original, } } func (r reverser[T]) Enumerate(ctx context.Context) Enumerator[T] { return r.original.Enumerate(ctx).Reverse() } // Reverse returns items in the opposite order it encountered them in. func (iter Enumerator[T]) Reverse() Enumerator[T] { cache := NewStack[T]() for entry := range iter { cache.Push(entry) } retval := make(chan T) go func() { for !cache.IsEmpty() { val, _ := cache.Pop() retval <- val } close(retval) }() return retval } type selecter[T any, E any] struct { original Enumerable[T] transform Transform[T, E] } func (s selecter[T, E]) Enumerate(ctx context.Context) Enumerator[E] { retval := make(chan E) go func() { defer close(retval) for item := range s.original.Enumerate(ctx) { select { case retval <- s.transform(item): // Intentionally Left Blank case <-ctx.Done(): return } } }() return retval } // Select creates a reusable stream of transformed values. func Select[T any, E any](subject Enumerable[T], transform Transform[T, E]) Enumerable[E] { return selecter[T, E]{ original: subject, transform: transform, } } // Select iterates over a list and returns a transformed item. // // This is commented out because Go 1.18 added support for // //func (iter Enumerator[T]) Select[E any](transform Transform[T, E]) Enumerator[E] { // retval := make(chan interface{}) // // go func() { // for item := range iter { // retval <- transform(item) // } // close(retval) // }() // // return retval //} type selectManyer[T any, E any] struct { original Enumerable[T] toMany Unfolder[T, E] } func (s selectManyer[T, E]) Enumerate(ctx context.Context) Enumerator[E] { retval := make(chan E) go func() { for parent := range s.original.Enumerate(ctx) { for child := range s.toMany(parent) { retval <- child } } close(retval) }() return retval } // SelectMany allows for unfolding of values. func SelectMany[T any, E any](subject Enumerable[T], toMany Unfolder[T, E]) Enumerable[E] { return selectManyer[T, E]{ original: subject, toMany: toMany, } } //// SelectMany allows for flattening of data structures. //func (iter Enumerator[T]) SelectMany[E any](lister Unfolder[T, E]) Enumerator[E] { // retval := make(chan E) // // go func() { // for parent := range iter { // for child := range lister(parent) { // retval <- child // } // } // close(retval) // }() // // return retval //} // Single retreives the only element from a list, or returns nil and an error. func Single[T any](iter Enumerable[T]) (retval T, err error) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() err = errNoElements firstPass := true for entry := range iter.Enumerate(ctx) { if firstPass { retval = entry err = nil } else { retval = *new(T) err = errMultipleElements break } firstPass = false } return } // Singlep retrieces the only element from a list that matches a criteria. If // no match is found, or two or more are found, `Singlep` returns nil and an // error. func Singlep[T any](iter Enumerable[T], pred Predicate[T]) (retval T, err error) { iter = Where(iter, pred) return Single(iter) } type skipper[T any] struct { original Enumerable[T] skipCount uint } func (s skipper[T]) Enumerate(ctx context.Context) Enumerator[T] { return s.original.Enumerate(ctx).Skip(s.skipCount) } // Skip creates a reusable stream which will skip the first `n` elements before iterating // over the rest of the elements in an Enumerable. func Skip[T any](subject Enumerable[T], n uint) Enumerable[T] { return skipper[T]{ original: subject, skipCount: n, } } // Skip retreives all elements after the first 'n' elements. func (iter Enumerator[T]) Skip(n uint) Enumerator[T] { results := make(chan T) go func() { defer close(results) i := uint(0) for entry := range iter { if i < n { i++ continue } results <- entry } }() return results } // splitN creates N Enumerators, each will be a subset of the original Enumerator and will have // distinct populations from one another. func splitN[T any, E any](iter Enumerator[T], operation Transform[T, E], n uint) []Enumerator[E] { results, cast := make([]chan E, n), make([]Enumerator[E], n) for i := uint(0); i < n; i++ { results[i] = make(chan E) cast[i] = results[i] } go func() { for i := uint(0); i < n; i++ { go func(addr uint) { defer close(results[addr]) for { read, ok := <-iter if !ok { return } results[addr] <- operation(read) } }(i) } }() return cast } type taker[T any] struct { original Enumerable[T] n uint } func (t taker[T]) Enumerate(ctx context.Context) Enumerator[T] { return t.original.Enumerate(ctx).Take(t.n) } // Take retreives just the first `n` elements from an Enumerable. func Take[T any](subject Enumerable[T], n uint) Enumerable[T] { return taker[T]{ original: subject, n: n, } } // Take retreives just the first 'n' elements from an Enumerator. func (iter Enumerator[T]) Take(n uint) Enumerator[T] { results := make(chan T) go func() { defer close(results) i := uint(0) for entry := range iter { if i >= n { return } i++ results <- entry } }() return results } type takeWhiler[T any] struct { original Enumerable[T] criteria func(T, uint) bool } func (tw takeWhiler[T]) Enumerate(ctx context.Context) Enumerator[T] { return tw.original.Enumerate(ctx).TakeWhile(tw.criteria) } // TakeWhile creates a reusable stream which will halt once some criteria is no longer met. func TakeWhile[T any](subject Enumerable[T], criteria func(T, uint) bool) Enumerable[T] { return takeWhiler[T]{ original: subject, criteria: criteria, } } // TakeWhile continues returning items as long as 'criteria' holds true. func (iter Enumerator[T]) TakeWhile(criteria func(T, uint) bool) Enumerator[T] { results := make(chan T) go func() { defer close(results) i := uint(0) for entry := range iter { if !criteria(entry, i) { return } i++ results <- entry } }() return results } // Tee creates two Enumerators which will have identical contents as one another. func (iter Enumerator[T]) Tee() (Enumerator[T], Enumerator[T]) { left, right := make(chan T), make(chan T) go func() { for entry := range iter { left <- entry right <- entry } close(left) close(right) }() return left, right } // ToSlice places all iterated over values in a Slice for easy consumption. func ToSlice[T any](iter Enumerable[T]) []T { return iter.Enumerate(context.Background()).ToSlice() } // ToSlice places all iterated over values in a Slice for easy consumption. func (iter Enumerator[T]) ToSlice() []T { retval := make([]T, 0) for entry := range iter { retval = append(retval, entry) } return retval } type wherer[T any] struct { original Enumerable[T] filter Predicate[T] } func (w wherer[T]) Enumerate(ctx context.Context) Enumerator[T] { retval := make(chan T) go func() { defer close(retval) for entry := range w.original.Enumerate(ctx) { if w.filter(entry) { retval <- entry } } }() return retval } // Where creates a reusable means of filtering a stream. func Where[T any](original Enumerable[T], p Predicate[T]) Enumerable[T] { return wherer[T]{ original: original, filter: p, } } // Where iterates over a list and returns only the elements that satisfy a // predicate. func (iter Enumerator[T]) Where(predicate Predicate[T]) Enumerator[T] { retval := make(chan T) go func() { for item := range iter { if predicate(item) { retval <- item } } close(retval) }() return retval } // UCount iterates over a list and keeps a running tally of the number of elements // satisfy a predicate. func UCount[T any](iter Enumerable[T], p Predicate[T]) uint { return iter.Enumerate(context.Background()).UCount(p) } // UCount iterates over a list and keeps a running tally of the number of elements // satisfy a predicate. func (iter Enumerator[T]) UCount(p Predicate[T]) uint { tally := uint(0) for entry := range iter { if p(entry) { tally++ } } return tally } // UCountAll iterates over a list and keeps a running tally of how many it's seen. func UCountAll[T any](iter Enumerable[T]) uint { return iter.Enumerate(context.Background()).UCountAll() } // UCountAll iterates over a list and keeps a running tally of how many it's seen. func (iter Enumerator[T]) UCountAll() uint { tally := uint(0) for range iter { tally++ } return tally } collection-2.0.0/query_examples_test.go000066400000000000000000000164601503143002500202750ustar00rootroot00000000000000package collection_test import ( "context" "fmt" "sync" "github.com/marstr/collection/v2" ) func ExampleEnumerableSlice_Enumerate() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 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. originalInts := []int{1, 2, 3, 4, 5} wrappedInts := collection.EnumerableSlice[int](originalInts) for entry := range wrappedInts.Enumerate(ctx) { fmt.Print(entry) } fmt.Println() // It's easy to convert arrays to slices for these enumerations as well. originalStrings := [7]string{"red", "orange", "yellow", "green", "blue", "indigo", "violet"} wrappedStrings := collection.EnumerableSlice[string](originalStrings[:]) for entry := range wrappedStrings.Enumerate(ctx) { fmt.Println(entry) } // Output: // 12345 // red // orange // yellow // green // blue // indigo // violet } func ExampleEnumerator_Count() { subject := collection.AsEnumerable("str1", "str1", "str2") count1 := subject.Enumerate(context.Background()).Count(func(a string) bool { return a == "str1" }) fmt.Println(count1) // Output: 2 } func ExampleEnumerator_CountAll() { subject := collection.AsEnumerable('a', 'b', 'c', 'd', 'e') fmt.Println(subject.Enumerate(context.Background()).CountAll()) // Output: 5 } func ExampleEnumerator_ElementAt() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // ElementAt leaves the Enumerator open, creating a memory leak unless remediated, // context.Context should be cancelled to indicate that no further reads are coming. fmt.Print(collection.Fibonacci.Enumerate(ctx).ElementAt(4)) // Output: 3 } func ExampleFirst() { empty := collection.NewQueue[int]() notEmpty := collection.NewQueue(1, 2, 3, 4) fmt.Println(collection.First[int](empty)) fmt.Println(collection.First[int](notEmpty)) // Output: // 0 enumerator encountered no elements // 1 } func ExampleLast() { subject := collection.NewList(1, 2, 3, 4) fmt.Println(collection.Last[int](subject)) // Output: 4 } func ExampleEnumerator_Last() { subject := collection.AsEnumerable(1, 2, 3) fmt.Print(subject.Enumerate(context.Background()).Last()) //Output: 3 } func ExampleMerge() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() a := collection.AsEnumerable(1, 2, 4) b := collection.AsEnumerable(8, 16, 32) c := collection.Merge(a, b) sum := 0 for x := range c.Enumerate(ctx) { sum += x } fmt.Println(sum) product := 1 for y := range a.Enumerate(ctx) { product *= y } fmt.Println(product) // Output: // 63 // 8 } func ExampleEnumerator_Reverse() { a := collection.AsEnumerable(1, 2, 3).Enumerate(context.Background()) a = a.Reverse() fmt.Println(a.ToSlice()) // Output: [3 2 1] } func ExampleSelect() { const offset = 'a' - 1 subject := collection.AsEnumerable[rune]('a', 'b', 'c') subject = collection.Select(subject, func(a rune) rune { return a - offset }) fmt.Println(collection.ToSlice(subject)) // Output: [1 2 3] } func ExampleSelectMany() { type BrewHouse struct { Name string Beers collection.Enumerable[string] } breweries := collection.AsEnumerable( BrewHouse{ "Mac & Jacks", collection.AsEnumerable( "African Amber", "Ibis IPA", ), }, BrewHouse{ "Post Doc", collection.AsEnumerable( "Prereq Pale", ), }, BrewHouse{ "Resonate", collection.AsEnumerable( "Comfortably Numb IPA", "Lithium Altbier", ), }, BrewHouse{ "Triplehorn", collection.AsEnumerable( "Samson", "Pepper Belly", ), }, ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() beers := collection.SelectMany(breweries, func(brewer BrewHouse) collection.Enumerator[string] { return brewer.Beers.Enumerate(ctx) }) for beer := range beers.Enumerate(ctx) { fmt.Println(beer) } // Output: // African Amber // Ibis IPA // Prereq Pale // Comfortably Numb IPA // Lithium Altbier // Samson // Pepper Belly } func ExampleSkip() { trimmed := collection.Take(collection.Skip(collection.Fibonacci, 1), 3) for entry := range trimmed.Enumerate(context.Background()) { fmt.Println(entry) } // Output: // 1 // 1 // 2 } func ExampleEnumerator_Skip() { subject := collection.AsEnumerable(1, 2, 3, 4, 5, 6, 7) skipped := subject.Enumerate(context.Background()).Skip(5) for entry := range skipped { fmt.Println(entry) } // Output: // 6 // 7 } func ExampleTake() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() taken := collection.Take(collection.Fibonacci, 4) for entry := range taken.Enumerate(ctx) { fmt.Println(entry) } // Output: // 0 // 1 // 1 // 2 } func ExampleEnumerator_Take() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() taken := collection.Fibonacci.Enumerate(ctx).Skip(4).Take(2) for entry := range taken { fmt.Println(entry) } // Output: // 3 // 5 } func ExampleTakeWhile() { taken := collection.TakeWhile(collection.Fibonacci, func(x, n uint) bool { return x < 10 }) ctx, cancel := context.WithCancel(context.Background()) defer cancel() for entry := range taken.Enumerate(ctx) { fmt.Println(entry) } // Output: // 0 // 1 // 1 // 2 // 3 // 5 // 8 } func ExampleEnumerator_TakeWhile() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() taken := collection.Fibonacci.Enumerate(ctx).TakeWhile(func(x, n uint) bool { return x < 6 }) for entry := range taken { fmt.Println(entry) } // Output: // 0 // 1 // 1 // 2 // 3 // 5 } func ExampleEnumerator_Tee() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() base := collection.AsEnumerable(1, 2, 4) left, right := base.Enumerate(ctx).Tee() var wg sync.WaitGroup wg.Add(2) product := 1 go func() { for x := range left { product *= x } wg.Done() }() sum := 0 go func() { for x := range right { sum += x } wg.Done() }() wg.Wait() fmt.Printf("Sum: %d\n", sum) fmt.Printf("Product: %d\n", product) // Output: // Sum: 7 // Product: 8 } func ExampleUCount() { subject := collection.NewStack[any](9, 'a', "str1") result := collection.UCount[interface{}](subject, func(a interface{}) bool { _, ok := a.(string) return ok }) fmt.Println(result) // Output: 1 } func ExampleEnumerator_UCount() { subject := collection.EnumerableSlice[string]([]string{"str1", "str1", "str2"}) count1 := subject.Enumerate(context.Background()).UCount(func(a string) bool { return a == "str1" }) fmt.Println(count1) // Output: 2 } func ExampleUCountAll() { subject := collection.NewStack(8, 9, 10, 11) fmt.Println(collection.UCountAll[int](subject)) // Output: 4 } func ExampleEnumerator_UCountAll() { subject := collection.EnumerableSlice[any]([]interface{}{'a', 2, "str1"}) fmt.Println(subject.Enumerate(context.Background()).UCountAll()) // Output: 3 } func ExampleEnumerator_Where() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() results := collection.Fibonacci.Enumerate(ctx).Where(func(a uint) bool { return a > 8 }).Take(3) fmt.Println(results.ToSlice()) // Output: [13 21 34] } func ExampleWhere() { nums := collection.EnumerableSlice[int]([]int{1, 2, 3, 4, 5}) results := collection.Where[int](nums, func(a int) bool { return a < 3 }) fmt.Println(collection.ToSlice(results)) // Output: [1 2] } collection-2.0.0/query_test.go000066400000000000000000000020551503143002500163720ustar00rootroot00000000000000package collection import ( "context" "testing" "time" ) func Test_Empty(t *testing.T) { if Any(Empty[int]()) { t.Log("empty should not have any elements") t.Fail() } if CountAll(Empty[int]()) != 0 { t.Log("empty should have counted to zero elements") t.Fail() } alwaysTrue := func(x int) bool { return true } if Count(Empty[int](), alwaysTrue) != 0 { t.Log("empty should have counted to zero even when discriminating") t.Fail() } } func BenchmarkEnumerator_Sum(b *testing.B) { var nums EnumerableSlice[int] = getInitializedSequentialArray[int]() ctx, cancel := context.WithCancel(context.Background()) defer cancel() b.ResetTimer() for i := 0; i < b.N; i++ { slowNums := Select[int, int](nums, sleepIdentity[int]) for range slowNums.Enumerate(ctx) { // Intentionally Left Blank } } } func sleepIdentity[T any](val T) T { time.Sleep(2 * time.Millisecond) return val } func getInitializedSequentialArray[T ~int]() []T { rawNums := make([]T, 1000) for i := range rawNums { rawNums[i] = T(i + 1) } return rawNums } collection-2.0.0/queue.go000066400000000000000000000034151503143002500153130ustar00rootroot00000000000000package collection import ( "context" "sync" ) // Queue implements a basic FIFO structure. type Queue[T any] struct { underlyer *LinkedList[T] key sync.RWMutex } // NewQueue instantiates a new FIFO structure. func NewQueue[T any](entries ...T) *Queue[T] { retval := &Queue[T]{ underlyer: NewLinkedList[T](entries...), } return retval } // Add places an item at the back of the Queue. func (q *Queue[T]) Add(entry T) { q.key.Lock() defer q.key.Unlock() if nil == q.underlyer { q.underlyer = NewLinkedList[T]() } q.underlyer.AddBack(entry) } // Enumerate peeks at each element of this queue without mutating it. func (q *Queue[T]) Enumerate(ctx context.Context) Enumerator[T] { q.key.RLock() defer q.key.RUnlock() return q.underlyer.Enumerate(ctx) } // IsEmpty tests the Queue to determine if it is populate or not. func (q *Queue[T]) IsEmpty() bool { q.key.RLock() defer q.key.RUnlock() return q.underlyer == nil || q.underlyer.IsEmpty() } // Length returns the number of items in the Queue. func (q *Queue[T]) Length() uint { q.key.RLock() defer q.key.RUnlock() if nil == q.underlyer { return 0 } return q.underlyer.length } // Next removes and returns the next item in the Queue. func (q *Queue[T]) Next() (T, bool) { q.key.Lock() defer q.key.Unlock() if q.underlyer == nil { return *new(T), false } return q.underlyer.RemoveFront() } // Peek returns the next item in the Queue without removing it. func (q *Queue[T]) Peek() (T, bool) { q.key.RLock() defer q.key.RUnlock() if q.underlyer == nil { return *new(T), false } return q.underlyer.PeekFront() } // ToSlice converts a Queue into a slice. func (q *Queue[T]) ToSlice() []T { q.key.RLock() defer q.key.RUnlock() if q.underlyer == nil { return []T{} } return q.underlyer.ToSlice() } collection-2.0.0/queue_test.go000066400000000000000000000070061503143002500163520ustar00rootroot00000000000000package collection import ( "fmt" "testing" ) func ExampleQueue_Add() { subject := &Queue[int]{} subject.Add(1) subject.Add(2) res, _ := subject.Peek() fmt.Println(res) // Output: 1 } func ExampleNewQueue() { empty := NewQueue[int]() fmt.Println(empty.Length()) populated := NewQueue(1, 2, 3, 5, 8, 13) fmt.Println(populated.Length()) // Output: // 0 // 6 } func ExampleQueue_IsEmpty() { empty := NewQueue[int]() fmt.Println(empty.IsEmpty()) populated := NewQueue(1, 2, 3, 5, 8, 13) fmt.Println(populated.IsEmpty()) // Output: // true // false } func ExampleQueue_Next() { subject := NewQueue(1, 2, 3, 5, 8, 13) for !subject.IsEmpty() { val, _ := subject.Next() fmt.Println(val) } // Output: // 1 // 2 // 3 // 5 // 8 // 13 } func TestQueue_Length(t *testing.T) { empty := NewQueue[int]() if count := empty.Length(); count != 0 { t.Logf("got: %d\nwant: %d", count, 0) t.Fail() } // Not the type magic number you're thinking of! // https://en.wikipedia.org/wiki/1729_(number) single := NewQueue(1729) if count := single.Length(); count != 1 { t.Logf("got: %d\nwant: %d", count, 1) t.Fail() } expectedMany := []interface{}{'a', 'b', 'c', 'd', 'e', 'e', 'f', 'g'} many := NewQueue(expectedMany...) if count := many.Length(); count != uint(len(expectedMany)) { t.Logf("got: %d\nwant: %d", count, len(expectedMany)) } } func TestQueue_Length_NonConstructed(t *testing.T) { subject := &Queue[int]{} if got := subject.Length(); got != 0 { t.Logf("got: %d\nwant: %d", got, 0) t.Fail() } } func TestQueue_Next_NonConstructed(t *testing.T) { const expected = 0 subject := &Queue[int]{} if got, ok := subject.Next(); ok { t.Logf("Next should not have been ok") t.Fail() } else if got != expected { t.Logf("got: %v\nwant: %v", got, expected) t.Fail() } } func TestQueue_Peek_DoesntRemove(t *testing.T) { expected := []interface{}{1, 2, 3} subject := NewQueue(expected...) if result, ok := subject.Peek(); !ok { t.Logf("no item present") t.Fail() } else if result != expected[0] { t.Logf("got: %d\nwant: %d", result, 1) t.Fail() } else if count := subject.Length(); count != uint(len(expected)) { t.Logf("got: %d\nwant: %d", count, len(expected)) } } func TestQueue_Peek_NonConstructed(t *testing.T) { const expected = 0 subject := &Queue[int]{} if got, ok := subject.Peek(); ok { t.Logf("Peek should not have been ok") t.Fail() } else if got != expected { t.Logf("got: %v\nwant: %v", got, expected) t.Fail() } } func TestQueue_ToSlice(t *testing.T) { subject := NewQueue(0, 1, 1, 2, 3, 5) expectedSliceString := "[0 1 1 2 3 5]" if result := subject.ToSlice(); len(result) != 6 { t.Logf("got: %d\nwant: %d", len(result), 6) t.Fail() } else if fmt.Sprintf("%v", result) != expectedSliceString { t.Logf("got:\n%v\nwant:\n%s\n", result, expectedSliceString) t.Fail() } } func TestQueue_ToSlice_Empty(t *testing.T) { subject := NewQueue[int]() result := subject.ToSlice() if len(result) != 0 { t.Logf("result should have been empty") t.Fail() } expectedStr := "[]" resultStr := fmt.Sprintf("%v", result) if resultStr != expectedStr { t.Logf("got:\n%s\nwant:\n%s", resultStr, expectedStr) t.Fail() } } func TestQueue_ToSlice_NotConstructed(t *testing.T) { subject := &Queue[int]{} result := subject.ToSlice() if len(result) != 0 { t.Logf("result should have been empty") t.Fail() } expectedStr := "[]" resultStr := fmt.Sprintf("%v", result) if resultStr != expectedStr { t.Logf("got:\n%s\nwant:\n%s", resultStr, expectedStr) t.Fail() } } collection-2.0.0/stack.go000066400000000000000000000033341503143002500152740ustar00rootroot00000000000000package collection import ( "context" "sync" ) // Stack implements a basic FILO structure. type Stack[T any] struct { underlyer *LinkedList[T] key sync.RWMutex } // NewStack instantiates a new FILO structure. func NewStack[T any](entries ...T) *Stack[T] { retval := &Stack[T]{} retval.underlyer = NewLinkedList[T]() for _, entry := range entries { retval.Push(entry) } return retval } // Enumerate peeks at each element in the stack without mutating it. func (stack *Stack[T]) Enumerate(ctx context.Context) Enumerator[T] { stack.key.RLock() defer stack.key.RUnlock() return stack.underlyer.Enumerate(ctx) } // IsEmpty tests the Stack to determine if it is populate or not. func (stack *Stack[T]) IsEmpty() bool { stack.key.RLock() defer stack.key.RUnlock() return stack.underlyer == nil || stack.underlyer.IsEmpty() } // Push adds an entry to the top of the Stack. func (stack *Stack[T]) Push(entry T) { stack.key.Lock() defer stack.key.Unlock() if nil == stack.underlyer { stack.underlyer = NewLinkedList[T]() } stack.underlyer.AddFront(entry) } // Pop returns the entry at the top of the Stack then removes it. func (stack *Stack[T]) Pop() (T, bool) { stack.key.Lock() defer stack.key.Unlock() if nil == stack.underlyer { return *new(T), false } return stack.underlyer.RemoveFront() } // Peek returns the entry at the top of the Stack without removing it. func (stack *Stack[T]) Peek() (T, bool) { stack.key.RLock() defer stack.key.RUnlock() return stack.underlyer.PeekFront() } // Size returns the number of entries populating the Stack. func (stack *Stack[T]) Size() uint { stack.key.RLock() defer stack.key.RUnlock() if stack.underlyer == nil { return 0 } return stack.underlyer.Length() } collection-2.0.0/stack_test.go000066400000000000000000000031001503143002500163220ustar00rootroot00000000000000package collection import ( "fmt" "testing" ) func TestStack_NewStack_FromEmpty(t *testing.T) { subject := NewStack[string]() subject.Push("alfa") subject.Push("bravo") subject.Push("charlie") if result, ok := subject.Pop(); result != "charlie" || ok != true { t.Logf("got: %s %v\nwant: %s %v", result, ok, "charlie", true) t.Fail() } if result, ok := subject.Pop(); result != "bravo" || ok != true { t.Logf("got: %s %v\nwant: %s %v", result, ok, "bravo", true) t.Fail() } if result, ok := subject.Pop(); result != "alfa" || ok != true { t.Logf("got: %s %v\nwant: %s %v", result, ok, "alfa", true) t.Fail() } if !subject.IsEmpty() { t.Log("subject should have been empty.") t.Fail() } } func ExampleNewStack() { subject := NewStack(1, 2, 3) for !subject.IsEmpty() { val, _ := subject.Pop() fmt.Println(val) } // Output: // 3 // 2 // 1 } func TestStack_Push_NonConstructor(t *testing.T) { subject := &Stack[int]{} sizeAssertion := func(want uint) { if got := subject.Size(); got != want { t.Logf("got: %d\nwant:%d\n", got, want) t.Fail() } } sizeAssertion(0) subject.Push(1) sizeAssertion(1) subject.Push(2) sizeAssertion(2) if result, ok := subject.Pop(); !ok { t.Logf("Pop is not ok") t.Fail() } else if result != 2 { t.Logf("got: %d\nwant: %d", result, 2) t.Fail() } } func TestStack_Pop_NonConstructorEmpty(t *testing.T) { subject := &Stack[string]{} if result, ok := subject.Pop(); ok { t.Logf("Pop should not have been okay") t.Fail() } else if result != "" { t.Logf("got: %v\nwant: %v", result, nil) } } collection-2.0.0/testdata/000077500000000000000000000000001503143002500154465ustar00rootroot00000000000000collection-2.0.0/testdata/foo/000077500000000000000000000000001503143002500162315ustar00rootroot00000000000000collection-2.0.0/testdata/foo/a.txt000066400000000000000000000000001503143002500172000ustar00rootroot00000000000000collection-2.0.0/testdata/foo/bar/000077500000000000000000000000001503143002500167755ustar00rootroot00000000000000collection-2.0.0/testdata/foo/bar/b.txt000066400000000000000000000000001503143002500177450ustar00rootroot00000000000000collection-2.0.0/testdata/foo/c.txt000066400000000000000000000000001503143002500172020ustar00rootroot00000000000000