diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..d40bb66a --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,75 @@ +version: 2.1 + +jobs: + test: + parameters: + version: + type: string + default: "1.21" + docker: + - image: cimg/go:<> + environment: + TEST_RESULTS: /tmp/test-results + working_directory: ~/gods + steps: + - run: + name: Print Go version (go version) + command: | + go version + - checkout + - run: + name: Run tests + command: | + mkdir -p $TEST_RESULTS + go install gotest.tools/gotestsum@latest + go test -v ./... | go tool test2json > $TEST_RESULTS/test2json-output.json + gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml + - run: + name: Calculate test coverage + command: | + mkdir -p $TEST_RESULTS + go test -coverprofile=coverage.out ./... > /dev/null + go test -race -coverprofile=coverage.txt -covermode=atomic ./... > /dev/null + go tool cover -html=coverage.out -o coverage.html + mv coverage.html $TEST_RESULTS + - run: + name: Upload test coverage + command: | + bash <(curl -s https://codecov.io/bash) + - run: + name: Lint (golint) + command: | + go install golang.org/x/lint/golint@latest + golint -set_exit_status ./... + - run: + name: Enforce formatted code (go fmt) + command: | + ! go fmt ./... 2>&1 | read + - run: + name: Examine and report suspicious constructs (go vet) + command: | + go vet -v ./... + - run: + name: Calculate cyclomatic complexity (gocyclo) + command: | + go install github.com/fzipp/gocyclo/cmd/gocyclo@latest + gocyclo -avg -over 15 ../gods + - run: + name: Check for unchecked errors (errcheck) + command: | + go install github.com/kisielk/errcheck@latest + errcheck ./... + - store_artifacts: + path: /tmp/test-results + destination: raw-test-output + - store_test_results: + path: /tmp/test-results + +workflows: + test: + jobs: + - test: + matrix: + parameters: + # To test with and without generics (versions prior to 1.18) + version: [ "1.21" ] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b9f86196 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +# Ref: https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..d0649bf9 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '30 4 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # πŸ“š https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ff2c2cd1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: go -go: - - 1.4 - - tip diff --git a/LICENSE b/LICENSE index 89e196c3..e5e449b6 100644 --- a/LICENSE +++ b/LICENSE @@ -20,4 +20,22 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- + +AVL Tree: + +Copyright (c) 2017 Benjamin Scher Purcell + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md index ff6ad5bd..a584a2ae 100644 --- a/README.md +++ b/README.md @@ -1,169 +1,149 @@ -[![Build Status](https://travis-ci.org/emirpasic/gods.svg)](https://travis-ci.org/emirpasic/gods) [![GoDoc](https://godoc.org/github.com/emirpasic/gods?status.svg)](https://godoc.org/github.com/emirpasic/gods) +[![GoDoc](https://godoc.org/github.com/emirpasic/gods?status.svg)](https://godoc.org/github.com/emirpasic/gods) +[![Build Status](https://circleci.com/gh/emirpasic/gods/tree/master.svg?style=shield)](https://circleci.com/gh/emirpasic/gods?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/emirpasic/gods)](https://goreportcard.com/report/github.com/emirpasic/gods) +[![codecov](https://codecov.io/gh/emirpasic/gods/branch/master/graph/badge.svg)](https://codecov.io/gh/emirpasic/gods) +[![Sourcegraph](https://sourcegraph.com/github.com/emirpasic/gods/-/badge.svg)](https://sourcegraph.com/github.com/emirpasic/gods?badge) +[![Release](https://img.shields.io/github/release/emirpasic/gods.svg?style=flat-square)](https://github.com/emirpasic/gods/releases) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gods&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=gods) +[![PyPI](https://img.shields.io/badge/License-BSD_2--Clause-green.svg)](https://github.com/emirpasic/gods/blob/master/LICENSE) # GoDS (Go Data Structures) -Implementation of various data structures in Go. +Implementation of various data structures and algorithms in Go. ## Data Structures - [Containers](#containers) - - [Sets](#sets) - - [HashSet](#hashset) - - [TreeSet](#treeset) - [Lists](#lists) - [ArrayList](#arraylist) - [SinglyLinkedList](#singlylinkedlist) - [DoublyLinkedList](#doublylinkedlist) + - [Sets](#sets) + - [HashSet](#hashset) + - [TreeSet](#treeset) + - [LinkedHashSet](#linkedhashset) - [Stacks](#stacks) - [LinkedListStack](#linkedliststack) - [ArrayStack](#arraystack) - [Maps](#maps) - [HashMap](#hashmap) - [TreeMap](#treemap) + - [LinkedHashMap](#linkedhashmap) + - [HashBidiMap](#hashbidimap) + - [TreeBidiMap](#treebidimap) - [Trees](#trees) - [RedBlackTree](#redblacktree) + - [AVLTree](#avltree) + - [BTree](#btree) - [BinaryHeap](#binaryheap) + - [Queues](#queues) + - [LinkedListQueue](#linkedlistqueue) + - [ArrayQueue](#arrayqueue) + - [CircularBuffer](#circularbuffer) + - [PriorityQueue](#priorityqueue) - [Functions](#functions) - [Comparator](#comparator) + - [Iterator](#iterator) + - [IteratorWithIndex](#iteratorwithindex) + - [IteratorWithKey](#iteratorwithkey) + - [ReverseIteratorWithIndex](#reverseiteratorwithindex) + - [ReverseIteratorWithKey](#reverseiteratorwithkey) + - [Enumerable](#enumerable) + - [EnumerableWithIndex](#enumerablewithindex) + - [EnumerableWithKey](#enumerablewithkey) + - [Serialization](#serialization) + - [JSONSerializer](#jsonserializer) + - [JSONDeserializer](#jsondeserializer) - [Sort](#sort) - + - [Container](#container) +- [Appendix](#appendix) -###Containers + +## Containers All data structures implement the container interface with the following methods: ```go -type Interface interface { +type Container interface { Empty() bool Size() int Clear() Values() []interface{} + String() string } - -``` - -Container specific operations: - -```go -// Returns sorted container's elements with respect to the passed comparator. -// Does not effect the ordering of elements within the container. -// Uses timsort. -func GetSortedValues(container Interface, comparator utils.Comparator) []interface{} { -``` - -####Sets - -A set is a data structure that can store elements and no repeated values. It is a computer implementation of the mathematical concept of a finite set. Unlike most other collection types, rather than retrieving a specific element from a set, one typically tests an element for membership in a set. This structed is often used to ensure that no duplicates are present in a collection. - -All sets implement the set interface with the following methods: - -```go -type Interface interface { - Add(elements ...interface{}) - Remove(elements ...interface{}) - Contains(elements ...interface{}) bool - - containers.Interface - // Empty() bool - // Size() int - // Clear() - // Values() []interface{} -} -``` - -#####HashSet - -This structure implements the Set interface and is backed by a hash table (actually a Go's map). It makes no guarantees as to the iteration order of the set, since Go randomizes this iteration order on maps. - -This structure offers constant time performance for the basic operations (add, remove, contains and size). - -```go -package main - -import "github.com/emirpasic/gods/sets/hashset" - -func main() { - set := hashset.New() // empty - set.Add(1) // 1 - set.Add(2, 2, 3, 4, 5) // 3, 1, 2, 4, 5 (random order, duplicates ignored) - set.Remove(4) // 5, 3, 2, 1 (random order) - set.Remove(2, 3) // 1, 5 (random order) - set.Contains(1) // true - set.Contains(1, 5) // true - set.Contains(1, 6) // false - _ = set.Values() // []int{5,1} (random order) - set.Clear() // empty - set.Empty() // true - set.Size() // 0 -} - - -``` - -#####TreeSet - -This structure implements the Set interface and is backed by a red-black tree to keep the elements sorted with respect to the comparator. - -This implementation provides guaranteed log(n) time cost for the basic operations (add, remove and contains). - -```go -package main - -import "github.com/emirpasic/gods/sets/treeset" - -func main() { - set := treeset.NewWithIntComparator() // empty (keys are of type int) - set.Add(1) // 1 - set.Add(2, 2, 3, 4, 5) // 1, 2, 3, 4, 5 (in order, duplicates ignored) - set.Remove(4) // 1, 2, 3, 5 (in order) - set.Remove(2, 3) // 1, 5 (in order) - set.Contains(1) // true - set.Contains(1, 5) // true - set.Contains(1, 6) // false - _ = set.Values() // []int{1,5} (in order) - set.Clear() // empty - set.Empty() // true - set.Size() // 0 -} - ``` -####Lists - -A list is a data structure that can store elements and may have repeated values. There is no ordering in a list. The user can access and remove an element by the index position. - -All lists implement the list interface with the following methods: +Containers are either ordered or unordered. All ordered containers provide [stateful iterators](#iterator) and some of them allow [enumerable functions](#enumerable). + +| **Data** | **Structure** | **Ordered** | **[Iterator](#iterator)** | **[Enumerable](#enumerable)** | **Referenced by** | +| :--- |:--------------------------------------| :---: | :---: | :---: | :---: | +| [Lists](#lists) | +| | [ArrayList](#arraylist) | yes | yes* | yes | index | +| | [SinglyLinkedList](#singlylinkedlist) | yes | yes | yes | index | +| | [DoublyLinkedList](#doublylinkedlist) | yes | yes* | yes | index | +| [Sets](#sets) | +| | [HashSet](#hashset) | no | no | no | index | +| | [TreeSet](#treeset) | yes | yes* | yes | index | +| | [LinkedHashSet](#linkedhashset) | yes | yes* | yes | index | +| [Stacks](#stacks) | +| | [LinkedListStack](#linkedliststack) | yes | yes | no | index | +| | [ArrayStack](#arraystack) | yes | yes* | no | index | +| [Maps](#maps) | +| | [HashMap](#hashmap) | no | no | no | key | +| | [TreeMap](#treemap) | yes | yes* | yes | key | +| | [LinkedHashMap](#linkedhashmap) | yes | yes* | yes | key | +| | [HashBidiMap](#hashbidimap) | no | no | no | key* | +| | [TreeBidiMap](#treebidimap) | yes | yes* | yes | key* | +| [Trees](#trees) | +| | [RedBlackTree](#redblacktree) | yes | yes* | no | key | +| | [AVLTree](#avltree) | yes | yes* | no | key | +| | [BTree](#btree) | yes | yes* | no | key | +| | [BinaryHeap](#binaryheap) | yes | yes* | no | index | +| [Queues](#queues) | +| | [LinkedListQueue](#linkedlistqueue) | yes | yes | no | index | +| | [ArrayQueue](#arrayqueue) | yes | yes* | no | index | +| | [CircularBuffer](#circularbuffer) | yes | yes* | no | index | +| | [PriorityQueue](#priorityqueue) | yes | yes* | no | index | +| | | | *reversible | | *bidirectional | + +### Lists + +A list is a data structure that stores values and may have repeated values. + +Implements [Container](#containers) interface. ```go -type Interface interface { - Get(index int) (interface{}, bool) +type List interface { + Get(index int) (interface{}, bool) Remove(index int) - Add(elements ...interface{}) - Contains(elements ...interface{}) bool + Add(values ...interface{}) + Contains(values ...interface{}) bool Sort(comparator utils.Comparator) - Swap(index1, index2 int) + Swap(index1, index2 int) + Insert(index int, values ...interface{}) + Set(index int, value interface{}) - containers.Interface + containers.Container // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string } ``` -#####ArrayList +#### ArrayList -This structure implements the List interface and is backed by a dynamic array that grows and shrinks implicitly (by 100% when capacity is reached). - -Direct access method _Get(index)_ is guaranteed a constant time performance. Remove is of linear time performance. Checking with _Contains()_ is of quadratic complexity. +A [list](#lists) backed by a dynamic array that grows and shrinks implicitly. +Implements [List](#lists), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [EnumerableWithIndex](#enumerablewithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. ```go package main import ( "github.com/emirpasic/gods/lists/arraylist" - "github.com/emirpasic/gods/utils" + "github.com/emirpasic/gods/utils" ) func main() { @@ -184,14 +164,16 @@ func main() { _ = list.Size() // 0 list.Add("a") // ["a"] list.Clear() // [] + list.Insert(0, "b") // ["b"] + list.Insert(0, "a") // ["a","b"] } ``` -#####SinglyLinkedList +#### SinglyLinkedList -This structure implements the _List_ interface and is a linked data structure where each element points to the next in the list. +A [list](#lists) where each element points to the next element in the list. -Direct access method _Get(index)_ and _Remove()_ are of linear performance. _Append_ and _Prepend_ are of constant time performance. Checking with _Contains()_ is of quadratic complexity. +Implements [List](#lists), [IteratorWithIndex](#iteratorwithindex), [EnumerableWithIndex](#enumerablewithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. ```go package main @@ -219,16 +201,16 @@ func main() { _ = list.Size() // 0 list.Add("a") // ["a"] list.Clear() // [] + list.Insert(0, "b") // ["b"] + list.Insert(0, "a") // ["a","b"] } - - ``` -#####DoublyLinkedList +#### DoublyLinkedList -This structure implements the _List_ interface and is a linked data structure where each element points to the next and previous element in the list. +A [list](#lists) where each element points to the next and previous elements in the list. -Direct access method _Get(index)_ and _Remove()_ are of linear performance. _Append_ and _Prepend_ are of constant time performance. Checking with _Contains()_ is of quadratic complexity. +Implements [List](#lists), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [EnumerableWithIndex](#enumerablewithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. ```go package main @@ -256,37 +238,145 @@ func main() { _ = list.Size() // 0 list.Add("a") // ["a"] list.Clear() // [] + list.Insert(0, "b") // ["b"] + list.Insert(0, "a") // ["a","b"] +} +``` + +### Sets + +A set is a data structure that can store elements and has no repeated values. It is a computer implementation of the mathematical concept of a finite set. Unlike most other collection types, rather than retrieving a specific element from a set, one typically tests an element for membership in a set. This structure is often used to ensure that no duplicates are present in a container. + +Set additionally allow set operations such as [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory)), [union](https://en.wikipedia.org/wiki/Union_(set_theory)), [difference](https://proofwiki.org/wiki/Definition:Set_Difference), etc. + +Implements [Container](#containers) interface. + +```go +type Set interface { + Add(elements ...interface{}) + Remove(elements ...interface{}) + Contains(elements ...interface{}) bool + // Intersection(another *Set) *Set + // Union(another *Set) *Set + // Difference(another *Set) *Set + + containers.Container + // Empty() bool + // Size() int + // Clear() + // Values() []interface{} + // String() string +} +``` + +#### HashSet + +A [set](#sets) backed by a hash table (actually a Go's map). It makes no guarantees as to the iteration order of the set. + +Implements [Set](#sets), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import "github.com/emirpasic/gods/sets/hashset" + +func main() { + set := hashset.New() // empty + set.Add(1) // 1 + set.Add(2, 2, 3, 4, 5) // 3, 1, 2, 4, 5 (random order, duplicates ignored) + set.Remove(4) // 5, 3, 2, 1 (random order) + set.Remove(2, 3) // 1, 5 (random order) + set.Contains(1) // true + set.Contains(1, 5) // true + set.Contains(1, 6) // false + _ = set.Values() // []int{5,1} (random order) + set.Clear() // empty + set.Empty() // true + set.Size() // 0 +} +``` + +#### TreeSet + +A [set](#sets) backed by a [red-black tree](#redblacktree) to keep the elements ordered with respect to the [comparator](#comparator). + +Implements [Set](#sets), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [EnumerableWithIndex](#enumerablewithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import "github.com/emirpasic/gods/sets/treeset" + +func main() { + set := treeset.NewWithIntComparator() // empty (keys are of type int) + set.Add(1) // 1 + set.Add(2, 2, 3, 4, 5) // 1, 2, 3, 4, 5 (in order, duplicates ignored) + set.Remove(4) // 1, 2, 3, 5 (in order) + set.Remove(2, 3) // 1, 5 (in order) + set.Contains(1) // true + set.Contains(1, 5) // true + set.Contains(1, 6) // false + _ = set.Values() // []int{1,5} (in order) + set.Clear() // empty + set.Empty() // true + set.Size() // 0 } +``` + +#### LinkedHashSet +A [set](#sets) that preserves insertion-order. Data structure is backed by a hash table to store values and [doubly-linked list](#doublylinkedlist) to store insertion ordering. +Implements [Set](#sets), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [EnumerableWithIndex](#enumerablewithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import "github.com/emirpasic/gods/sets/linkedhashset" + +func main() { + set := linkedhashset.New() // empty + set.Add(5) // 5 + set.Add(4, 4, 3, 2, 1) // 5, 4, 3, 2, 1 (in insertion-order, duplicates ignored) + set.Add(4) // 5, 4, 3, 2, 1 (duplicates ignored, insertion-order unchanged) + set.Remove(4) // 5, 3, 2, 1 (in insertion-order) + set.Remove(2, 3) // 5, 1 (in insertion-order) + set.Contains(1) // true + set.Contains(1, 5) // true + set.Contains(1, 6) // false + _ = set.Values() // []int{5, 1} (in insertion-order) + set.Clear() // empty + set.Empty() // true + set.Size() // 0 +} ``` +### Stacks -####Stacks +A stack that represents a last-in-first-out (LIFO) data structure. The usual push and pop operations are provided, as well as a method to peek at the top item on the stack. -The stack interface represents a last-in-first-out (LIFO) collection of objects. The usual push and pop operations are provided, as well as a method to peek at the top item on the stack, a method to check whether the stack is empty and the size (number of elements). +Implements [Container](#containers) interface. -All stacks implement the stack interface with the following methods: ```go -type Interface interface { +type Stack interface { Push(value interface{}) Pop() (value interface{}, ok bool) Peek() (value interface{}, ok bool) - containers.Interface + containers.Container // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string } - ``` -#####LinkedListStack +#### LinkedListStack -This stack structure is based on a linked list, i.e. each previous element has a point to the next. +A [stack](#stacks) based on a [linked list](#singlylinkedlist). -All operations are guaranted constant time performance, except _Values()_, which is as always of linear time performance. +Implements [Stack](#stacks), [IteratorWithIndex](#iteratorwithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. ```go package main @@ -307,14 +397,13 @@ func main() { stack.Empty() // true stack.Size() // 0 } - ``` -#####ArrayStack +#### ArrayStack -This stack structure is back by ArrayList. +A [stack](#stacks) based on a [array list](#arraylist). -All operations are guaranted constant time performance. +Implements [Stack](#stacks), [IteratorWithIndex](#iteratorwithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. ```go package main @@ -335,35 +424,45 @@ func main() { stack.Empty() // true stack.Size() // 0 } - - ``` -####Maps +### Maps + +A Map is a data structure that maps keys to values. A map cannot contain duplicate keys and each key can map to at most one value. -Structure that maps keys to values. A map cannot contain duplicate keys and each key can map to at most one value. +Implements [Container](#containers) interface. -All maps implement the map interface with the following methods: ```go -type Interface interface { - Put(key interface{}, value interface{}) +type Map interface { + Put(key interface{}, value interface{}) Get(key interface{}) (value interface{}, found bool) Remove(key interface{}) Keys() []interface{} - containers.Interface + containers.Container // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string +} +``` + +A BidiMap is an extension to the Map. A bidirectional map (BidiMap), also called a hash bag, is an associative data structure in which the key-value pairs form a one-to-one relation. This relation works in both directions by allow the value to also act as a key to key, e.g. a pair (a,b) thus provides a coupling between 'a' and 'b' so that 'b' can be found when 'a' is used as a key and 'a' can be found when 'b' is used as a key. + +```go +type BidiMap interface { + GetKey(value interface{}) (key interface{}, found bool) + + Map } ``` -#####HashMap +#### HashMap -Map structure based on hash tables, more exactly, Go's map. Keys are unordered. +A [map](#maps) based on hash tables. Keys are unordered. -All operations are guaranted constant time performance, except _Key()_ and _Values()_ retrieval that of linear time performance. +Implements [Map](#maps), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. ```go package main @@ -373,7 +472,7 @@ import "github.com/emirpasic/gods/maps/hashmap" func main() { m := hashmap.New() // empty m.Put(1, "x") // 1->x - m.Put(2, "b") // 2->b, 1->x (random order) + m.Put(2, "b") // 2->b, 1->x (random order) m.Put(1, "a") // 2->b, 1->a (random order) _, _ = m.Get(2) // b, true _, _ = m.Get(3) // nil, false @@ -384,16 +483,13 @@ func main() { m.Empty() // true m.Size() // 0 } - ``` -#####TreeMap - -Map structure based on our red-black tree implementation. Keys are ordered with respect to the passed comparator. +#### TreeMap -_Put()_, _Get()_ and _Remove()_ are guaranteed log(n) time performance. +A [map](#maps) based on [red-black tree](#redblacktree). Keys are ordered with respect to the [comparator](#comparator). -_Key()_ and _Values()_ methods return keys and values respectively in order of the keys. These meethods are quaranteed linear time performance. +Implements [Map](#maps), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [EnumerableWithKey](#enumerablewithkey), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. ```go package main @@ -413,33 +509,128 @@ func main() { m.Clear() // empty m.Empty() // true m.Size() // 0 + + // Other: + m.Min() // Returns the minimum key and its value from map. + m.Max() // Returns the maximum key and its value from map. +} +``` + +#### LinkedHashMap + +A [map](#maps) that preserves insertion-order. It is backed by a hash table to store values and [doubly-linked list](doublylinkedlist) to store ordering. + +Implements [Map](#maps), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [EnumerableWithKey](#enumerablewithkey), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import "github.com/emirpasic/gods/maps/linkedhashmap" + +func main() { + m := linkedhashmap.New() // empty (keys are of type int) + m.Put(2, "b") // 2->b + m.Put(1, "x") // 2->b, 1->x (insertion-order) + m.Put(1, "a") // 2->b, 1->a (insertion-order) + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"b", "a"} (insertion-order) + _ = m.Keys() // []interface {}{2, 1} (insertion-order) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 +} + +``` + +#### HashBidiMap + +A [map](#maps) based on two hashmaps. Keys are unordered. + +Implements [BidiMap](#maps), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import "github.com/emirpasic/gods/maps/hashbidimap" + +func main() { + m := hashbidimap.New() // empty + m.Put(1, "x") // 1->x + m.Put(3, "b") // 1->x, 3->b (random order) + m.Put(1, "a") // 1->a, 3->b (random order) + m.Put(2, "b") // 1->a, 2->b (random order) + _, _ = m.GetKey("a") // 1, true + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"a", "b"} (random order) + _ = m.Keys() // []interface {}{1, 2} (random order) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 } +``` + +#### TreeBidiMap + +A [map](#maps) based on red-black tree. This map guarantees that the map will be in both ascending key and value order. Other than key and value ordering, the goal with this structure is to avoid duplication of elements (unlike in [HashBidiMap](#hashbidimap)), which can be significant if contained elements are large. + +Implements [BidiMap](#maps), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [EnumerableWithKey](#enumerablewithkey), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. +```go +package main + +import ( + "github.com/emirpasic/gods/maps/treebidimap" + "github.com/emirpasic/gods/utils" +) +func main() { + m := treebidimap.NewWith(utils.IntComparator, utils.StringComparator) + m.Put(1, "x") // 1->x + m.Put(3, "b") // 1->x, 3->b (ordered) + m.Put(1, "a") // 1->a, 3->b (ordered) + m.Put(2, "b") // 1->a, 2->b (ordered) + _, _ = m.GetKey("a") // 1, true + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"a", "b"} (ordered) + _ = m.Keys() // []interface {}{1, 2} (ordered) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 +} ``` -####Trees +### Trees A tree is a widely used data data structure that simulates a hierarchical tree structure, with a root value and subtrees of children, represented as a set of linked nodes; thus no cyclic links. -All trees implement the tree interface with the following methods: +Implements [Container](#containers) interface. + ```go -type Interface interface { - containers.Interface +type Tree interface { + containers.Container // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string } ``` - -#####RedBlackTree -A red–black tree is a binary search tree with an extra bit of data per node, its color, which can be either red or black. The extra bit of storage ensures an approximately balanced tree by constraining how nodes are colored from any path from the root to the leaf. Thus, it is a data structure which is a type of self-balancing binary search tree. +#### RedBlackTree + +A red–black [tree](#trees) is a binary search tree with an extra bit of data per node, its color, which can be either red or black. The extra bit of storage ensures an approximately balanced tree by constraining how nodes are colored from any path from the root to the leaf. Thus, it is a data structure which is a type of self-balancing binary search tree. -The balancing of the tree is not perfect but it is good enough to allow it to guarantee searching in O(log n) time, where n is the total number of elements in the tree. The insertion and deletion operations, along with the tree rearrangement and recoloring, are also performed in O(log n) time.[Wikipedia](http://en.wikipedia.org/wiki/Red%E2%80%93black_tree) +The balancing of the tree is not perfect but it is good enough to allow it to guarantee searching in O(log n) time, where n is the total number of elements in the tree. The insertion and deletion operations, along with the tree rearrangement and recoloring, are also performed in O(log n) time. [Wikipedia](http://en.wikipedia.org/wiki/Red%E2%80%93black_tree) -
+Implements [Tree](#trees), [ReverseIteratorWithKey](#reverseiteratorwithkey), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +

```go package main @@ -450,7 +641,7 @@ import ( ) func main() { - tree := rbt.NewWithIntComparator() // empty(keys are of type int) + tree := rbt.NewWithIntComparator() // empty (keys are of type int) tree.Put(1, "x") // 1->x tree.Put(2, "b") // 1->x, 2->b (in order) @@ -460,7 +651,7 @@ func main() { tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) - fmt.Println(m) + fmt.Println(tree) // // RedBlackTree // β”‚ β”Œβ”€β”€ 6 @@ -474,7 +665,7 @@ func main() { _ = tree.Keys() // []interface {}{1, 2, 3, 4, 5, 6} (in order) tree.Remove(2) // 1->a, 3->c, 4->d, 5->e, 6->f (in order) - fmt.Println(m) + fmt.Println(tree) // // RedBlackTree // β”‚ β”Œβ”€β”€ 6 @@ -486,129 +677,426 @@ func main() { tree.Clear() // empty tree.Empty() // true tree.Size() // 0 -} + // Other: + tree.Left() // gets the left-most (min) node + tree.Right() // get the right-most (max) node + tree.Floor(1) // get the floor node + tree.Ceiling(1) // get the ceiling node +} ``` -#####BinaryHeap +Extending the red-black tree's functionality has been demonstrated in the following [example](https://github.com/emirpasic/gods/blob/master/examples/redblacktreeextended/redblacktreeextended.go). -A binary heap is a heap data structure created using a binary tree. It can be seen as a binary tree with two additional constraints: +#### AVLTree -- Shape property: +AVL [tree](#trees) is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property. Lookup, insertion, and deletion all take O(log n) time in both the average and worst cases, where n is the number of nodes in the tree prior to the operation. Insertions and deletions may require the tree to be rebalanced by one or more tree rotations. - A binary heap is a complete binary tree; that is, all levels of the tree, except possibly the last one (deepest) are fully filled, and, if the last level of the tree is not complete, the nodes of that level are filled from left to right. -- Heap property: +AVL trees are often compared with red–black trees because both support the same set of operations and take O(log n) time for the basic operations. For lookup-intensive applications, AVL trees are faster than red–black trees because they are more strictly balanced. [Wikipedia](https://en.wikipedia.org/wiki/AVL_tree) - All nodes are either greater than or equal to or less than or equal to each of its children, according to a comparison predicate defined for the heap. [Wikipedia](http://en.wikipedia.org/wiki/Binary_heap) +Implements [Tree](#trees), [ReverseIteratorWithKey](#reverseiteratorwithkey), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. -
+


AVL tree with balance factors (green)

```go package main import ( - "github.com/emirpasic/gods/trees/binaryheap" - "github.com/emirpasic/gods/utils" + "fmt" + avl "github.com/emirpasic/gods/trees/avltree" ) func main() { + tree := avl.NewWithIntComparator() // empty(keys are of type int) - // Min-heap - heap := binaryheap.NewWithIntComparator() // empty (min-heap) - heap.Push(2) // 2 - heap.Push(3) // 2, 3 - heap.Push(1) // 1, 3, 2 - heap.Values() // 1, 3, 2 - _, _ = heap.Peek() // 1,true - _, _ = heap.Pop() // 1, true - _, _ = heap.Pop() // 2, true - _, _ = heap.Pop() // 3, true - _, _ = heap.Pop() // nil, false (nothing to pop) - heap.Push(1) // 1 - heap.Clear() // empty - heap.Empty() // true - heap.Size() // 0 - - // Max-heap - inverseIntComparator := func(a, b interface{}) int { - return -utils.IntComparator(a, b) - } - heap = binaryheap.NewWith(inverseIntComparator) // empty (min-heap) - heap.Push(2) // 2 - heap.Push(3) // 3, 2 - heap.Push(1) // 3, 2, 1 - heap.Values() // 3, 2, 1 -} -``` + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) -### Functions + fmt.Println(tree) + // + // AVLTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 2 + // └── 1 -Various helper functions used throughout the library. -#### Comparator + _ = tree.Values() // []interface {}{"a", "b", "c", "d", "e", "f"} (in order) + _ = tree.Keys() // []interface {}{1, 2, 3, 4, 5, 6} (in order) -Some data structures (e.g. TreeMap, TreeSet) require a comparator function to sort their contained elements. This comparator is necessary during the initalization. + tree.Remove(2) // 1->a, 3->c, 4->d, 5->e, 6->f (in order) + fmt.Println(tree) + // + // AVLTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // └── 3 + // └── 1 -Comparator is defined as: + tree.Clear() // empty + tree.Empty() // true + tree.Size() // 0 +} +``` + +#### BTree + +B-tree is a self-balancing tree data structure that keeps data sorted and allows searches, sequential access, insertions, and deletions in logarithmic time. The B-tree is a generalization of a binary search tree in that a node can have more than two children. +According to Knuth's definition, a B-tree of order m is a tree which satisfies the following properties: + +- Every node has at most m children. +- Every non-leaf node (except root) has at least ⌈m/2βŒ‰ children. +- The root has at least two children if it is not a leaf node. +- A non-leaf node with k children contains kβˆ’1 keys. +- All leaves appear in the same level + +Each internal node’s keys act as separation values which divide its subtrees. For example, if an internal node has 3 child nodes (or subtrees) then it must have 2 keys: a1 and a2. All values in the leftmost subtree will be less than a1, all values in the middle subtree will be between a1 and a2, and all values in the rightmost subtree will be greater than a2.[Wikipedia](http://en.wikipedia.org/wiki/Red%E2%80%93black_tree) + +Implements [Tree](#trees), [ReverseIteratorWithKey](#reverseiteratorwithkey), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +

```go -Return values: +package main - -1, if a < b - 0, if a == b - 1, if a > b - -Comparator signature: +import ( + "fmt" + "github.com/emirpasic/gods/trees/btree" +) + +func main() { + tree := btree.NewWithIntComparator(3) // empty (keys are of type int) + + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + tree.Put(7, "g") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f, 7->g (in order) + + fmt.Println(tree) + // BTree + // 1 + // 2 + // 3 + // 4 + // 5 + // 6 + // 7 + + _ = tree.Values() // []interface {}{"a", "b", "c", "d", "e", "f", "g"} (in order) + _ = tree.Keys() // []interface {}{1, 2, 3, 4, 5, 6, 7} (in order) + + tree.Remove(2) // 1->a, 3->c, 4->d, 5->e, 6->f, 7->g (in order) + fmt.Println(tree) + // BTree + // 1 + // 3 + // 4 + // 5 + // 6 + // 7 - type Comparator func(a, b interface{}) int + tree.Clear() // empty + tree.Empty() // true + tree.Size() // 0 + + // Other: + tree.Height() // gets the height of the tree + tree.Left() // gets the left-most (min) node + tree.LeftKey() // get the left-most (min) node's key + tree.LeftValue() // get the left-most (min) node's value + tree.Right() // get the right-most (max) node + tree.RightKey() // get the right-most (max) node's key + tree.RightValue() // get the right-most (max) node's value +} ``` -Two common comparators are included in the library: +#### BinaryHeap + +A binary heap is a [tree](#trees) created using a binary tree. It can be seen as a binary tree with two additional constraints: + +- Shape property: + + A binary heap is a complete binary tree; that is, all levels of the tree, except possibly the last one (deepest) are fully filled, and, if the last level of the tree is not complete, the nodes of that level are filled from left to right. +- Heap property: + + All nodes are either greater than or equal to or less than or equal to each of its children, according to a comparison predicate defined for the heap. [Wikipedia](http://en.wikipedia.org/wiki/Binary_heap) + +Implements [Tree](#trees), [ReverseIteratorWithIndex](#reverseiteratorwithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +

-#####IntComparator ```go -func IntComparator(a, b interface{}) int { - aInt := a.(int) - bInt := b.(int) - switch { - case aInt > bInt: - return 1 - case aInt < bInt: - return -1 - default: - return 0 +package main + +import ( + "github.com/emirpasic/gods/trees/binaryheap" + "github.com/emirpasic/gods/utils" +) + +func main() { + + // Min-heap + heap := binaryheap.NewWithIntComparator() // empty (min-heap) + heap.Push(2) // 2 + heap.Push(3) // 2, 3 + heap.Push(1) // 1, 3, 2 + heap.Values() // 1, 3, 2 + _, _ = heap.Peek() // 1,true + _, _ = heap.Pop() // 1, true + _, _ = heap.Pop() // 2, true + _, _ = heap.Pop() // 3, true + _, _ = heap.Pop() // nil, false (nothing to pop) + heap.Push(1) // 1 + heap.Clear() // empty + heap.Empty() // true + heap.Size() // 0 + + // Max-heap + inverseIntComparator := func(a, b interface{}) int { + return -utils.IntComparator(a, b) } + heap = binaryheap.NewWith(inverseIntComparator) // empty (min-heap) + heap.Push(2, 3, 1) // 3, 2, 1 (bulk optimized) + heap.Values() // 3, 2, 1 +} +``` + +### Queues + +A queue that represents a first-in-first-out (FIFO) data structure. The usual enqueue and dequeue operations are provided, as well as a method to peek at the first item in the queue. + +

+ +Implements [Container](#containers) interface. + +```go +type Queue interface { + Enqueue(value interface{}) + Dequeue() (value interface{}, ok bool) + Peek() (value interface{}, ok bool) + + containers.Container + // Empty() bool + // Size() int + // Clear() + // Values() []interface{} + // String() string } ``` -#####StringComparator +#### LinkedListQueue + +A [queue](#queues) based on a [linked list](#singlylinkedlist). + +Implements [Queue](#queues), [IteratorWithIndex](#iteratorwithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + ```go -func StringComparator(a, b interface{}) int { - s1 := a.(string) - s2 := b.(string) - min := len(s2) - if len(s1) < len(s2) { - min = len(s1) - } - diff := 0 - for i := 0; i < min && diff == 0; i++ { - diff = int(s1[i]) - int(s2[i]) - } - if diff == 0 { - diff = len(s1) - len(s2) - } - if diff < 0 { - return -1 - } - if diff > 0 { - return 1 - } - return 0 +package main + +import llq "github.com/emirpasic/gods/queues/linkedlistqueue" + +// LinkedListQueueExample to demonstrate basic usage of LinkedListQueue +func main() { + queue := llq.New() // empty + queue.Enqueue(1) // 1 + queue.Enqueue(2) // 1, 2 + _ = queue.Values() // 1, 2 (FIFO order) + _, _ = queue.Peek() // 1,true + _, _ = queue.Dequeue() // 1, true + _, _ = queue.Dequeue() // 2, true + _, _ = queue.Dequeue() // nil, false (nothing to deque) + queue.Enqueue(1) // 1 + queue.Clear() // empty + queue.Empty() // true + _ = queue.Size() // 0 } ``` -#####CustomComparator +#### ArrayQueue + +A [queue](#queues) based on a [array list](#arraylist). + +Implements [Queue](#queues), [ReverseIteratorWithIndex](#iteratorwithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import aq "github.com/emirpasic/gods/queues/arrayqueue" + +// ArrayQueueExample to demonstrate basic usage of ArrayQueue +func main() { + queue := aq.New() // empty + queue.Enqueue(1) // 1 + queue.Enqueue(2) // 1, 2 + _ = queue.Values() // 1, 2 (FIFO order) + _, _ = queue.Peek() // 1,true + _, _ = queue.Dequeue() // 1, true + _, _ = queue.Dequeue() // 2, true + _, _ = queue.Dequeue() // nil, false (nothing to deque) + queue.Enqueue(1) // 1 + queue.Clear() // empty + queue.Empty() // true + _ = queue.Size() // 0 +} +``` + +#### CircularBuffer + +A circular buffer, circular [queue](#queues), cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. This structure lends itself easily to buffering data streams. + +

+ +Implements [Queue](#queues), [ReverseIteratorWithIndex](#iteratorwithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import cb "github.com/emirpasic/gods/queues/circularbuffer" + +// CircularBufferExample to demonstrate basic usage of CircularBuffer +func main() { + queue := cb.New(3) // empty (max size is 3) + queue.Enqueue(1) // 1 + queue.Enqueue(2) // 1, 2 + queue.Enqueue(3) // 1, 2, 3 + _ = queue.Values() // 1, 2, 3 + queue.Enqueue(3) // 4, 2, 3 + _, _ = queue.Peek() // 4,true + _, _ = queue.Dequeue() // 4, true + _, _ = queue.Dequeue() // 2, true + _, _ = queue.Dequeue() // 3, true + _, _ = queue.Dequeue() // nil, false (nothing to deque) + queue.Enqueue(1) // 1 + queue.Clear() // empty + queue.Empty() // true + _ = queue.Size() // 0 +} +``` + +#### PriorityQueue + +A priority queue is a special type of [queue](#queues) in which each element is associated with a priority value. And, elements are served on the basis of their priority. That is, higher priority elements are served first. However, if elements with the same priority occur, they are served according to their order in the queue. + +Implements [Queue](#queues), [ReverseIteratorWithIndex](#iteratorwithindex), [JSONSerializer](#jsonserializer) and [JSONDeserializer](#jsondeserializer) interfaces. + +```go +package main + +import ( + pq "github.com/emirpasic/gods/queues/priorityqueue" + "github.com/emirpasic/gods/utils" +) + +// Element is an entry in the priority queue +type Element struct { + name string + priority int +} + +// Comparator function (sort by element's priority value in descending order) +func byPriority(a, b interface{}) int { + priorityA := a.(Element).priority + priorityB := b.(Element).priority + return -utils.IntComparator(priorityA, priorityB) // "-" descending order +} + +// PriorityQueueExample to demonstrate basic usage of BinaryHeap +func main() { + a := Element{name: "a", priority: 1} + b := Element{name: "b", priority: 2} + c := Element{name: "c", priority: 3} + + queue := pq.NewWith(byPriority) // empty + queue.Enqueue(a) // {a 1} + queue.Enqueue(c) // {c 3}, {a 1} + queue.Enqueue(b) // {c 3}, {b 2}, {a 1} + _ = queue.Values() // [{c 3} {b 2} {a 1}] + _, _ = queue.Peek() // {c 3} true + _, _ = queue.Dequeue() // {c 3} true + _, _ = queue.Dequeue() // {b 2} true + _, _ = queue.Dequeue() // {a 1} true + _, _ = queue.Dequeue() // false (nothing to dequeue) + queue.Clear() // empty + _ = queue.Empty() // true + _ = queue.Size() // 0 +} +``` + +## Functions + +Various helper functions used throughout the library. + +### Comparator + +Some data structures (e.g. TreeMap, TreeSet) require a comparator function to automatically keep their elements sorted upon insertion. This comparator is necessary during the initalization. + +Comparator is defined as: + +Return values (int): + +```go +negative , if a < b +zero , if a == b +positive , if a > b +``` + +Comparator signature: + +```go +type Comparator func(a, b interface{}) int +``` + +All common comparators for builtin types are included in the library: + +```go +func StringComparator(a, b interface{}) int + +func IntComparator(a, b interface{}) int + +func Int8Comparator(a, b interface{}) int + +func Int16Comparator(a, b interface{}) int + +func Int32Comparator(a, b interface{}) int + +func Int64Comparator(a, b interface{}) int + +func UIntComparator(a, b interface{}) int + +func UInt8Comparator(a, b interface{}) int + +func UInt16Comparator(a, b interface{}) int + +func UInt32Comparator(a, b interface{}) int + +func UInt64Comparator(a, b interface{}) int + +func Float32Comparator(a, b interface{}) int + +func Float64Comparator(a, b interface{}) int + +func ByteComparator(a, b interface{}) int + +func RuneComparator(a, b interface{}) int + +func TimeComparator(a, b interface{}) int +``` + +Writing custom comparators is easy: + ```go package main @@ -622,7 +1110,7 @@ type User struct { name string } -// Comparator function (sort by IDs) +// Custom comparator (sort by IDs) func byID(a, b interface{}) int { // Type assertion, program will panic if this is not respected @@ -640,22 +1128,526 @@ func byID(a, b interface{}) int { } func main() { - set := treeset.NewWith(byID) + set := treeset.NewWith(byID) set.Add(User{2, "Second"}) set.Add(User{3, "Third"}) set.Add(User{1, "First"}) set.Add(User{4, "Fourth"}) - + fmt.Println(set) // {1 First}, {2 Second}, {3 Third}, {4 Fourth} } ``` -#### Sort +### Iterator + +All ordered containers have stateful iterators. Typically an iterator is obtained by _Iterator()_ function of an ordered container. Once obtained, iterator's _Next()_ function moves the iterator to the next element and returns true if there was a next element. If there was an element, then element's can be obtained by iterator's _Value()_ function. Depending on the ordering type, it's position can be obtained by iterator's _Index()_ or _Key()_ functions. Some containers even provide reversible iterators, essentially the same, but provide another extra _Prev()_ function that moves the iterator to the previous element and returns true if there was a previous element. + +Note: it is unsafe to remove elements from container while iterating. + +#### IteratorWithIndex + +An [iterator](#iterator) whose elements are referenced by an index. + +Typical usage: +```go +it := list.Iterator() +for it.Next() { + index, value := it.Index(), it.Value() + ... +} +``` + +Other usages: +```go +if it.First() { + firstIndex, firstValue := it.Index(), it.Value() + ... +} +``` + +```go +for it.Begin(); it.Next(); { + ... +} +``` + +Seeking to a specific element: + +```go +// Seek function, i.e. find element starting with "b" +seek := func(index int, value interface{}) bool { + return strings.HasSuffix(value.(string), "b") +} + +// Seek to the condition and continue traversal from that point (forward). +// assumes it.Begin() was called. +for found := it.NextTo(seek); found; found = it.Next() { + index, value := it.Index(), it.Value() + ... +} +``` + +#### IteratorWithKey + +An [iterator](#iterator) whose elements are referenced by a key. + +Typical usage: +```go +it := tree.Iterator() +for it.Next() { + key, value := it.Key(), it.Value() + ... +} +``` + +Other usages: +```go +if it.First() { + firstKey, firstValue := it.Key(), it.Value() + ... +} +``` + +```go +for it.Begin(); it.Next(); { + ... +} +``` + +Seeking to a specific element from the current iterator position: + +```go +// Seek function, i.e. find element starting with "b" +seek := func(key interface{}, value interface{}) bool { + return strings.HasSuffix(value.(string), "b") +} + +// Seek to the condition and continue traversal from that point (forward). +// assumes it.Begin() was called. +for found := it.NextTo(seek); found; found = it.Next() { + key, value := it.Key(), it.Value() + ... +} +``` + +#### ReverseIteratorWithIndex + +An [iterator](#iterator) whose elements are referenced by an index. Provides all functions as [IteratorWithIndex](#iteratorwithindex), but can also be used for reverse iteration. + +Typical usage of iteration in reverse: +```go +it := list.Iterator() +for it.End(); it.Prev(); { + index, value := it.Index(), it.Value() + ... +} +``` + +Other usages: +```go +if it.Last() { + lastIndex, lastValue := it.Index(), it.Value() + ... +} +``` + +Seeking to a specific element: + +```go +// Seek function, i.e. find element starting with "b" +seek := func(index int, value interface{}) bool { + return strings.HasSuffix(value.(string), "b") +} + +// Seek to the condition and continue traversal from that point (in reverse). +// assumes it.End() was called. +for found := it.PrevTo(seek); found; found = it.Prev() { + index, value := it.Index(), it.Value() + ... +} +``` + +#### ReverseIteratorWithKey + +An [iterator](#iterator) whose elements are referenced by a key. Provides all functions as [IteratorWithKey](#iteratorwithkey), but can also be used for reverse iteration. + +Typical usage of iteration in reverse: +```go +it := tree.Iterator() +for it.End(); it.Prev(); { + key, value := it.Key(), it.Value() + ... +} +``` + +Other usages: +```go +if it.Last() { + lastKey, lastValue := it.Key(), it.Value() + ... +} +``` + +```go +// Seek function, i.e. find element starting with "b" +seek := func(key interface{}, value interface{}) bool { + return strings.HasSuffix(value.(string), "b") +} + +// Seek to the condition and continue traversal from that point (in reverse). +// assumes it.End() was called. +for found := it.PrevTo(seek); found; found = it.Prev() { + key, value := it.Key(), it.Value() + ... +} +``` + +### Enumerable + +Enumerable functions for ordered containers that implement [EnumerableWithIndex](#enumerablewithindex) or [EnumerableWithKey](#enumerablewithkey) interfaces. + +#### EnumerableWithIndex + +[Enumerable](#enumerable) functions for ordered containers whose values can be fetched by an index. + +**Each** + +Calls the given function once for each element, passing that element's index and value. + +```go +Each(func(index int, value interface{})) +``` + +**Map** + +Invokes the given function once for each element and returns a container containing the values returned by the given function. + +```go +Map(func(index int, value interface{}) interface{}) Container +``` + +**Select** + +Returns a new container containing all elements for which the given function returns a true value. + +```go +Select(func(index int, value interface{}) bool) Container +``` + +**Any** + +Passes each element of the container to the given function and returns true if the function ever returns true for any element. + +```go +Any(func(index int, value interface{}) bool) bool +``` + +**All** + +Passes each element of the container to the given function and returns true if the function returns true for all elements. + +```go +All(func(index int, value interface{}) bool) bool +``` -Sort uses timsort for best performance on real-world data. Lists have an in-place _Sort()_ method. All containers can return their sorted elements via _GetSortedValues()_ call. +**Find** -Internally they use the _utils.Sort()_ method: +Passes each element of the container to the given function and returns the first (index,value) for which the function is true or -1,nil otherwise if no element matches the criteria. + +```go +Find(func(index int, value interface{}) bool) (int, interface{})} +``` + +**Example:** + +```go +package main + +import ( + "fmt" + "github.com/emirpasic/gods/sets/treeset" +) + +func printSet(txt string, set *treeset.Set) { + fmt.Print(txt, "[ ") + set.Each(func(index int, value interface{}) { + fmt.Print(value, " ") + }) + fmt.Println("]") +} + +func main() { + set := treeset.NewWithIntComparator() + set.Add(2, 3, 4, 2, 5, 6, 7, 8) + printSet("Initial", set) // [ 2 3 4 5 6 7 8 ] + + even := set.Select(func(index int, value interface{}) bool { + return value.(int)%2 == 0 + }) + printSet("Even numbers", even) // [ 2 4 6 8 ] + + foundIndex, foundValue := set.Find(func(index int, value interface{}) bool { + return value.(int)%2 == 0 && value.(int)%3 == 0 + }) + if foundIndex != -1 { + fmt.Println("Number divisible by 2 and 3 found is", foundValue, "at index", foundIndex) // value: 6, index: 4 + } + + square := set.Map(func(index int, value interface{}) interface{} { + return value.(int) * value.(int) + }) + printSet("Numbers squared", square) // [ 4 9 16 25 36 49 64 ] + + bigger := set.Any(func(index int, value interface{}) bool { + return value.(int) > 5 + }) + fmt.Println("Set contains a number bigger than 5 is ", bigger) // true + + positive := set.All(func(index int, value interface{}) bool { + return value.(int) > 0 + }) + fmt.Println("All numbers are positive is", positive) // true + + evenNumbersSquared := set.Select(func(index int, value interface{}) bool { + return value.(int)%2 == 0 + }).Map(func(index int, value interface{}) interface{} { + return value.(int) * value.(int) + }) + printSet("Chaining", evenNumbersSquared) // [ 4 16 36 64 ] +} +``` + +#### EnumerableWithKey + +Enumerable functions for ordered containers whose values whose elements are key/value pairs. + +**Each** + +Calls the given function once for each element, passing that element's key and value. + +```go +Each(func(key interface{}, value interface{})) +``` + +**Map** + +Invokes the given function once for each element and returns a container containing the values returned by the given function as key/value pairs. + +```go +Map(func(key interface{}, value interface{}) (interface{}, interface{})) Container +``` + +**Select** + +Returns a new container containing all elements for which the given function returns a true value. + +```go +Select(func(key interface{}, value interface{}) bool) Container +``` + +**Any** + +Passes each element of the container to the given function and returns true if the function ever returns true for any element. + +```go +Any(func(key interface{}, value interface{}) bool) bool +``` + +**All** + +Passes each element of the container to the given function and returns true if the function returns true for all elements. + +```go +All(func(key interface{}, value interface{}) bool) bool +``` + +**Find** + +Passes each element of the container to the given function and returns the first (key,value) for which the function is true or nil,nil otherwise if no element matches the criteria. + +```go +Find(func(key interface{}, value interface{}) bool) (interface{}, interface{}) +``` + +**Example:** + +```go +package main + +import ( + "fmt" + "github.com/emirpasic/gods/maps/treemap" +) + +func printMap(txt string, m *treemap.Map) { + fmt.Print(txt, " { ") + m.Each(func(key interface{}, value interface{}) { + fmt.Print(key, ":", value, " ") + }) + fmt.Println("}") +} + +func main() { + m := treemap.NewWithStringComparator() + m.Put("g", 7) + m.Put("f", 6) + m.Put("e", 5) + m.Put("d", 4) + m.Put("c", 3) + m.Put("b", 2) + m.Put("a", 1) + printMap("Initial", m) // { a:1 b:2 c:3 d:4 e:5 f:6 g:7 } + + even := m.Select(func(key interface{}, value interface{}) bool { + return value.(int) % 2 == 0 + }) + printMap("Elements with even values", even) // { b:2 d:4 f:6 } + + foundKey, foundValue := m.Find(func(key interface{}, value interface{}) bool { + return value.(int) % 2 == 0 && value.(int) % 3 == 0 + }) + if foundKey != nil { + fmt.Println("Element with value divisible by 2 and 3 found is", foundValue, "with key", foundKey) // value: 6, index: 4 + } + + square := m.Map(func(key interface{}, value interface{}) (interface{}, interface{}) { + return key.(string) + key.(string), value.(int) * value.(int) + }) + printMap("Elements' values squared and letters duplicated", square) // { aa:1 bb:4 cc:9 dd:16 ee:25 ff:36 gg:49 } + + bigger := m.Any(func(key interface{}, value interface{}) bool { + return value.(int) > 5 + }) + fmt.Println("Map contains element whose value is bigger than 5 is", bigger) // true + + positive := m.All(func(key interface{}, value interface{}) bool { + return value.(int) > 0 + }) + fmt.Println("All map's elements have positive values is", positive) // true + + evenNumbersSquared := m.Select(func(key interface{}, value interface{}) bool { + return value.(int) % 2 == 0 + }).Map(func(key interface{}, value interface{}) (interface{}, interface{}) { + return key, value.(int) * value.(int) + }) + printMap("Chaining", evenNumbersSquared) // { b:4 d:16 f:36 } +} +``` + +### Serialization + +All data structures can be serialized (marshalled) and deserialized (unmarshalled). Currently, only JSON support is available. + +#### JSONSerializer + +Outputs the container into its JSON representation. + +Typical usage for key-value structures: + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/emirpasic/gods/maps/hashmap" +) + +func main() { + m := hashmap.New() + m.Put("a", "1") + m.Put("b", "2") + m.Put("c", "3") + + bytes, err := json.Marshal(m) // Same as "m.ToJSON(m)" + if err != nil { + fmt.Println(err) + } + fmt.Println(string(bytes)) // {"a":"1","b":"2","c":"3"} +} +``` + +Typical usage for value-only structures: + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/emirpasic/gods/lists/arraylist" +) + +func main() { + list := arraylist.New() + list.Add("a", "b", "c") + + bytes, err := json.Marshal(list) // Same as "list.ToJSON(list)" + if err != nil { + fmt.Println(err) + } + fmt.Println(string(bytes)) // ["a","b","c"] +} +``` + +#### JSONDeserializer + +Populates the container with elements from the input JSON representation. + +Typical usage for key-value structures: + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/emirpasic/gods/maps/hashmap" +) + +func main() { + hm := hashmap.New() + + bytes := []byte(`{"a":"1","b":"2"}`) + err := json.Unmarshal(bytes, &hm) // Same as "hm.FromJSON(bytes)" + if err != nil { + fmt.Println(err) + } + fmt.Println(hm) // HashMap map[b:2 a:1] +} +``` + +Typical usage for value-only structures: + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/emirpasic/gods/lists/arraylist" +) + +func main() { + list := arraylist.New() + + bytes := []byte(`["a","b"]`) + err := json.Unmarshal(bytes, &list) // Same as "list.FromJSON(bytes)" + if err != nil { + fmt.Println(err) + } + fmt.Println(list) // ArrayList ["a","b"] +} +``` + +### Sort + +Sort is a general purpose sort function. + +Lists have an in-place _Sort()_ function and all containers can return their sorted elements via _containers.GetSortedValues()_ function. + +Internally these all use the _utils.Sort()_ method: ```go package main @@ -670,55 +1662,113 @@ func main() { strings = append(strings, "c") // ["d","a",b","c"] utils.Sort(strings, utils.StringComparator) // ["a","b","c","d"] } +``` + +### Container +Container specific operations: + +```go +// Returns sorted container''s elements with respect to the passed comparator. +// Does not affect the ordering of elements within the container. +func GetSortedValues(container Container, comparator utils.Comparator) []interface{} +``` + +Usage: + +```go +package main + +import ( + "github.com/emirpasic/gods/lists/arraylist" + "github.com/emirpasic/gods/utils" +) + +func main() { + list := arraylist.New() + list.Add(2, 1, 3) + values := GetSortedValues(container, utils.StringComparator) // [1, 2, 3] +} ``` -## Motivations +## Appendix + +### Motivation -Collections and data structures found in other languages: Java Collections, C++ Standard Template Library (STL) containers, Qt Containers, etc. +Collections and data structures found in other languages: Java Collections, C++ Standard Template Library (STL) containers, Qt Containers, Ruby Enumerable etc. -## Goals +### Goals -**Fast algorithms**: +**Fast algorithms**: - Based on decades of knowledge and experiences of other libraries mentioned above. -**Memory efficient algorithms**: - +**Memory efficient algorithms**: + - Avoiding to consume memory by using optimal algorithms and data structures for the given set of problems, e.g. red-black tree in case of TreeMap to avoid keeping redundant sorted array of keys in memory. -**Easy to use library**: - - - Well-structued library with minimalistic set of atomic operations from which more complex operations can be crafted. +**Easy to use library**: + + - Well-structured library with minimalistic set of atomic operations from which more complex operations can be crafted. + +**Stable library**: -**Stable library**: - - Only additions are permitted keeping the library backward compatible. -**Solid documentation and examples**: - +**Solid documentation and examples**: + - Learning by example. -**Production ready**: +**Production ready**: + + - Used in production. - - Still waiting for the project to mature and be used in some heavy back-end tasks. +**No dependencies**: + + - No external imports. There is often a tug of war between speed and memory when crafting algorithms. We choose to optimize for speed in most cases within reasonable limits on memory consumption. Thread safety is not a concern of this project, this should be handled at a higher level. -## Testing and Benchmarking +### Testing and Benchmarking + +This takes a while, so test within sub-packages: -`go test -v -bench . -benchmem -benchtime 1s ./...` +`go test -run=NO_TEST -bench . -benchmem -benchtime 1s ./...` -## Contributing +

+ +### Contributing Biggest contribution towards this library is to use it and give us feedback for further improvements and additions. -For direct contributions, branch of from master and do _pull request_. +For direct contributions, _pull request_ into master branch or ask to become a contributor. + +Coding style: + +```shell +# Install tooling and set path: +go install gotest.tools/gotestsum@latest +go install golang.org/x/lint/golint@latest +go install github.com/kisielk/errcheck@latest +export PATH=$PATH:$GOPATH/bin + +# Fix errors and warnings: +go fmt ./... && +go test -v ./... && +golint -set_exit_status ./... && +! go fmt ./... 2>&1 | read && +go vet -v ./... && +gocyclo -avg -over 15 ../gods && +errcheck ./... +``` -## License +### License This library is distributed under the BSD-style license found in the [LICENSE](https://github.com/emirpasic/gods/blob/master/LICENSE) file. -TimSort copied from [https://github.com/psilva261/timsort](https://github.com/psilva261/timsort) with MIT [LICENSE](https://github.com/emirpasic/gods/blob/master/utils/timsort/LICENSE) file. +### Sponsors + +## BrowserStack +[BrowserStack](https://www.browserstack.com/?ref=webhook) is a cloud-based cross-browser testing tool that enables developers to test their websites across various browsers on different operating systems and mobile devices, without requiring users to install virtual machines, devices or emulators. diff --git a/containers/containers.go b/containers/containers.go index 96d3e073..1c016aa6 100644 --- a/containers/containers.go +++ b/containers/containers.go @@ -1,50 +1,51 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// All data structures must implement the container structure - +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package containers provides core interfaces and functions for data structures. +// +// Container is the base interface for all data structures to implement. +// +// Iterators provide stateful iterators. +// +// Enumerable provides Ruby inspired (each, select, map, find, any?, etc.) container functions. +// +// Serialization provides serializers (marshalers) and deserializers (unmarshalers). package containers -import "github.com/emirpasic/gods/utils" +import ( + "cmp" + "slices" + + "github.com/emirpasic/gods/v2/utils" +) -type Interface interface { +// Container is base interface that all data structures implement. +type Container[T any] interface { Empty() bool Size() int Clear() - Values() []interface{} + Values() []T + String() string +} + +// GetSortedValues returns sorted container's elements with respect to the passed comparator. +// Does not affect the ordering of elements within the container. +func GetSortedValues[T cmp.Ordered](container Container[T]) []T { + values := container.Values() + if len(values) < 2 { + return values + } + slices.Sort(values) + return values } -// Returns sorted container's elements using with respect to the passed comparator. -// Does not effect the ordering of elements within the container. -// Uses timsort. -func GetSortedValues(container Interface, comparator utils.Comparator) []interface{} { +// GetSortedValuesFunc is the equivalent of GetSortedValues for containers of values that are not ordered. +func GetSortedValuesFunc[T any](container Container[T], comparator utils.Comparator[T]) []T { values := container.Values() if len(values) < 2 { return values } - utils.Sort(values, comparator) + slices.SortFunc(values, comparator) return values } diff --git a/containers/containers_test.go b/containers/containers_test.go index bef88f42..06763b43 100644 --- a/containers/containers_test.go +++ b/containers/containers_test.go @@ -1,76 +1,76 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. // All data structures must implement the container structure package containers import ( - "github.com/emirpasic/gods/utils" + "cmp" + "fmt" + "strings" "testing" ) // For testing purposes -type Container struct { - values []interface{} +type ContainerTest[T any] struct { + values []T } -func (container Container) Empty() bool { +func (container ContainerTest[T]) Empty() bool { return len(container.values) == 0 } -func (container Container) Size() int { +func (container ContainerTest[T]) Size() int { return len(container.values) } -func (container Container) Clear() { - container.values = []interface{}{} +func (container ContainerTest[T]) Clear() { + container.values = []T{} } -func (container Container) Values() []interface{} { +func (container ContainerTest[T]) Values() []T { return container.values } +func (container ContainerTest[T]) String() string { + str := "ContainerTest\n" + var values []string + for _, value := range container.values { + values = append(values, fmt.Sprintf("%v", value)) + } + str += strings.Join(values, ", ") + return str +} + func TestGetSortedValuesInts(t *testing.T) { - container := Container{} - container.values = []interface{}{5, 1, 3, 2, 4} - values := GetSortedValues(container, utils.IntComparator) + container := ContainerTest[int]{} + GetSortedValues(container) + container.values = []int{5, 1, 3, 2, 4} + values := GetSortedValues(container) for i := 1; i < container.Size(); i++ { - if values[i-1].(int) > values[i].(int) { + if values[i-1] > values[i] { t.Errorf("Not sorted!") } } } -func TestGetSortedValuesStrings(t *testing.T) { - container := Container{} - container.values = []interface{}{"g", "a", "d", "e", "f", "c", "b"} - values := GetSortedValues(container, utils.StringComparator) +type NotInt struct { + i int +} + +func TestGetSortedValuesNotInts(t *testing.T) { + container := ContainerTest[NotInt]{} + GetSortedValuesFunc(container, func(x, y NotInt) int { + return cmp.Compare(x.i, y.i) + }) + container.values = []NotInt{{5}, {1}, {3}, {2}, {4}} + values := GetSortedValuesFunc(container, func(x, y NotInt) int { + return cmp.Compare(x.i, y.i) + }) for i := 1; i < container.Size(); i++ { - if values[i-1].(string) > values[i].(string) { + if values[i-1].i > values[i].i { t.Errorf("Not sorted!") } } diff --git a/containers/enumerable.go b/containers/enumerable.go new file mode 100644 index 00000000..1121388c --- /dev/null +++ b/containers/enumerable.go @@ -0,0 +1,57 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package containers + +// EnumerableWithIndex provides functions for ordered containers whose values can be fetched by an index. +type EnumerableWithIndex[T any] interface { + // Each calls the given function once for each element, passing that element's index and value. + Each(func(index int, value T)) + + // Map invokes the given function once for each element and returns a + // container containing the values returned by the given function. + // Map(func(index int, value interface{}) interface{}) Container + + // Select returns a new container containing all elements for which the given function returns a true value. + // Select(func(index int, value interface{}) bool) Container + + // Any passes each element of the container to the given function and + // returns true if the function ever returns true for any element. + Any(func(index int, value T) bool) bool + + // All passes each element of the container to the given function and + // returns true if the function returns true for all elements. + All(func(index int, value T) bool) bool + + // Find passes each element of the container to the given function and returns + // the first (index,value) for which the function is true or -1,nil otherwise + // if no element matches the criteria. + Find(func(index int, value T) bool) (int, T) +} + +// EnumerableWithKey provides functions for ordered containers whose values whose elements are key/value pairs. +type EnumerableWithKey[K, V any] interface { + // Each calls the given function once for each element, passing that element's key and value. + Each(func(key K, value V)) + + // Map invokes the given function once for each element and returns a container + // containing the values returned by the given function as key/value pairs. + // Map(func(key interface{}, value interface{}) (interface{}, interface{})) Container + + // Select returns a new container containing all elements for which the given function returns a true value. + // Select(func(key interface{}, value interface{}) bool) Container + + // Any passes each element of the container to the given function and + // returns true if the function ever returns true for any element. + Any(func(key K, value V) bool) bool + + // All passes each element of the container to the given function and + // returns true if the function returns true for all elements. + All(func(key K, value V) bool) bool + + // Find passes each element of the container to the given function and returns + // the first (key,value) for which the function is true or nil,nil otherwise if no element + // matches the criteria. + Find(func(key K, value V) bool) (K, V) +} diff --git a/containers/iterator.go b/containers/iterator.go new file mode 100644 index 00000000..68f4b5d5 --- /dev/null +++ b/containers/iterator.go @@ -0,0 +1,133 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package containers + +// IteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index. +type IteratorWithIndex[T any] interface { + // Next moves the iterator to the next element and returns true if there was a next element in the container. + // If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). + // If Next() was called for the first time, then it will point the iterator to the first element if it exists. + // Modifies the state of the iterator. + Next() bool + + // Value returns the current element's value. + // Does not modify the state of the iterator. + Value() T + + // Index returns the current element's index. + // Does not modify the state of the iterator. + Index() int + + // Begin resets the iterator to its initial state (one-before-first) + // Call Next() to fetch the first element if any. + Begin() + + // First moves the iterator to the first element and returns true if there was a first element in the container. + // If First() returns true, then first element's index and value can be retrieved by Index() and Value(). + // Modifies the state of the iterator. + First() bool + + // NextTo moves the iterator to the next element from current position that satisfies the condition given by the + // passed function, and returns true if there was a next element in the container. + // If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). + // Modifies the state of the iterator. + NextTo(func(index int, value T) bool) bool +} + +// IteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs. +type IteratorWithKey[K, V any] interface { + // Next moves the iterator to the next element and returns true if there was a next element in the container. + // If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). + // If Next() was called for the first time, then it will point the iterator to the first element if it exists. + // Modifies the state of the iterator. + Next() bool + + // Value returns the current element's value. + // Does not modify the state of the iterator. + Value() V + + // Key returns the current element's key. + // Does not modify the state of the iterator. + Key() K + + // Begin resets the iterator to its initial state (one-before-first) + // Call Next() to fetch the first element if any. + Begin() + + // First moves the iterator to the first element and returns true if there was a first element in the container. + // If First() returns true, then first element's key and value can be retrieved by Key() and Value(). + // Modifies the state of the iterator. + First() bool + + // NextTo moves the iterator to the next element from current position that satisfies the condition given by the + // passed function, and returns true if there was a next element in the container. + // If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). + // Modifies the state of the iterator. + NextTo(func(key K, value V) bool) bool +} + +// ReverseIteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index. +// +// Essentially it is the same as IteratorWithIndex, but provides additional: +// +// # Prev() function to enable traversal in reverse +// +// Last() function to move the iterator to the last element. +// +// End() function to move the iterator past the last element (one-past-the-end). +type ReverseIteratorWithIndex[T any] interface { + // Prev moves the iterator to the previous element and returns true if there was a previous element in the container. + // If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). + // Modifies the state of the iterator. + Prev() bool + + // End moves the iterator past the last element (one-past-the-end). + // Call Prev() to fetch the last element if any. + End() + + // Last moves the iterator to the last element and returns true if there was a last element in the container. + // If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). + // Modifies the state of the iterator. + Last() bool + + // PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the + // passed function, and returns true if there was a next element in the container. + // If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). + // Modifies the state of the iterator. + PrevTo(func(index int, value T) bool) bool + + IteratorWithIndex[T] +} + +// ReverseIteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs. +// +// Essentially it is the same as IteratorWithKey, but provides additional: +// +// # Prev() function to enable traversal in reverse +// +// Last() function to move the iterator to the last element. +type ReverseIteratorWithKey[K, V any] interface { + // Prev moves the iterator to the previous element and returns true if there was a previous element in the container. + // If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). + // Modifies the state of the iterator. + Prev() bool + + // End moves the iterator past the last element (one-past-the-end). + // Call Prev() to fetch the last element if any. + End() + + // Last moves the iterator to the last element and returns true if there was a last element in the container. + // If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). + // Modifies the state of the iterator. + Last() bool + + // PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the + // passed function, and returns true if there was a next element in the container. + // If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). + // Modifies the state of the iterator. + PrevTo(func(key K, value V) bool) bool + + IteratorWithKey[K, V] +} diff --git a/containers/serialization.go b/containers/serialization.go new file mode 100644 index 00000000..fd9cbe23 --- /dev/null +++ b/containers/serialization.go @@ -0,0 +1,21 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package containers + +// JSONSerializer provides JSON serialization +type JSONSerializer interface { + // ToJSON outputs the JSON representation of containers's elements. + ToJSON() ([]byte, error) + // MarshalJSON @implements json.Marshaler + MarshalJSON() ([]byte, error) +} + +// JSONDeserializer provides JSON deserialization +type JSONDeserializer interface { + // FromJSON populates containers's elements from the input JSON representation. + FromJSON([]byte) error + // UnmarshalJSON @implements json.Unmarshaler + UnmarshalJSON([]byte) error +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..8a1e8cae --- /dev/null +++ b/examples/README.md @@ -0,0 +1,29 @@ +# GoDS (Go Data Structures) + +Various examples on how to use data structures. + +## Examples + +- [ArrayList](https://github.com/emirpasic/gods/blob/master/examples/arraylist/arraylist.go) +- [ArrayStack](https://github.com/emirpasic/gods/blob/master/examples/arraystack/arraystack.go) +- [AVLTree](https://github.com/emirpasic/gods/blob/master/examples/avltree/avltree.go) +- [BinaryHeap](https://github.com/emirpasic/gods/blob/master/examples/binaryheap/binaryheap.go) +- [BTree](https://github.com/emirpasic/gods/blob/master/examples/btree/btree.go) +- [Custom Comparator](https://github.com/emirpasic/gods/blob/master/examples/customcomparator/customcomparator.go) +- [DoublyLinkedList](https://github.com/emirpasic/gods/blob/master/examples/doublylinkedlist/doublylinkedlist.go) +- [EnumerableWithIndex](https://github.com/emirpasic/gods/blob/master/examples/enumerablewithindex/enumerablewithindex.go) +- [EnumerableWithKey](https://github.com/emirpasic/gods/blob/master/examples/enumerablewithkey/enumerablewithkey.go) +- [HashBidiMap](https://github.com/emirpasic/gods/blob/master/examples/hashbidimap/hashbidimap.go) +- [HashMap](https://github.com/emirpasic/gods/blob/master/examples/hashmap/hashmap.go) +- [HashSet](https://github.com/emirpasic/gods/blob/master/examples/hashset/hashset.go) +- [IteratorWithIndex](https://github.com/emirpasic/gods/blob/master/examples/iteratorwithindex/iteratorwithindex.go) +- [iteratorwithkey](https://github.com/emirpasic/gods/blob/master/examples/iteratorwithkey/iteratorwithkey.go) +- [IteratorWithKey](https://github.com/emirpasic/gods/blob/master/examples/linkedliststack/linkedliststack.go) +- [RedBlackTree](https://github.com/emirpasic/gods/blob/master/examples/redblacktree/redblacktree.go) +- [RedBlackTreeExtended](https://github.com/emirpasic/gods/blob/master/examples/redblacktreeextended/redblacktreeextended.go) +- [Serialization](https://github.com/emirpasic/gods/blob/master/examples/serialization/serialization.go) +- [SinglyLinkedList](https://github.com/emirpasic/gods/blob/master/examples/singlylinkedlist/singlylinkedlist.go) +- [Sort](https://github.com/emirpasic/gods/blob/master/examples/sort/sort.go) +- [TreeBidiMap](https://github.com/emirpasic/gods/blob/master/examples/treebidimap/treebidimap.go) +- [TreeMap](https://github.com/emirpasic/gods/blob/master/examples/treemap/treemap.go) +- [TreeSet](https://github.com/emirpasic/gods/blob/master/examples/treeset/treeset.go) diff --git a/examples/arraylist.go b/examples/arraylist.go deleted file mode 100644 index f2134c70..00000000 --- a/examples/arraylist.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import ( - "github.com/emirpasic/gods/lists/arraylist" - "github.com/emirpasic/gods/utils" -) - -func ArrayListExample() { - list := arraylist.New() - list.Add("a") // ["a"] - list.Add("c", "b") // ["a","c","b"] - list.Sort(utils.StringComparator) // ["a","b","c"] - _, _ = list.Get(0) // "a",true - _, _ = list.Get(100) // nil,false - _ = list.Contains("a", "b", "c") // true - _ = list.Contains("a", "b", "c", "d") // false - list.Swap(0, 1) // ["b","a",c"] - list.Remove(2) // ["b","a"] - list.Remove(1) // ["b"] - list.Remove(0) // [] - list.Remove(0) // [] (ignored) - _ = list.Empty() // true - _ = list.Size() // 0 - list.Add("a") // ["a"] - list.Clear() // [] -} diff --git a/examples/arraylist/arraylist.go b/examples/arraylist/arraylist.go new file mode 100644 index 00000000..fb98d64c --- /dev/null +++ b/examples/arraylist/arraylist.go @@ -0,0 +1,32 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "cmp" + + "github.com/emirpasic/gods/v2/lists/arraylist" +) + +// ArrayListExample to demonstrate basic usage of ArrayList +func main() { + list := arraylist.New[string]() + list.Add("a") // ["a"] + list.Add("c", "b") // ["a","c","b"] + list.Sort(cmp.Compare[string]) // ["a","b","c"] + _, _ = list.Get(0) // "a",true + _, _ = list.Get(100) // nil,false + _ = list.Contains("a", "b", "c") // true + _ = list.Contains("a", "b", "c", "d") // false + list.Swap(0, 1) // ["b","a",c"] + list.Remove(2) // ["b","a"] + list.Remove(1) // ["b"] + list.Remove(0) // [] + list.Remove(0) // [] (ignored) + _ = list.Empty() // true + _ = list.Size() // 0 + list.Add("a") // ["a"] + list.Clear() // [] +} diff --git a/examples/arrayqueue/arrayqqueue.go b/examples/arrayqueue/arrayqqueue.go new file mode 100644 index 00000000..bc4c4584 --- /dev/null +++ b/examples/arrayqueue/arrayqqueue.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import aq "github.com/emirpasic/gods/v2/queues/arrayqueue" + +// ArrayQueueExample to demonstrate basic usage of ArrayQueue +func main() { + queue := aq.New[int]() // empty + queue.Enqueue(1) // 1 + queue.Enqueue(2) // 1, 2 + _ = queue.Values() // 1, 2 (FIFO order) + _, _ = queue.Peek() // 1,true + _, _ = queue.Dequeue() // 1, true + _, _ = queue.Dequeue() // 2, true + _, _ = queue.Dequeue() // nil, false (nothing to deque) + queue.Enqueue(1) // 1 + queue.Clear() // empty + queue.Empty() // true + _ = queue.Size() // 0 +} diff --git a/examples/arraystack.go b/examples/arraystack.go deleted file mode 100644 index 1dd93cea..00000000 --- a/examples/arraystack.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import "github.com/emirpasic/gods/stacks/arraystack" - -func ArrayStackExample() { - stack := arraystack.New() // empty - stack.Push(1) // 1 - stack.Push(2) // 1, 2 - stack.Values() // 2, 1 (LIFO order) - _, _ = stack.Peek() // 2,true - _, _ = stack.Pop() // 2, true - _, _ = stack.Pop() // 1, true - _, _ = stack.Pop() // nil, false (nothing to pop) - stack.Push(1) // 1 - stack.Clear() // empty - stack.Empty() // true - stack.Size() // 0 -} diff --git a/examples/arraystack/arraystack.go b/examples/arraystack/arraystack.go new file mode 100644 index 00000000..1a54f370 --- /dev/null +++ b/examples/arraystack/arraystack.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/stacks/arraystack" + +// ArrayStackExample to demonstrate basic usage of ArrayStack +func main() { + stack := arraystack.New[int]() // empty + stack.Push(1) // 1 + stack.Push(2) // 1, 2 + stack.Values() // 2, 1 (LIFO order) + _, _ = stack.Peek() // 2,true + _, _ = stack.Pop() // 2, true + _, _ = stack.Pop() // 1, true + _, _ = stack.Pop() // nil, false (nothing to pop) + stack.Push(1) // 1 + stack.Clear() // empty + stack.Empty() // true + stack.Size() // 0 +} diff --git a/examples/avltree/avltree.go b/examples/avltree/avltree.go new file mode 100644 index 00000000..5499ae0d --- /dev/null +++ b/examples/avltree/avltree.go @@ -0,0 +1,51 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + avl "github.com/emirpasic/gods/v2/trees/avltree" +) + +// AVLTreeExample to demonstrate basic usage of AVLTree +func main() { + tree := avl.New[int, string]() // empty(keys are of type int) + + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + + fmt.Println(tree) + // + // AVLTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 2 + // └── 1 + + _ = tree.Values() // []interface {}{"a", "b", "c", "d", "e", "f"} (in order) + _ = tree.Keys() // []interface {}{1, 2, 3, 4, 5, 6} (in order) + + tree.Remove(2) // 1->a, 3->c, 4->d, 5->e, 6->f (in order) + fmt.Println(tree) + // + // AVLTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // └── 3 + // └── 1 + + tree.Clear() // empty + tree.Empty() // true + tree.Size() // 0 +} diff --git a/examples/binaryheap.go b/examples/binaryheap.go deleted file mode 100644 index 588ce5b8..00000000 --- a/examples/binaryheap.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import ( - "github.com/emirpasic/gods/trees/binaryheap" - "github.com/emirpasic/gods/utils" -) - -func BinaryHeapExample() { - - // Min-heap - heap := binaryheap.NewWithIntComparator() // empty (min-heap) - heap.Push(2) // 2 - heap.Push(3) // 2, 3 - heap.Push(1) // 1, 3, 2 - heap.Values() // 1, 3, 2 - _, _ = heap.Peek() // 1,true - _, _ = heap.Pop() // 1, true - _, _ = heap.Pop() // 2, true - _, _ = heap.Pop() // 3, true - _, _ = heap.Pop() // nil, false (nothing to pop) - heap.Push(1) // 1 - heap.Clear() // empty - heap.Empty() // true - heap.Size() // 0 - - // Max-heap - inverseIntComparator := func(a, b interface{}) int { - return -utils.IntComparator(a, b) - } - heap = binaryheap.NewWith(inverseIntComparator) // empty (min-heap) - heap.Push(2) // 2 - heap.Push(3) // 3, 2 - heap.Push(1) // 3, 2, 1 - heap.Values() // 3, 2, 1 -} diff --git a/examples/binaryheap/binaryheap.go b/examples/binaryheap/binaryheap.go new file mode 100644 index 00000000..ad680389 --- /dev/null +++ b/examples/binaryheap/binaryheap.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "cmp" + + "github.com/emirpasic/gods/v2/trees/binaryheap" +) + +// BinaryHeapExample to demonstrate basic usage of BinaryHeap +func main() { + + // Min-heap + heap := binaryheap.New[int]() // empty (min-heap) + heap.Push(2) // 2 + heap.Push(3) // 2, 3 + heap.Push(1) // 1, 3, 2 + heap.Values() // 1, 3, 2 + _, _ = heap.Peek() // 1,true + _, _ = heap.Pop() // 1, true + _, _ = heap.Pop() // 2, true + _, _ = heap.Pop() // 3, true + _, _ = heap.Pop() // nil, false (nothing to pop) + heap.Push(1) // 1 + heap.Clear() // empty + heap.Empty() // true + heap.Size() // 0 + + // Max-heap + inverseIntComparator := func(a, b int) int { + return -cmp.Compare(a, b) + } + heap = binaryheap.NewWith(inverseIntComparator) // empty (min-heap) + heap.Push(2) // 2 + heap.Push(3) // 3, 2 + heap.Push(1) // 3, 2, 1 + heap.Values() // 3, 2, 1 +} diff --git a/examples/btree/btree.go b/examples/btree/btree.go new file mode 100644 index 00000000..fdf87d11 --- /dev/null +++ b/examples/btree/btree.go @@ -0,0 +1,60 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + "github.com/emirpasic/gods/v2/trees/btree" +) + +// BTreeExample to demonstrate basic usage of BTree +func main() { + tree := btree.New[int, string](3) // empty (keys are of type int) + + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + tree.Put(7, "g") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f, 7->g (in order) + + fmt.Println(tree) + // BTree + // 1 + // 2 + // 3 + // 4 + // 5 + // 6 + // 7 + + _ = tree.Values() // []interface {}{"a", "b", "c", "d", "e", "f", "g"} (in order) + _ = tree.Keys() // []interface {}{1, 2, 3, 4, 5, 6, 7} (in order) + + tree.Remove(2) // 1->a, 3->c, 4->d, 5->e, 6->f (in order) + fmt.Println(tree) + // BTree + // 1 + // 3 + // 4 + // 5 + // 6 + + tree.Clear() // empty + tree.Empty() // true + tree.Size() // 0 + + // Other: + tree.Height() // gets the height of the tree + tree.Left() // gets the left-most (min) node + tree.LeftKey() // get the left-most (min) node's key + tree.LeftValue() // get the left-most (min) node's value + tree.Right() // get the right-most (max) node + tree.RightKey() // get the right-most (max) node's key + tree.RightValue() // get the right-most (max) node's value +} diff --git a/examples/circularbuffer/circularbuffer.go b/examples/circularbuffer/circularbuffer.go new file mode 100644 index 00000000..1d230033 --- /dev/null +++ b/examples/circularbuffer/circularbuffer.go @@ -0,0 +1,26 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import cb "github.com/emirpasic/gods/v2/queues/circularbuffer" + +// CircularBufferExample to demonstrate basic usage of CircularBuffer +func main() { + queue := cb.New[int](3) // empty (max size is 3) + queue.Enqueue(1) // 1 + queue.Enqueue(2) // 1, 2 + queue.Enqueue(3) // 1, 2, 3 + _ = queue.Values() // 1, 2, 3 + queue.Enqueue(3) // 4, 2, 3 + _, _ = queue.Peek() // 4,true + _, _ = queue.Dequeue() // 4, true + _, _ = queue.Dequeue() // 2, true + _, _ = queue.Dequeue() // 3, true + _, _ = queue.Dequeue() // nil, false (nothing to deque) + queue.Enqueue(1) // 1 + queue.Clear() // empty + queue.Empty() // true + _ = queue.Size() // 0 +} diff --git a/examples/customcomparator.go b/examples/customcomparator.go deleted file mode 100644 index b14e4afa..00000000 --- a/examples/customcomparator.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import ( - "fmt" - "github.com/emirpasic/gods/sets/treeset" -) - -type User struct { - id int - name string -} - -// Comparator function (sort by IDs) -func byID(a, b interface{}) int { - - // Type assertion, program will panic if this is not respected - c1 := a.(User) - c2 := b.(User) - - switch { - case c1.id > c2.id: - return 1 - case c1.id < c2.id: - return -1 - default: - return 0 - } -} - -func CustomComparatorExample() { - set := treeset.NewWith(byID) - - set.Add(User{2, "Second"}) - set.Add(User{3, "Third"}) - set.Add(User{1, "First"}) - set.Add(User{4, "Fourth"}) - - fmt.Println(set) // {1 First}, {2 Second}, {3 Third}, {4 Fourth} -} diff --git a/examples/customcomparator/customcomparator.go b/examples/customcomparator/customcomparator.go new file mode 100644 index 00000000..0a5d9572 --- /dev/null +++ b/examples/customcomparator/customcomparator.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + "github.com/emirpasic/gods/v2/sets/treeset" +) + +// User model (id and name) +type User struct { + id int + name string +} + +// Comparator function (sort by IDs) +func byID(a, b User) int { + + switch { + case a.id > b.id: + return 1 + case a.id < b.id: + return -1 + default: + return 0 + } +} + +// CustomComparatorExample to demonstrate basic usage of CustomComparator +func main() { + set := treeset.NewWith(byID) + + set.Add(User{2, "Second"}) + set.Add(User{3, "Third"}) + set.Add(User{1, "First"}) + set.Add(User{4, "Fourth"}) + + fmt.Println(set) // {1 First}, {2 Second}, {3 Third}, {4 Fourth} +} diff --git a/examples/doublylinkedlist.go b/examples/doublylinkedlist.go deleted file mode 100644 index d475eb73..00000000 --- a/examples/doublylinkedlist.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import ( - dll "github.com/emirpasic/gods/lists/doublylinkedlist" - "github.com/emirpasic/gods/utils" -) - -func DoublyLinkedListExample() { - list := dll.New() - list.Add("a") // ["a"] - list.Append("b") // ["a","b"] (same as Add()) - list.Prepend("c") // ["c","a","b"] - list.Sort(utils.StringComparator) // ["a","b","c"] - _, _ = list.Get(0) // "a",true - _, _ = list.Get(100) // nil,false - _ = list.Contains("a", "b", "c") // true - _ = list.Contains("a", "b", "c", "d") // false - list.Remove(2) // ["a","b"] - list.Remove(1) // ["a"] - list.Remove(0) // [] - list.Remove(0) // [] (ignored) - _ = list.Empty() // true - _ = list.Size() // 0 - list.Add("a") // ["a"] - list.Clear() // [] -} diff --git a/examples/doublylinkedlist/doublylinkedlist.go b/examples/doublylinkedlist/doublylinkedlist.go new file mode 100644 index 00000000..53b4a897 --- /dev/null +++ b/examples/doublylinkedlist/doublylinkedlist.go @@ -0,0 +1,32 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "cmp" + + dll "github.com/emirpasic/gods/v2/lists/doublylinkedlist" +) + +// DoublyLinkedListExample to demonstrate basic usage of DoublyLinkedList +func main() { + list := dll.New[string]() + list.Add("a") // ["a"] + list.Append("b") // ["a","b"] (same as Add()) + list.Prepend("c") // ["c","a","b"] + list.Sort(cmp.Compare[string]) // ["a","b","c"] + _, _ = list.Get(0) // "a",true + _, _ = list.Get(100) // nil,false + _ = list.Contains("a", "b", "c") // true + _ = list.Contains("a", "b", "c", "d") // false + list.Remove(2) // ["a","b"] + list.Remove(1) // ["a"] + list.Remove(0) // [] + list.Remove(0) // [] (ignored) + _ = list.Empty() // true + _ = list.Size() // 0 + list.Add("a") // ["a"] + list.Clear() // [] +} diff --git a/examples/enumerablewithindex/enumerablewithindex.go b/examples/enumerablewithindex/enumerablewithindex.go new file mode 100644 index 00000000..f3dd7419 --- /dev/null +++ b/examples/enumerablewithindex/enumerablewithindex.go @@ -0,0 +1,60 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + "github.com/emirpasic/gods/v2/sets/treeset" +) + +func printSet(txt string, set *treeset.Set[int]) { + fmt.Print(txt, "[ ") + set.Each(func(index int, value int) { + fmt.Print(value, " ") + }) + fmt.Println("]") +} + +// EnumerableWithIndexExample to demonstrate basic usage of EnumerableWithIndex +func main() { + set := treeset.New[int]() + set.Add(2, 3, 4, 2, 5, 6, 7, 8) + printSet("Initial", set) // [ 2 3 4 5 6 7 8 ] + + even := set.Select(func(index int, value int) bool { + return value%2 == 0 + }) + printSet("Even numbers", even) // [ 2 4 6 8 ] + + foundIndex, foundValue := set.Find(func(index int, value int) bool { + return value%2 == 0 && value%3 == 0 + }) + if foundIndex != -1 { + fmt.Println("Number divisible by 2 and 3 found is", foundValue, "at index", foundIndex) // value: 6, index: 4 + } + + square := set.Map(func(index int, value int) int { + return value * value + }) + printSet("Numbers squared", square) // [ 4 9 16 25 36 49 64 ] + + bigger := set.Any(func(index int, value int) bool { + return value > 5 + }) + fmt.Println("Set contains a number bigger than 5 is ", bigger) // true + + positive := set.All(func(index int, value int) bool { + return value > 0 + }) + fmt.Println("All numbers are positive is", positive) // true + + evenNumbersSquared := set.Select(func(index int, value int) bool { + return value%2 == 0 + }).Map(func(index int, value int) int { + return value * value + }) + printSet("Chaining", evenNumbersSquared) // [ 4 16 36 64 ] +} diff --git a/examples/enumerablewithkey/enumerablewithkey.go b/examples/enumerablewithkey/enumerablewithkey.go new file mode 100644 index 00000000..0bfa4c08 --- /dev/null +++ b/examples/enumerablewithkey/enumerablewithkey.go @@ -0,0 +1,66 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + "github.com/emirpasic/gods/v2/maps/treemap" +) + +func printMap(txt string, m *treemap.Map[string, int]) { + fmt.Print(txt, " { ") + m.Each(func(key string, value int) { + fmt.Print(key, ":", value, " ") + }) + fmt.Println("}") +} + +// EunumerableWithKeyExample to demonstrate basic usage of EunumerableWithKey +func main() { + m := treemap.New[string, int]() + m.Put("g", 7) + m.Put("f", 6) + m.Put("e", 5) + m.Put("d", 4) + m.Put("c", 3) + m.Put("b", 2) + m.Put("a", 1) + printMap("Initial", m) // { a:1 b:2 c:3 d:4 e:5 f:6 g:7 } + + even := m.Select(func(key string, value int) bool { + return value%2 == 0 + }) + printMap("Elements with even values", even) // { b:2 d:4 f:6 } + + foundKey, foundValue := m.Find(func(key string, value int) bool { + return value%2 == 0 && value%3 == 0 + }) + if foundKey != "" { + fmt.Println("Element with value divisible by 2 and 3 found is", foundValue, "with key", foundKey) // value: 6, index: 4 + } + + square := m.Map(func(key string, value int) (string, int) { + return key + key, value * value + }) + printMap("Elements' values squared and letters duplicated", square) // { aa:1 bb:4 cc:9 dd:16 ee:25 ff:36 gg:49 } + + bigger := m.Any(func(key string, value int) bool { + return value > 5 + }) + fmt.Println("Map contains element whose value is bigger than 5 is", bigger) // true + + positive := m.All(func(key string, value int) bool { + return value > 0 + }) + fmt.Println("All map's elements have positive values is", positive) // true + + evenNumbersSquared := m.Select(func(key string, value int) bool { + return value%2 == 0 + }).Map(func(key string, value int) (string, int) { + return key, value * value + }) + printMap("Chaining", evenNumbersSquared) // { b:4 d:16 f:36 } +} diff --git a/examples/hashbidimap/hashbidimap.go b/examples/hashbidimap/hashbidimap.go new file mode 100644 index 00000000..cc297668 --- /dev/null +++ b/examples/hashbidimap/hashbidimap.go @@ -0,0 +1,25 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/maps/hashbidimap" + +// HashBidiMapExample to demonstrate basic usage of HashMap +func main() { + m := hashbidimap.New[int, string]() // empty + m.Put(1, "x") // 1->x + m.Put(3, "b") // 1->x, 3->b (random order) + m.Put(1, "a") // 1->a, 3->b (random order) + m.Put(2, "b") // 1->a, 2->b (random order) + _, _ = m.GetKey("a") // 1, true + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"a", "b"} (random order) + _ = m.Keys() // []interface {}{1, 2} (random order) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 +} diff --git a/examples/hashmap.go b/examples/hashmap.go deleted file mode 100644 index f73882cb..00000000 --- a/examples/hashmap.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import "github.com/emirpasic/gods/maps/hashmap" - -func HashMapExample() { - m := hashmap.New() // empty - m.Put(1, "x") // 1->x - m.Put(2, "b") // 2->b, 1->x (random order) - m.Put(1, "a") // 2->b, 1->a (random order) - _, _ = m.Get(2) // b, true - _, _ = m.Get(3) // nil, false - _ = m.Values() // []interface {}{"b", "a"} (random order) - _ = m.Keys() // []interface {}{1, 2} (random order) - m.Remove(1) // 2->b - m.Clear() // empty - m.Empty() // true - m.Size() // 0 -} diff --git a/examples/hashmap/hashmap.go b/examples/hashmap/hashmap.go new file mode 100644 index 00000000..4be346d5 --- /dev/null +++ b/examples/hashmap/hashmap.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/maps/hashmap" + +// HashMapExample to demonstrate basic usage of HashMap +func main() { + m := hashmap.New[int, string]() // empty + m.Put(1, "x") // 1->x + m.Put(2, "b") // 2->b, 1->x (random order) + m.Put(1, "a") // 2->b, 1->a (random order) + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"b", "a"} (random order) + _ = m.Keys() // []interface {}{1, 2} (random order) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 +} diff --git a/examples/hashset.go b/examples/hashset.go deleted file mode 100644 index ed466742..00000000 --- a/examples/hashset.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import "github.com/emirpasic/gods/sets/hashset" - -func HashSetExample() { - set := hashset.New() // empty (keys are of type int) - set.Add(1) // 1 - set.Add(2, 2, 3, 4, 5) // 3, 1, 2, 4, 5 (random order, duplicates ignored) - set.Remove(4) // 5, 3, 2, 1 (random order) - set.Remove(2, 3) // 1, 5 (random order) - set.Contains(1) // true - set.Contains(1, 5) // true - set.Contains(1, 6) // false - _ = set.Values() // []int{5,1} (random order) - set.Clear() // empty - set.Empty() // true - set.Size() // 0 -} diff --git a/examples/hashset/hashset.go b/examples/hashset/hashset.go new file mode 100644 index 00000000..eccff89a --- /dev/null +++ b/examples/hashset/hashset.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/sets/hashset" + +// HashSetExample to demonstrate basic usage of HashSet +func main() { + set := hashset.New[int]() // empty (keys are of type int) + set.Add(1) // 1 + set.Add(2, 2, 3, 4, 5) // 3, 1, 2, 4, 5 (random order, duplicates ignored) + set.Remove(4) // 5, 3, 2, 1 (random order) + set.Remove(2, 3) // 1, 5 (random order) + set.Contains(1) // true + set.Contains(1, 5) // true + set.Contains(1, 6) // false + _ = set.Values() // []int{5,1} (random order) + set.Clear() // empty + set.Empty() // true + set.Size() // 0 +} diff --git a/examples/iteratorwithindex/iteratorwithindex.go b/examples/iteratorwithindex/iteratorwithindex.go new file mode 100644 index 00000000..d68cf283 --- /dev/null +++ b/examples/iteratorwithindex/iteratorwithindex.go @@ -0,0 +1,81 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/sets/treeset" +) + +// IteratorWithIndexExample to demonstrate basic usage of IteratorWithIndex +func main() { + set := treeset.New[string]() + set.Add("a", "b", "c") + it := set.Iterator() + + fmt.Print("\nForward iteration\n") + for it.Next() { + index, value := it.Index(), it.Value() + fmt.Print("[", index, ":", value, "]") // [0:a][1:b][2:c] + } + + fmt.Print("\nForward iteration (again)\n") + for it.Begin(); it.Next(); { + index, value := it.Index(), it.Value() + fmt.Print("[", index, ":", value, "]") // [0:a][1:b][2:c] + } + + fmt.Print("\nBackward iteration\n") + for it.Prev() { + index, value := it.Index(), it.Value() + fmt.Print("[", index, ":", value, "]") // [2:c][1:b][0:a] + } + + fmt.Print("\nBackward iteration (again)\n") + for it.End(); it.Prev(); { + index, value := it.Index(), it.Value() + fmt.Print("[", index, ":", value, "]") // [2:c][1:b][0:a] + } + + if it.First() { + fmt.Print("\nFirst index: ", it.Index()) // First index: 0 + fmt.Print("\nFirst value: ", it.Value()) // First value: a + } + + if it.Last() { + fmt.Print("\nLast index: ", it.Index()) // Last index: 3 + fmt.Print("\nLast value: ", it.Value()) // Last value: c + } + + // Seek element starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + it.Begin() + for found := it.NextTo(seek); found; found = it.Next() { + fmt.Print("\nNextTo index: ", it.Index()) + fmt.Print("\nNextTo value: ", it.Value()) + } /* + NextTo index: 1 + NextTo value: "b" + NextTo index: 2 + NextTo value: "c" + */ + + it.End() + for found := it.PrevTo(seek); found; found = it.Prev() { + fmt.Print("\nNextTo index: ", it.Index()) + fmt.Print("\nNextTo value: ", it.Value()) + } /* + NextTo index: 1 + NextTo value: "b" + NextTo index: 0 + NextTo value: "a" + */ + +} diff --git a/examples/iteratorwithkey/iteratorwithkey.go b/examples/iteratorwithkey/iteratorwithkey.go new file mode 100644 index 00000000..32396082 --- /dev/null +++ b/examples/iteratorwithkey/iteratorwithkey.go @@ -0,0 +1,82 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/maps/treemap" +) + +// IteratorWithKeyExample to demonstrate basic usage of IteratorWithKey +func main() { + m := treemap.New[int, string]() + m.Put(0, "a") + m.Put(1, "b") + m.Put(2, "c") + it := m.Iterator() + + fmt.Print("\nForward iteration\n") + for it.Next() { + key, value := it.Key(), it.Value() + fmt.Print("[", key, ":", value, "]") // [0:a][1:b][2:c] + } + + fmt.Print("\nForward iteration (again)\n") + for it.Begin(); it.Next(); { + key, value := it.Key(), it.Value() + fmt.Print("[", key, ":", value, "]") // [0:a][1:b][2:c] + } + + fmt.Print("\nBackward iteration\n") + for it.Prev() { + key, value := it.Key(), it.Value() + fmt.Print("[", key, ":", value, "]") // [2:c][1:b][0:a] + } + + fmt.Print("\nBackward iteration (again)\n") + for it.End(); it.Prev(); { + key, value := it.Key(), it.Value() + fmt.Print("[", key, ":", value, "]") // [2:c][1:b][0:a] + } + + if it.First() { + fmt.Print("\nFirst key: ", it.Key()) // First key: 0 + fmt.Print("\nFirst value: ", it.Value()) // First value: a + } + + if it.Last() { + fmt.Print("\nLast key: ", it.Key()) // Last key: 2 + fmt.Print("\nLast value: ", it.Value()) // Last value: c + } + + // Seek key-value pair whose value starts with "b" + seek := func(key int, value string) bool { + return strings.HasSuffix(value, "b") + } + + it.Begin() + for found := it.NextTo(seek); found; found = it.Next() { + fmt.Print("\nNextTo key: ", it.Key()) + fmt.Print("\nNextTo value: ", it.Value()) + } /* + NextTo key: 1 + NextTo value: "b" + NextTo key: 2 + NextTo value: "c" + */ + + it.End() + for found := it.PrevTo(seek); found; found = it.Prev() { + fmt.Print("\nNextTo key: ", it.Key()) + fmt.Print("\nNextTo value: ", it.Value()) + } /* + NextTo key: 1 + NextTo value: "b" + NextTo key: 0 + NextTo value: "a" + */ +} diff --git a/examples/linkedhashmap/linkedhashmap.go b/examples/linkedhashmap/linkedhashmap.go new file mode 100644 index 00000000..fe33f49d --- /dev/null +++ b/examples/linkedhashmap/linkedhashmap.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/maps/linkedhashmap" + +// LinkedHashMapExample to demonstrate basic usage of LinkedHashMapExample +func main() { + m := linkedhashmap.New[int, string]() // empty (keys are of type int) + m.Put(2, "b") // 2->b + m.Put(1, "x") // 2->b, 1->x (insertion-order) + m.Put(1, "a") // 2->b, 1->a (insertion-order) + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"b", "a"} (insertion-order) + _ = m.Keys() // []interface {}{2, 1} (insertion-order) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 +} diff --git a/examples/linkedhashset/linkedhashset.go b/examples/linkedhashset/linkedhashset.go new file mode 100644 index 00000000..e6a5d69c --- /dev/null +++ b/examples/linkedhashset/linkedhashset.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/sets/linkedhashset" + +// LinkedHashSetExample to demonstrate basic usage of LinkedHashSet +func main() { + set := linkedhashset.New[int]() // empty + set.Add(5) // 5 + set.Add(4, 4, 3, 2, 1) // 5, 4, 3, 2, 1 (in insertion-order, duplicates ignored) + set.Remove(4) // 5, 3, 2, 1 (in insertion-order) + set.Remove(2, 3) // 5, 1 (in insertion-order) + set.Contains(1) // true + set.Contains(1, 5) // true + set.Contains(1, 6) // false + _ = set.Values() // []int{5, 1} (in insertion-order) + set.Clear() // empty + set.Empty() // true + set.Size() // 0 +} diff --git a/examples/linkedlistqueue/linkedlistqueue.go b/examples/linkedlistqueue/linkedlistqueue.go new file mode 100644 index 00000000..2a61d2f5 --- /dev/null +++ b/examples/linkedlistqueue/linkedlistqueue.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import llq "github.com/emirpasic/gods/v2/queues/linkedlistqueue" + +// LinkedListQueueExample to demonstrate basic usage of LinkedListQueue +func main() { + queue := llq.New[int]() // empty + queue.Enqueue(1) // 1 + queue.Enqueue(2) // 1, 2 + _ = queue.Values() // 1, 2 (FIFO order) + _, _ = queue.Peek() // 1,true + _, _ = queue.Dequeue() // 1, true + _, _ = queue.Dequeue() // 2, true + _, _ = queue.Dequeue() // nil, false (nothing to deque) + queue.Enqueue(1) // 1 + queue.Clear() // empty + queue.Empty() // true + _ = queue.Size() // 0 +} diff --git a/examples/linkedliststack.go b/examples/linkedliststack.go deleted file mode 100644 index b7911f16..00000000 --- a/examples/linkedliststack.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import lls "github.com/emirpasic/gods/stacks/linkedliststack" - -func LinkedListStackExample() { - stack := lls.New() // empty - stack.Push(1) // 1 - stack.Push(2) // 1, 2 - stack.Values() // 2, 1 (LIFO order) - _, _ = stack.Peek() // 2,true - _, _ = stack.Pop() // 2, true - _, _ = stack.Pop() // 1, true - _, _ = stack.Pop() // nil, false (nothing to pop) - stack.Push(1) // 1 - stack.Clear() // empty - stack.Empty() // true - stack.Size() // 0 -} diff --git a/examples/linkedliststack/linkedliststack.go b/examples/linkedliststack/linkedliststack.go new file mode 100644 index 00000000..81ace6c6 --- /dev/null +++ b/examples/linkedliststack/linkedliststack.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import lls "github.com/emirpasic/gods/v2/stacks/linkedliststack" + +// LinkedListStackExample to demonstrate basic usage of LinkedListStack +func main() { + stack := lls.New[int]() // empty + stack.Push(1) // 1 + stack.Push(2) // 1, 2 + stack.Values() // 2, 1 (LIFO order) + _, _ = stack.Peek() // 2,true + _, _ = stack.Pop() // 2, true + _, _ = stack.Pop() // 1, true + _, _ = stack.Pop() // nil, false (nothing to pop) + stack.Push(1) // 1 + stack.Clear() // empty + stack.Empty() // true + stack.Size() // 0 +} diff --git a/examples/priorityqueue/priorityqueue.go b/examples/priorityqueue/priorityqueue.go new file mode 100644 index 00000000..e77f2210 --- /dev/null +++ b/examples/priorityqueue/priorityqueue.go @@ -0,0 +1,43 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "cmp" + + pq "github.com/emirpasic/gods/v2/queues/priorityqueue" +) + +// Element is an entry in the priority queue +type Element struct { + name string + priority int +} + +// Comparator function (sort by element's priority value in descending order) +func byPriority(a, b Element) int { + return -cmp.Compare(a.priority, b.priority) // "-" descending order +} + +// PriorityQueueExample to demonstrate basic usage of BinaryHeap +func main() { + a := Element{name: "a", priority: 1} + b := Element{name: "b", priority: 2} + c := Element{name: "c", priority: 3} + + queue := pq.NewWith(byPriority) // empty + queue.Enqueue(a) // {a 1} + queue.Enqueue(c) // {c 3}, {a 1} + queue.Enqueue(b) // {c 3}, {b 2}, {a 1} + _ = queue.Values() // [{c 3} {b 2} {a 1}] + _, _ = queue.Peek() // {c 3} true + _, _ = queue.Dequeue() // {c 3} true + _, _ = queue.Dequeue() // {b 2} true + _, _ = queue.Dequeue() // {a 1} true + _, _ = queue.Dequeue() // false (nothing to dequeue) + queue.Clear() // empty + _ = queue.Empty() // true + _ = queue.Size() // 0 +} diff --git a/examples/redblacktree.go b/examples/redblacktree.go deleted file mode 100644 index a5c54ec8..00000000 --- a/examples/redblacktree.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import ( - "fmt" - rbt "github.com/emirpasic/gods/trees/redblacktree" -) - -func RedBlacTtreeExample() { - tree := rbt.NewWithIntComparator() // empty(keys are of type int) - - tree.Put(1, "x") // 1->x - tree.Put(2, "b") // 1->x, 2->b (in order) - tree.Put(1, "a") // 1->a, 2->b (in order, replacement) - tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) - tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) - tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) - tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) - - fmt.Println(tree) - // - // RedBlackTree - // β”‚ β”Œβ”€β”€ 6 - // β”‚ β”Œβ”€β”€ 5 - // β”‚ β”Œβ”€β”€ 4 - // β”‚ β”‚ └── 3 - // └── 2 - // └── 1 - - _ = tree.Values() // []interface {}{"a", "b", "c", "d", "e", "f"} (in order) - _ = tree.Keys() // []interface {}{1, 2, 3, 4, 5, 6} (in order) - - tree.Remove(2) // 1->a, 3->c, 4->d, 5->e, 6->f (in order) - fmt.Println(tree) - // - // RedBlackTree - // β”‚ β”Œβ”€β”€ 6 - // β”‚ β”Œβ”€β”€ 5 - // └── 4 - // β”‚ β”Œβ”€β”€ 3 - // └── 1 - - tree.Clear() // empty - tree.Empty() // true - tree.Size() // 0 -} diff --git a/examples/redblacktree/redblacktree.go b/examples/redblacktree/redblacktree.go new file mode 100644 index 00000000..6661b166 --- /dev/null +++ b/examples/redblacktree/redblacktree.go @@ -0,0 +1,51 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +// RedBlackTreeExample to demonstrate basic usage of RedBlackTree +func main() { + tree := rbt.New[int, string]() // empty(keys are of type int) + + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + + fmt.Println(tree) + // + // RedBlackTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // β”‚ β”Œβ”€β”€ 4 + // β”‚ β”‚ └── 3 + // └── 2 + // └── 1 + + _ = tree.Values() // []interface {}{"a", "b", "c", "d", "e", "f"} (in order) + _ = tree.Keys() // []interface {}{1, 2, 3, 4, 5, 6} (in order) + + tree.Remove(2) // 1->a, 3->c, 4->d, 5->e, 6->f (in order) + fmt.Println(tree) + // + // RedBlackTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 1 + + tree.Clear() // empty + tree.Empty() // true + tree.Size() // 0 +} diff --git a/examples/redblacktreeextended/redblacktreeextended.go b/examples/redblacktreeextended/redblacktreeextended.go new file mode 100644 index 00000000..602dfecc --- /dev/null +++ b/examples/redblacktreeextended/redblacktreeextended.go @@ -0,0 +1,114 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package redblacktreeextended + +import ( + "fmt" + + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +// RedBlackTreeExtended to demonstrate how to extend a RedBlackTree to include new functions +type RedBlackTreeExtended[K comparable, V any] struct { + *rbt.Tree[K, V] +} + +// GetMin gets the min value and flag if found +func (tree *RedBlackTreeExtended[K, V]) GetMin() (value V, found bool) { + node, found := tree.getMinFromNode(tree.Root) + if found { + return node.Value, found + } + return value, false +} + +// GetMax gets the max value and flag if found +func (tree *RedBlackTreeExtended[K, V]) GetMax() (value V, found bool) { + node, found := tree.getMaxFromNode(tree.Root) + if found { + return node.Value, found + } + return value, false +} + +// RemoveMin removes the min value and flag if found +func (tree *RedBlackTreeExtended[K, V]) RemoveMin() (value V, deleted bool) { + node, found := tree.getMinFromNode(tree.Root) + if found { + tree.Remove(node.Key) + return node.Value, found + } + return value, false +} + +// RemoveMax removes the max value and flag if found +func (tree *RedBlackTreeExtended[K, V]) RemoveMax() (value V, deleted bool) { + node, found := tree.getMaxFromNode(tree.Root) + if found { + tree.Remove(node.Key) + return node.Value, found + } + return value, false +} + +func (tree *RedBlackTreeExtended[K, V]) getMinFromNode(node *rbt.Node[K, V]) (foundNode *rbt.Node[K, V], found bool) { + if node == nil { + return nil, false + } + if node.Left == nil { + return node, true + } + return tree.getMinFromNode(node.Left) +} + +func (tree *RedBlackTreeExtended[K, V]) getMaxFromNode(node *rbt.Node[K, V]) (foundNode *rbt.Node[K, V], found bool) { + if node == nil { + return nil, false + } + if node.Right == nil { + return node, true + } + return tree.getMaxFromNode(node.Right) +} + +func print(tree *RedBlackTreeExtended[int, string]) { + max, _ := tree.GetMax() + min, _ := tree.GetMin() + fmt.Printf("Value for max key: %v \n", max) + fmt.Printf("Value for min key: %v \n", min) + fmt.Println(tree) +} + +// RedBlackTreeExtendedExample main method on how to use the custom red-black tree above +func main() { + tree := RedBlackTreeExtended[int, string]{rbt.New[int, string]()} + + tree.Put(1, "a") // 1->x (in order) + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(3, "c") // 1->x, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->x, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->x, 2->b, 3->c, 4->d, 5->e (in order) + + print(&tree) + // Value for max key: e + // Value for min key: a + // RedBlackTree + // β”‚ β”Œβ”€β”€ 5 + // β”‚ β”Œβ”€β”€ 4 + // β”‚ β”‚ └── 3 + // └── 2 + // └── 1 + + tree.RemoveMin() // 2->b, 3->c, 4->d, 5->e (in order) + tree.RemoveMax() // 2->b, 3->c, 4->d (in order) + tree.RemoveMin() // 3->c, 4->d (in order) + tree.RemoveMax() // 3->c (in order) + + print(&tree) + // Value for max key: c + // Value for min key: c + // RedBlackTree + // └── 3 +} diff --git a/examples/serialization/serialization.go b/examples/serialization/serialization.go new file mode 100644 index 00000000..c03252ce --- /dev/null +++ b/examples/serialization/serialization.go @@ -0,0 +1,52 @@ +package serialization + +import ( + "fmt" + + "github.com/emirpasic/gods/v2/lists/arraylist" + "github.com/emirpasic/gods/v2/maps/hashmap" +) + +// ListSerializationExample demonstrates how to serialize and deserialize lists to and from JSON +func ListSerializationExample() { + list := arraylist.New[string]() + list.Add("a", "b", "c") + + // Serialization (marshalling) + json, err := list.ToJSON() + if err != nil { + fmt.Println(err) + } + fmt.Println(string(json)) // ["a","b","c"] + + // Deserialization (unmarshalling) + json = []byte(`["a","b"]`) + err = list.FromJSON(json) + if err != nil { + fmt.Println(err) + } + fmt.Println(list) // ArrayList ["a","b"] +} + +// MapSerializationExample demonstrates how to serialize and deserialize maps to and from JSON +func MapSerializationExample() { + m := hashmap.New[string, string]() + m.Put("a", "1") + m.Put("b", "2") + m.Put("c", "3") + + // Serialization (marshalling) + json, err := m.ToJSON() + if err != nil { + fmt.Println(err) + } + fmt.Println(string(json)) // {"a":"1","b":"2","c":"3"} + + // Deserialization (unmarshalling) + json = []byte(`{"a":"1","b":"2"}`) + err = m.FromJSON(json) + if err != nil { + fmt.Println(err) + } + fmt.Println(m) // HashMap {"a":"1","b":"2"} +} diff --git a/examples/singlylinkedlist.go b/examples/singlylinkedlist.go deleted file mode 100644 index 67252367..00000000 --- a/examples/singlylinkedlist.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import ( - sll "github.com/emirpasic/gods/lists/singlylinkedlist" - "github.com/emirpasic/gods/utils" -) - -func SinglyLinkedListExample() { - list := sll.New() - list.Add("a") // ["a"] - list.Append("b") // ["a","b"] (same as Add()) - list.Prepend("c") // ["c","a","b"] - list.Sort(utils.StringComparator) // ["a","b","c"] - _, _ = list.Get(0) // "a",true - _, _ = list.Get(100) // nil,false - _ = list.Contains("a", "b", "c") // true - _ = list.Contains("a", "b", "c", "d") // false - list.Remove(2) // ["a","b"] - list.Remove(1) // ["a"] - list.Remove(0) // [] - list.Remove(0) // [] (ignored) - _ = list.Empty() // true - _ = list.Size() // 0 - list.Add("a") // ["a"] - list.Clear() // [] -} diff --git a/examples/singlylinkedlist/singlylinkedlist.go b/examples/singlylinkedlist/singlylinkedlist.go new file mode 100644 index 00000000..3d327848 --- /dev/null +++ b/examples/singlylinkedlist/singlylinkedlist.go @@ -0,0 +1,32 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "cmp" + + sll "github.com/emirpasic/gods/v2/lists/singlylinkedlist" +) + +// SinglyLinkedListExample to demonstrate basic usage of SinglyLinkedList +func main() { + list := sll.New[string]() + list.Add("a") // ["a"] + list.Append("b") // ["a","b"] (same as Add()) + list.Prepend("c") // ["c","a","b"] + list.Sort(cmp.Compare[string]) // ["a","b","c"] + _, _ = list.Get(0) // "a",true + _, _ = list.Get(100) // nil,false + _ = list.Contains("a", "b", "c") // true + _ = list.Contains("a", "b", "c", "d") // false + list.Remove(2) // ["a","b"] + list.Remove(1) // ["a"] + list.Remove(0) // [] + list.Remove(0) // [] (ignored) + _ = list.Empty() // true + _ = list.Size() // 0 + list.Add("a") // ["a"] + list.Clear() // [] +} diff --git a/examples/sort.go b/examples/sort.go deleted file mode 100644 index b7133790..00000000 --- a/examples/sort.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import "github.com/emirpasic/gods/utils" - -func SortExample() { - strings := []interface{}{} // [] - strings = append(strings, "d") // ["d"] - strings = append(strings, "a") // ["d","a"] - strings = append(strings, "b") // ["d","a",b" - strings = append(strings, "c") // ["d","a",b","c"] - utils.Sort(strings, utils.StringComparator) // ["a","b","c","d"] -} diff --git a/examples/treebidimap/treebidimap.go b/examples/treebidimap/treebidimap.go new file mode 100644 index 00000000..f378eca2 --- /dev/null +++ b/examples/treebidimap/treebidimap.go @@ -0,0 +1,27 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "github.com/emirpasic/gods/v2/maps/treebidimap" +) + +// TreeBidiMapExample to demonstrate basic usage of TreeBidiMap +func main() { + m := treebidimap.New[int, string]() + m.Put(1, "x") // 1->x + m.Put(3, "b") // 1->x, 3->b (ordered) + m.Put(1, "a") // 1->a, 3->b (ordered) + m.Put(2, "b") // 1->a, 2->b (ordered) + _, _ = m.GetKey("a") // 1, true + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"a", "b"} (ordered) + _ = m.Keys() // []interface {}{1, 2} (ordered) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 +} diff --git a/examples/treemap.go b/examples/treemap.go deleted file mode 100644 index 45bea0b5..00000000 --- a/examples/treemap.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import "github.com/emirpasic/gods/maps/treemap" - -func TreeMapExample() { - m := treemap.NewWithIntComparator() // empty (keys are of type int) - m.Put(1, "x") // 1->x - m.Put(2, "b") // 1->x, 2->b (in order) - m.Put(1, "a") // 1->a, 2->b (in order) - _, _ = m.Get(2) // b, true - _, _ = m.Get(3) // nil, false - _ = m.Values() // []interface {}{"a", "b"} (in order) - _ = m.Keys() // []interface {}{1, 2} (in order) - m.Remove(1) // 2->b - m.Clear() // empty - m.Empty() // true - m.Size() // 0 -} diff --git a/examples/treemap/treemap.go b/examples/treemap/treemap.go new file mode 100644 index 00000000..4a1a21b2 --- /dev/null +++ b/examples/treemap/treemap.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/maps/treemap" + +// TreeMapExample to demonstrate basic usage of TreeMap +func main() { + m := treemap.New[int, string]() // empty + m.Put(1, "x") // 1->x + m.Put(2, "b") // 1->x, 2->b (in order) + m.Put(1, "a") // 1->a, 2->b (in order) + _, _ = m.Get(2) // b, true + _, _ = m.Get(3) // nil, false + _ = m.Values() // []interface {}{"a", "b"} (in order) + _ = m.Keys() // []interface {}{1, 2} (in order) + m.Remove(1) // 2->b + m.Clear() // empty + m.Empty() // true + m.Size() // 0 +} diff --git a/examples/treeset.go b/examples/treeset.go deleted file mode 100644 index 62e717f3..00000000 --- a/examples/treeset.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package examples - -import "github.com/emirpasic/gods/sets/treeset" - -func TreeSetExample() { - set := treeset.NewWithIntComparator() // empty - set.Add(1) // 1 - set.Add(2, 2, 3, 4, 5) // 1, 2, 3, 4, 5 (in order, duplicates ignored) - set.Remove(4) // 1, 2, 3, 5 (in order) - set.Remove(2, 3) // 1, 5 (in order) - set.Contains(1) // true - set.Contains(1, 5) // true - set.Contains(1, 6) // false - _ = set.Values() // []int{1,5} (in order) - set.Clear() // empty - set.Empty() // true - set.Size() // 0 -} diff --git a/examples/treeset/treeset.go b/examples/treeset/treeset.go new file mode 100644 index 00000000..e56b148e --- /dev/null +++ b/examples/treeset/treeset.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "github.com/emirpasic/gods/v2/sets/treeset" + +// TreeSetExample to demonstrate basic usage of TreeSet +func main() { + set := treeset.New[int]() // empty + set.Add(1) // 1 + set.Add(2, 2, 3, 4, 5) // 1, 2, 3, 4, 5 (in order, duplicates ignored) + set.Remove(4) // 1, 2, 3, 5 (in order) + set.Remove(2, 3) // 1, 5 (in order) + set.Contains(1) // true + set.Contains(1, 5) // true + set.Contains(1, 6) // false + _ = set.Values() // []int{1,5} (in order) + set.Clear() // empty + set.Empty() // true + set.Size() // 0 +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..4d864ff5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/emirpasic/gods/v2 + +go 1.21 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..b5ad6665 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= diff --git a/lists/arraylist/arraylist.go b/lists/arraylist/arraylist.go index f98b5e5f..163c28d0 100644 --- a/lists/arraylist/arraylist.go +++ b/lists/arraylist/arraylist.go @@ -1,194 +1,207 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of list using a slice. -// Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/List_%28abstract_data_type%29 +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// Package arraylist implements the array list. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29 package arraylist import ( "fmt" - "github.com/emirpasic/gods/lists" - "github.com/emirpasic/gods/utils" + "slices" "strings" + + "github.com/emirpasic/gods/v2/lists" + "github.com/emirpasic/gods/v2/utils" ) -func assertInterfaceImplementation() { - var _ lists.Interface = (*List)(nil) -} +// Assert List implementation +var _ lists.List[int] = (*List[int])(nil) -type List struct { - elements []interface{} - size int +// List holds the elements in a slice +type List[T comparable] struct { + elements []T } const ( - GROWTH_FACTOR = float32(2.0) // growth by 100% - SHRINK_FACTOR = float32(0.25) // shrink when size is 25% of capacity (0 means never shrink) + growthFactor = float32(2.0) // growth by 100% + shrinkFactor = float32(0.25) // shrink when size is 25% of capacity (0 means never shrink) ) -// Instantiates a new empty list -func New() *List { - return &List{} +// New instantiates a new list and adds the passed values, if any, to the list +func New[T comparable](values ...T) *List[T] { + list := &List[T]{} + if len(values) > 0 { + list.Add(values...) + } + return list } -// Appends a value at the end of the list -func (list *List) Add(elements ...interface{}) { - list.growBy(len(elements)) - for _, element := range elements { - list.elements[list.size] = element - list.size += 1 +// Add appends a value at the end of the list +func (list *List[T]) Add(values ...T) { + l := len(list.elements) + list.growBy(len(values)) + for i := range values { + list.elements[l+i] = values[i] } } -// Returns the element at index. +// Get returns the element at index. // Second return parameter is true if index is within bounds of the array and array is not empty, otherwise false. -func (list *List) Get(index int) (interface{}, bool) { +func (list *List[T]) Get(index int) (T, bool) { if !list.withinRange(index) { - return nil, false + var t T + return t, false } return list.elements[index], true } -// Removes one or more elements from the list with the supplied indices. -func (list *List) Remove(index int) { +// Remove removes the element at the given index from the list. +func (list *List[T]) Remove(index int) { if !list.withinRange(index) { return } - list.elements[index] = nil // cleanup reference - copy(list.elements[index:], list.elements[index+1:list.size]) // shift to the left by one (slow operation, need ways to optimize this) - list.size -= 1 - + list.elements = slices.Delete(list.elements, index, index+1) list.shrink() } -// Check if elements (one or more) are present in the set. +// Contains checks if elements (one or more) are present in the set. // All elements have to be present in the set for the method to return true. // Performance time complexity of n^2. // Returns true if no arguments are passed at all, i.e. set is always super-set of empty set. -func (list *List) Contains(elements ...interface{}) bool { - - for _, searchElement := range elements { - found := false - for _, element := range list.elements { - if element == searchElement { - found = true - break - } - } - if !found { +func (list *List[T]) Contains(values ...T) bool { + for _, searchValue := range values { + if !slices.Contains(list.elements, searchValue) { return false } } return true } -// Returns all elements in the list. -func (list *List) Values() []interface{} { - newElements := make([]interface{}, list.size, list.size) - copy(newElements, list.elements[:list.size]) - return newElements +// Values returns all elements in the list. +func (list *List[T]) Values() []T { + return slices.Clone(list.elements) } -// Returns true if list does not contain any elements. -func (list *List) Empty() bool { - return list.size == 0 +// IndexOf returns index of provided element +func (list *List[T]) IndexOf(value T) int { + return slices.Index(list.elements, value) } -// Returns number of elements within the list. -func (list *List) Size() int { - return list.size +// Empty returns true if list does not contain any elements. +func (list *List[T]) Empty() bool { + return len(list.elements) == 0 } -// Removes all elements from the list. -func (list *List) Clear() { - list.size = 0 - list.elements = []interface{}{} +// Size returns number of elements within the list. +func (list *List[T]) Size() int { + return len(list.elements) } -// Sorts values (in-place) using timsort. -func (list *List) Sort(comparator utils.Comparator) { +// Clear removes all elements from the list. +func (list *List[T]) Clear() { + clear(list.elements[:cap(list.elements)]) + list.elements = list.elements[:0] +} + +// Sort sorts values (in-place) using. +func (list *List[T]) Sort(comparator utils.Comparator[T]) { if len(list.elements) < 2 { return } - utils.Sort(list.elements[:list.size], comparator) + slices.SortFunc(list.elements, comparator) } -// Swaps values of two elements at the given indices. -func (list *List) Swap(i, j int) { +// Swap swaps the two values at the specified positions. +func (list *List[T]) Swap(i, j int) { if list.withinRange(i) && list.withinRange(j) { list.elements[i], list.elements[j] = list.elements[j], list.elements[i] } } -func (list *List) String() string { +// Insert inserts values at specified index position shifting the value at that position (if any) and any subsequent elements to the right. +// Does not do anything if position is negative or bigger than list's size +// Note: position equal to list's size is valid, i.e. append. +func (list *List[T]) Insert(index int, values ...T) { + if !list.withinRange(index) { + // Append + if index == len(list.elements) { + list.Add(values...) + } + return + } + + l := len(list.elements) + list.growBy(len(values)) + list.elements = slices.Insert(list.elements[:l], index, values...) +} + +// Set the value at specified index +// Does not do anything if position is negative or bigger than list's size +// Note: position equal to list's size is valid, i.e. append. +func (list *List[T]) Set(index int, value T) { + + if !list.withinRange(index) { + // Append + if index == len(list.elements) { + list.Add(value) + } + return + } + + list.elements[index] = value +} + +// String returns a string representation of container +func (list *List[T]) String() string { str := "ArrayList\n" - values := []string{} - for _, value := range list.elements[:list.size] { + values := make([]string, 0, len(list.elements)) + for _, value := range list.elements { values = append(values, fmt.Sprintf("%v", value)) } str += strings.Join(values, ", ") return str } -// Check that the index is withing bounds of the list -func (list *List) withinRange(index int) bool { - return index >= 0 && index < list.size && list.size != 0 +// Check that the index is within bounds of the list +func (list *List[T]) withinRange(index int) bool { + return index >= 0 && index < len(list.elements) } -func (list *List) resize(cap int) { - newElements := make([]interface{}, cap, cap) +func (list *List[T]) resize(len, cap int) { + newElements := make([]T, len, cap) copy(newElements, list.elements) list.elements = newElements } // Expand the array if necessary, i.e. capacity will be reached if we add n elements -func (list *List) growBy(n int) { - // When capacity is reached, grow by a factor of GROWTH_FACTOR and add number of elements +func (list *List[T]) growBy(n int) { + // When capacity is reached, grow by a factor of growthFactor and add number of elements currentCapacity := cap(list.elements) - if list.size+n >= currentCapacity { - newCapacity := int(GROWTH_FACTOR * float32(currentCapacity+n)) - list.resize(newCapacity) + + if newLength := len(list.elements) + n; newLength >= currentCapacity { + newCapacity := int(growthFactor * float32(currentCapacity+n)) + list.resize(newLength, newCapacity) + } else { + list.elements = list.elements[:newLength] } } -// Shrink the array if necessary, i.e. when size is SHRINK_FACTOR percent of current capacity -func (list *List) shrink() { - if SHRINK_FACTOR == 0.0 { +// Shrink the array if necessary, i.e. when size is shrinkFactor percent of current capacity +func (list *List[T]) shrink() { + if shrinkFactor == 0.0 { return } - // Shrink when size is at SHRINK_FACTOR * capacity + // Shrink when size is at shrinkFactor * capacity currentCapacity := cap(list.elements) - if list.size <= int(float32(currentCapacity)*SHRINK_FACTOR) { - list.resize(list.size) + if len(list.elements) <= int(float32(currentCapacity)*shrinkFactor) { + list.resize(len(list.elements), len(list.elements)) } - } diff --git a/lists/arraylist/arraylist_test.go b/lists/arraylist/arraylist_test.go index 4eb239fa..614b436d 100644 --- a/lists/arraylist/arraylist_test.go +++ b/lists/arraylist/arraylist_test.go @@ -1,136 +1,804 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package arraylist import ( - "github.com/emirpasic/gods/utils" + "cmp" + "encoding/json" + "slices" + "strings" "testing" ) -func TestArrayList(t *testing.T) { +func TestListNew(t *testing.T) { + list1 := New[int]() - list := New() + if actualValue := list1.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } - list.Sort(utils.StringComparator) + list2 := New[int](1, 2) - list.Add("e", "f", "g", "a", "b", "c", "d") + if actualValue := list2.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + + if actualValue, ok := list2.Get(0); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } + + if actualValue, ok := list2.Get(1); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + + if actualValue, ok := list2.Get(2); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestListAdd(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue := list.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } +} + +func TestListIndexOf(t *testing.T) { + list := New[string]() + + expectedIndex := -1 + if index := list.IndexOf("a"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } - list.Sort(utils.StringComparator) + list.Add("a") + list.Add("b", "c") + + expectedIndex = 0 + if index := list.IndexOf("a"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } + + expectedIndex = 1 + if index := list.IndexOf("b"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } + + expectedIndex = 2 + if index := list.IndexOf("c"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } +} + +func TestListRemove(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + list.Remove(2) + if actualValue, ok := list.Get(2); actualValue != "" || ok { + t.Errorf("Got %v expected %v", actualValue, "") + } + list.Remove(1) + list.Remove(0) + list.Remove(0) // no effect + if actualValue := list.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestListGet(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue, ok := list.Get(0); actualValue != "a" || !ok { + t.Errorf("Got %v expected %v", actualValue, "a") + } + if actualValue, ok := list.Get(1); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } + if actualValue, ok := list.Get(3); actualValue != "" || ok { + t.Errorf("Got %v expected %v", actualValue, "") + } + list.Remove(0) + if actualValue, ok := list.Get(0); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } +} + +func TestListSwap(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + list.Swap(0, 1) + if actualValue, ok := list.Get(0); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } +} + +func TestListSort(t *testing.T) { + list := New[string]() + list.Sort(cmp.Compare[string]) + list.Add("e", "f", "g", "a", "b", "c", "d") + list.Sort(cmp.Compare[string]) for i := 1; i < list.Size(); i++ { a, _ := list.Get(i - 1) b, _ := list.Get(i) - if a.(string) > b.(string) { + if a > b { t.Errorf("Not sorted! %s > %s", a, b) } } +} +func TestListClear(t *testing.T) { + list := New[string]() + list.Add("e", "f", "g", "a", "b", "c", "d") list.Clear() - if actualValue := list.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := list.Size(); actualValue != 0 { t.Errorf("Got %v expected %v", actualValue, 0) } +} +func TestListContains(t *testing.T) { + list := New[string]() list.Add("a") list.Add("b", "c") - - if actualValue := list.Empty(); actualValue != false { + if actualValue := list.Contains("a"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Contains(""); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } + if actualValue := list.Contains("a", "b", "c"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Contains("a", "b", "c", "d"); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + list.Clear() + if actualValue := list.Contains("a"); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Contains("a", "b", "c"); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } +} +func TestListValues(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue, expectedValue := list.Values(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestListInsert(t *testing.T) { + list := New[string]() + list.Insert(0, "b", "c") + list.Insert(0, "a") + list.Insert(10, "x") // ignore if actualValue := list.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } + list.Insert(3, "d") // append + if actualValue := list.Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + if actualValue, expectedValue := strings.Join(list.Values(), ""), "abcd"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} - if actualValue, ok := list.Get(2); actualValue != "c" || !ok { - t.Errorf("Got %v expected %v", actualValue, "c") +func TestListSet(t *testing.T) { + list := New[string]() + list.Set(0, "a") + list.Set(1, "b") + if actualValue := list.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + list.Set(2, "c") // append + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + list.Set(4, "d") // ignore + list.Set(1, "bb") // update + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) } + if actualValue, expectedValue := list.Values(), []string{"a", "bb", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} - list.Swap(0, 1) +func TestListEach(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + list.Each(func(index int, value string) { + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} - if actualValue, ok := list.Get(0); actualValue != "b" || !ok { +func TestListMap(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + mappedList := list.Map(func(index int, value string) string { + return "mapped: " + value + }) + if actualValue, _ := mappedList.Get(0); actualValue != "mapped: a" { + t.Errorf("Got %v expected %v", actualValue, "mapped: a") + } + if actualValue, _ := mappedList.Get(1); actualValue != "mapped: b" { + t.Errorf("Got %v expected %v", actualValue, "mapped: b") + } + if actualValue, _ := mappedList.Get(2); actualValue != "mapped: c" { + t.Errorf("Got %v expected %v", actualValue, "mapped: c") + } + if mappedList.Size() != 3 { + t.Errorf("Got %v expected %v", mappedList.Size(), 3) + } +} + +func TestListSelect(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + selectedList := list.Select(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if actualValue, _ := selectedList.Get(0); actualValue != "a" { + t.Errorf("Got %v expected %v", actualValue, "value: a") + } + if actualValue, _ := selectedList.Get(1); actualValue != "b" { + t.Errorf("Got %v expected %v", actualValue, "value: b") + } + if selectedList.Size() != 2 { + t.Errorf("Got %v expected %v", selectedList.Size(), 3) + } +} + +func TestListAny(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + any := list.Any(func(index int, value string) bool { + return value == "c" + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = list.Any(func(index int, value string) bool { + return value == "x" + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} +func TestListAll(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + all := list.All(func(index int, value string) bool { + return value >= "a" && value <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = list.All(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} +func TestListFind(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + foundIndex, foundValue := list.Find(func(index int, value string) bool { + return value == "c" + }) + if foundValue != "c" || foundIndex != 2 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, "c", 2) + } + foundIndex, foundValue = list.Find(func(index int, value string) bool { + return value == "x" + }) + if foundValue != "" || foundIndex != -1 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, nil, nil) + } +} +func TestListChaining(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + chainedList := list.Select(func(index int, value string) bool { + return value > "a" + }).Map(func(index int, value string) string { + return value + value + }) + if chainedList.Size() != 2 { + t.Errorf("Got %v expected %v", chainedList.Size(), 2) + } + if actualValue, ok := chainedList.Get(0); actualValue != "bb" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } + if actualValue, ok := chainedList.Get(1); actualValue != "cc" || !ok { t.Errorf("Got %v expected %v", actualValue, "c") } +} - list.Remove(2) +func TestListIteratorNextOnEmpty(t *testing.T) { + list := New[string]() + it := list.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty list") + } +} - if actualValue, ok := list.Get(2); actualValue != nil || ok { - t.Errorf("Got %v expected %v", actualValue, nil) +func TestListIteratorNext(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + it := list.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } +} - list.Remove(1) - list.Remove(0) +func TestListIteratorPrevOnEmpty(t *testing.T) { + list := New[string]() + it := list.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty list") + } +} - if actualValue := list.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestListIteratorPrev(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + it := list.Iterator() + for it.Next() { + } + count := 0 + for it.Prev() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } +} - if actualValue := list.Size(); actualValue != 0 { - t.Errorf("Got %v expected %v", actualValue, 0) +func TestListIteratorBegin(t *testing.T) { + list := New[string]() + it := list.Iterator() + it.Begin() + list.Add("a", "b", "c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestListIteratorEnd(t *testing.T) { + list := New[string]() + it := list.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + list.Add("a", "b", "c") + it.End() + if index := it.Index(); index != list.Size() { + t.Errorf("Got %v expected %v", index, list.Size()) } + it.Prev() + if index, value := it.Index(), it.Value(); index != list.Size()-1 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, list.Size()-1, "c") + } +} + +func TestListIteratorFirst(t *testing.T) { + list := New[string]() + it := list.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } list.Add("a", "b", "c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} - if actualValue := list.Contains("a", "b", "c"); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestListIteratorLast(t *testing.T) { + list := New[string]() + it := list.Iterator() + if actualValue, expectedValue := it.Last(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + list.Add("a", "b", "c") + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "c") } +} - if actualValue := list.Contains("a", "b", "c", "d"); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) +func TestListIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") } - list.Clear() + // NextTo (empty) + { + list := New[string]() + it := list.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } - if actualValue := list.Contains("a"); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) + // NextTo (not found) + { + list := New[string]() + list.Add("xx", "yy") + it := list.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } } - if actualValue, ok := list.Get(0); actualValue != nil || ok { - t.Errorf("Got %v expected %v", actualValue, false) + // NextTo (found) + { + list := New[string]() + list.Add("aa", "bb", "cc") + it := list.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } } +} - if actualValue := list.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestListIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + list := New[string]() + it := list.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } } + // PrevTo (not found) + { + list := New[string]() + list.Add("xx", "yy") + it := list.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // PrevTo (found) + { + list := New[string]() + list.Add("aa", "bb", "cc") + it := list.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } } -func BenchmarkArrayList(b *testing.B) { +func TestListSerialization(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + + var err error + assert := func() { + if actualValue, expectedValue := list.Values(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := list.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := list.ToJSON() + assert() + + err = list.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]any{"a", "b", "c", list}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &list) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestListString(t *testing.T) { + c := New[int]() + c.Add(1) + if !strings.HasPrefix(c.String(), "ArrayList") { + t.Errorf("String should start with container name") + } +} + +func benchmarkGet(b *testing.B, list *List[int], size int) { for i := 0; i < b.N; i++ { - list := New() - for n := 0; n < 1000; n++ { - list.Add(i) + for n := 0; n < size; n++ { + list.Get(n) } - for !list.Empty() { - list.Remove(0) + } +} + +func benchmarkAdd(b *testing.B, list *List[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + list.Add(n) + } + } +} + +func benchmarkRemove(b *testing.B, list *List[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + list.Remove(n) } } +} + +func BenchmarkArrayListGet100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkArrayListGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkArrayListGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkArrayListGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} +func BenchmarkArrayListAdd100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkArrayListAdd1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkArrayListAdd10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkArrayListAdd100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkArrayListRemove100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkArrayListRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkArrayListRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkArrayListRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) } diff --git a/lists/arraylist/enumerable.go b/lists/arraylist/enumerable.go new file mode 100644 index 00000000..94589d72 --- /dev/null +++ b/lists/arraylist/enumerable.go @@ -0,0 +1,79 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arraylist + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Enumerable implementation +var _ containers.EnumerableWithIndex[int] = (*List[int])(nil) + +// Each calls the given function once for each element, passing that element's index and value. +func (list *List[T]) Each(f func(index int, value T)) { + iterator := list.Iterator() + for iterator.Next() { + f(iterator.Index(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a +// container containing the values returned by the given function. +func (list *List[T]) Map(f func(index int, value T) T) *List[T] { + newList := &List[T]{} + iterator := list.Iterator() + for iterator.Next() { + newList.Add(f(iterator.Index(), iterator.Value())) + } + return newList +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (list *List[T]) Select(f func(index int, value T) bool) *List[T] { + newList := &List[T]{} + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + newList.Add(iterator.Value()) + } + } + return newList +} + +// Any passes each element of the collection to the given function and +// returns true if the function ever returns true for any element. +func (list *List[T]) Any(f func(index int, value T) bool) bool { + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the collection to the given function and +// returns true if the function returns true for all elements. +func (list *List[T]) All(f func(index int, value T) bool) bool { + iterator := list.Iterator() + for iterator.Next() { + if !f(iterator.Index(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (index,value) for which the function is true or -1,nil otherwise +// if no element matches the criteria. +func (list *List[T]) Find(f func(index int, value T) bool) (int, T) { + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return iterator.Index(), iterator.Value() + } + } + var t T + return -1, t +} diff --git a/lists/arraylist/iterator.go b/lists/arraylist/iterator.go new file mode 100644 index 00000000..3c2c6e78 --- /dev/null +++ b/lists/arraylist/iterator.go @@ -0,0 +1,110 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arraylist + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator holding the iterator's state +type Iterator[T comparable] struct { + list *List[T] + index int +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (list *List[T]) Iterator() *Iterator[T] { + return &Iterator[T]{list: list, index: -1} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.list.Size() { + iterator.index++ + } + return iterator.list.withinRange(iterator.index) +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + if iterator.index >= 0 { + iterator.index-- + } + return iterator.list.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + return iterator.list.elements[iterator.index] +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.index = iterator.list.Size() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/lists/arraylist/serialization.go b/lists/arraylist/serialization.go new file mode 100644 index 00000000..73074102 --- /dev/null +++ b/lists/arraylist/serialization.go @@ -0,0 +1,36 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arraylist + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*List[int])(nil) +var _ containers.JSONDeserializer = (*List[int])(nil) + +// ToJSON outputs the JSON representation of list's elements. +func (list *List[T]) ToJSON() ([]byte, error) { + return json.Marshal(list.elements) +} + +// FromJSON populates list's elements from the input JSON representation. +func (list *List[T]) FromJSON(data []byte) error { + err := json.Unmarshal(data, &list.elements) + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (list *List[T]) UnmarshalJSON(bytes []byte) error { + return list.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (list *List[T]) MarshalJSON() ([]byte, error) { + return list.ToJSON() +} diff --git a/lists/doublylinkedlist/doublylinkedlist.go b/lists/doublylinkedlist/doublylinkedlist.go index ed40eebf..dcbd0e51 100644 --- a/lists/doublylinkedlist/doublylinkedlist.go +++ b/lists/doublylinkedlist/doublylinkedlist.go @@ -1,67 +1,52 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of doubly linked list. -// Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/Doubly_linked_list +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// Package doublylinkedlist implements the doubly-linked list. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29 package doublylinkedlist import ( "fmt" - "github.com/emirpasic/gods/lists" - "github.com/emirpasic/gods/utils" + "slices" "strings" + + "github.com/emirpasic/gods/v2/lists" + "github.com/emirpasic/gods/v2/utils" ) -func assertInterfaceImplementation() { - var _ lists.Interface = (*List)(nil) -} +// Assert List implementation +var _ lists.List[any] = (*List[any])(nil) -type List struct { - first *element - last *element +// List holds the elements, where each element points to the next and previous element +type List[T comparable] struct { + first *element[T] + last *element[T] size int } -type element struct { - value interface{} - prev *element - next *element +type element[T comparable] struct { + value T + prev *element[T] + next *element[T] } -// Instantiates a new empty list -func New() *List { - return &List{} +// New instantiates a new list and adds the passed values, if any, to the list +func New[T comparable](values ...T) *List[T] { + list := &List[T]{} + if len(values) > 0 { + list.Add(values...) + } + return list } -// Appends a value (one or more) at the end of the list (same as Append()) -func (list *List) Add(values ...interface{}) { +// Add appends a value (one or more) at the end of the list (same as Append()) +func (list *List[T]) Add(values ...T) { for _, value := range values { - newElement := &element{value: value, prev: list.last} + newElement := &element[T]{value: value, prev: list.last} if list.size == 0 { list.first = newElement list.last = newElement @@ -73,16 +58,16 @@ func (list *List) Add(values ...interface{}) { } } -// Appends a value (one or more) at the end of the list (same as Add()) -func (list *List) Append(values ...interface{}) { +// Append appends a value (one or more) at the end of the list (same as Add()) +func (list *List[T]) Append(values ...T) { list.Add(values...) } -// Prepends a values (or more) -func (list *List) Prepend(values ...interface{}) { +// Prepend prepends a values (or more) +func (list *List[T]) Prepend(values ...T) { // in reverse to keep passed order i.e. ["c","d"] -> Prepend(["a","b"]) -> ["a","b","c",d"] for v := len(values) - 1; v >= 0; v-- { - newElement := &element{value: values[v], next: list.first} + newElement := &element[T]{value: values[v], next: list.first} if list.size == 0 { list.first = newElement list.last = newElement @@ -94,31 +79,30 @@ func (list *List) Prepend(values ...interface{}) { } } -// Returns the element at index. +// Get returns the element at index. // Second return parameter is true if index is within bounds of the array and array is not empty, otherwise false. -func (list *List) Get(index int) (interface{}, bool) { +func (list *List[T]) Get(index int) (T, bool) { if !list.withinRange(index) { - return nil, false + var t T + return t, false } - // determine traveral direction, last to first or first to last + // determine traversal direction, last to first or first to last if list.size-index < index { element := list.last for e := list.size - 1; e != index; e, element = e-1, element.prev { } return element.value, true - } else { - element := list.first - for e := 0; e != index; e, element = e+1, element.next { - } - return element.value, true } - + element := list.first + for e := 0; e != index; e, element = e+1, element.next { + } + return element.value, true } -// Removes one or more elements from the list with the supplied indices. -func (list *List) Remove(index int) { +// Remove removes the element at the given index from the list. +func (list *List[T]) Remove(index int) { if !list.withinRange(index) { return @@ -129,7 +113,7 @@ func (list *List) Remove(index int) { return } - var element *element + var element *element[T] // determine traversal direction, last to first or first to last if list.size-index < index { element = list.last @@ -159,11 +143,11 @@ func (list *List) Remove(index int) { list.size-- } -// Check if values (one or more) are present in the set. +// Contains check if values (one or more) are present in the set. // All values have to be present in the set for the method to return true. // Performance time complexity of n^2. // Returns true if no arguments are passed at all, i.e. set is always super-set of empty set. -func (list *List) Contains(values ...interface{}) bool { +func (list *List[T]) Contains(values ...T) bool { if len(values) == 0 { return true @@ -186,41 +170,54 @@ func (list *List) Contains(values ...interface{}) bool { return true } -// Returns all elements in the list. -func (list *List) Values() []interface{} { - values := make([]interface{}, list.size, list.size) +// Values returns all elements in the list. +func (list *List[T]) Values() []T { + values := make([]T, list.size, list.size) for e, element := 0, list.first; element != nil; e, element = e+1, element.next { values[e] = element.value } return values } -// Returns true if list does not contain any elements. -func (list *List) Empty() bool { +// IndexOf returns index of provided element +func (list *List[T]) IndexOf(value T) int { + if list.size == 0 { + return -1 + } + for index, element := range list.Values() { + if element == value { + return index + } + } + return -1 +} + +// Empty returns true if list does not contain any elements. +func (list *List[T]) Empty() bool { return list.size == 0 } -// Returns number of elements within the list. -func (list *List) Size() int { +// Size returns number of elements within the list. +func (list *List[T]) Size() int { return list.size } -// Removes all elements from the list. -func (list *List) Clear() { +// Clear removes all elements from the list. +func (list *List[T]) Clear() { list.size = 0 list.first = nil list.last = nil } -// Sorts values (in-place) using timsort. -func (list *List) Sort(comparator utils.Comparator) { +// Sort sorts values (in-place) using a Comparator. +func (list *List[T]) Sort(comparator utils.Comparator[T]) { if list.size < 2 { return } values := list.Values() - utils.Sort(values, comparator) + slices.SortFunc(values, comparator) list.Clear() @@ -228,10 +225,10 @@ func (list *List) Sort(comparator utils.Comparator) { } -// Swaps values of two elements at the given indices. -func (list *List) Swap(i, j int) { +// Swap swaps values of two elements at the given indices. +func (list *List[T]) Swap(i, j int) { if list.withinRange(i) && list.withinRange(j) && i != j { - var element1, element2 *element + var element1, element2 *element[T] for e, currentElement := 0, list.first; element1 == nil || element2 == nil; e, currentElement = e+1, currentElement.next { switch e { case i: @@ -244,7 +241,97 @@ func (list *List) Swap(i, j int) { } } -func (list *List) String() string { +// Insert inserts values at specified index position shifting the value at that position (if any) and any subsequent elements to the right. +// Does not do anything if position is negative or bigger than list's size +// Note: position equal to list's size is valid, i.e. append. +func (list *List[T]) Insert(index int, values ...T) { + + if !list.withinRange(index) { + // Append + if index == list.size { + list.Add(values...) + } + return + } + + var beforeElement *element[T] + var foundElement *element[T] + // determine traversal direction, last to first or first to last + if list.size-index < index { + foundElement = list.last + beforeElement = list.last.prev + for e := list.size - 1; e != index; e, foundElement = e-1, foundElement.prev { + beforeElement = beforeElement.prev + } + } else { + foundElement = list.first + for e := 0; e != index; e, foundElement = e+1, foundElement.next { + beforeElement = foundElement + } + } + + if foundElement == list.first { + oldNextElement := list.first + for i, value := range values { + newElement := &element[T]{value: value} + if i == 0 { + list.first = newElement + } else { + newElement.prev = beforeElement + beforeElement.next = newElement + } + beforeElement = newElement + } + oldNextElement.prev = beforeElement + beforeElement.next = oldNextElement + } else { + oldNextElement := beforeElement.next + for _, value := range values { + newElement := &element[T]{value: value} + newElement.prev = beforeElement + beforeElement.next = newElement + beforeElement = newElement + } + oldNextElement.prev = beforeElement + beforeElement.next = oldNextElement + } + + list.size += len(values) +} + +// Set value at specified index position +// Does not do anything if position is negative or bigger than list's size +// Note: position equal to list's size is valid, i.e. append. +func (list *List[T]) Set(index int, value T) { + + if !list.withinRange(index) { + // Append + if index == list.size { + list.Add(value) + } + return + } + + var foundElement *element[T] + // determine traversal direction, last to first or first to last + if list.size-index < index { + foundElement = list.last + for e := list.size - 1; e != index; { + fmt.Println("Set last", index, value, foundElement, foundElement.prev) + e, foundElement = e-1, foundElement.prev + } + } else { + foundElement = list.first + for e := 0; e != index; { + e, foundElement = e+1, foundElement.next + } + } + + foundElement.value = value +} + +// String returns a string representation of container +func (list *List[T]) String() string { str := "DoublyLinkedList\n" values := []string{} for element := list.first; element != nil; element = element.next { @@ -254,7 +341,7 @@ func (list *List) String() string { return str } -// Check that the index is withing bounds of the list -func (list *List) withinRange(index int) bool { - return index >= 0 && index < list.size && list.size != 0 +// Check that the index is within bounds of the list +func (list *List[T]) withinRange(index int) bool { + return index >= 0 && index < list.size } diff --git a/lists/doublylinkedlist/doublylinkedlist_test.go b/lists/doublylinkedlist/doublylinkedlist_test.go index 0c78c7ba..4a4afdc7 100644 --- a/lists/doublylinkedlist/doublylinkedlist_test.go +++ b/lists/doublylinkedlist/doublylinkedlist_test.go @@ -1,137 +1,806 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package doublylinkedlist import ( - "github.com/emirpasic/gods/utils" + "cmp" + "encoding/json" + "slices" + "strings" "testing" ) -func TestDoublyLinkedList(t *testing.T) { +func TestListNew(t *testing.T) { + list1 := New[int]() - list := New() + if actualValue := list1.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } - list.Sort(utils.StringComparator) + list2 := New[int](1, 2) - list.Add("e", "f", "g", "a", "b", "c", "d") + if actualValue := list2.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + + if actualValue, ok := list2.Get(0); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } + + if actualValue, ok := list2.Get(1); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + + if actualValue, ok := list2.Get(2); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestListAdd(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue := list.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } +} + +func TestListAppendAndPrepend(t *testing.T) { + list := New[string]() + list.Add("b") + list.Prepend("a") + list.Append("c") + if actualValue := list.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := list.Get(0); actualValue != "a" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } + if actualValue, ok := list.Get(1); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } +} + +func TestListRemove(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + list.Remove(2) + if actualValue, ok := list.Get(2); actualValue != "" || ok { + t.Errorf("Got %v expected %v", actualValue, "") + } + list.Remove(1) + list.Remove(0) + list.Remove(0) // no effect + if actualValue := list.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestListGet(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue, ok := list.Get(0); actualValue != "a" || !ok { + t.Errorf("Got %v expected %v", actualValue, "a") + } + if actualValue, ok := list.Get(1); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } + if actualValue, ok := list.Get(3); actualValue != "" || ok { + t.Errorf("Got %v expected %v", actualValue, "") + } + list.Remove(0) + if actualValue, ok := list.Get(0); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } +} + +func TestListSwap(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + list.Swap(0, 1) + if actualValue, ok := list.Get(0); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } +} - list.Sort(utils.StringComparator) +func TestListSort(t *testing.T) { + list := New[string]() + list.Sort(cmp.Compare[string]) + list.Add("e", "f", "g", "a", "b", "c", "d") + list.Sort(cmp.Compare[string]) for i := 1; i < list.Size(); i++ { a, _ := list.Get(i - 1) b, _ := list.Get(i) - if a.(string) > b.(string) { + if a > b { t.Errorf("Not sorted! %s > %s", a, b) } } +} +func TestListClear(t *testing.T) { + list := New[string]() + list.Add("e", "f", "g", "a", "b", "c", "d") list.Clear() - if actualValue := list.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := list.Size(); actualValue != 0 { t.Errorf("Got %v expected %v", actualValue, 0) } +} +func TestListContains(t *testing.T) { + list := New[string]() list.Add("a") list.Add("b", "c") - - if actualValue := list.Empty(); actualValue != false { + if actualValue := list.Contains("a"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Contains(""); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Contains("a", "b", "c"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Contains("a", "b", "c", "d"); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + list.Clear() + if actualValue := list.Contains("a"); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } + if actualValue := list.Contains("a", "b", "c"); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } +} + +func TestListValues(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue, expectedValue := list.Values(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} +func TestListInsert(t *testing.T) { + list := New[string]() + list.Insert(0, "b", "c", "d") + list.Insert(0, "a") + list.Insert(10, "x") // ignore + if actualValue := list.Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + list.Insert(4, "g") // append + if actualValue := list.Size(); actualValue != 5 { + t.Errorf("Got %v expected %v", actualValue, 5) + } + if actualValue, expectedValue := strings.Join(list.Values(), ""), "abcdg"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + list.Insert(4, "e", "f") // last to first traversal + if actualValue := list.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + if actualValue, expectedValue := strings.Join(list.Values(), ""), "abcdefg"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestListSet(t *testing.T) { + list := New[string]() + list.Set(0, "a") + list.Set(1, "b") + if actualValue := list.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + list.Set(2, "c") // append if actualValue := list.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } + list.Set(4, "d") // ignore + list.Set(1, "bb") // update + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, expectedValue := list.Values(), []string{"a", "bb", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} - if actualValue, ok := list.Get(2); actualValue != "c" || !ok { - t.Errorf("Got %v expected %v", actualValue, "c") +func TestListEach(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + list.Each(func(index int, value string) { + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} + +func TestListMap(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + mappedList := list.Map(func(index int, value string) string { + return "mapped: " + value + }) + if actualValue, _ := mappedList.Get(0); actualValue != "mapped: a" { + t.Errorf("Got %v expected %v", actualValue, "mapped: a") + } + if actualValue, _ := mappedList.Get(1); actualValue != "mapped: b" { + t.Errorf("Got %v expected %v", actualValue, "mapped: b") + } + if actualValue, _ := mappedList.Get(2); actualValue != "mapped: c" { + t.Errorf("Got %v expected %v", actualValue, "mapped: c") + } + if mappedList.Size() != 3 { + t.Errorf("Got %v expected %v", mappedList.Size(), 3) } +} - list.Swap(0, 2) - list.Swap(0, 2) - list.Swap(0, 1) +func TestListSelect(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + selectedList := list.Select(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if actualValue, _ := selectedList.Get(0); actualValue != "a" { + t.Errorf("Got %v expected %v", actualValue, "value: a") + } + if actualValue, _ := selectedList.Get(1); actualValue != "b" { + t.Errorf("Got %v expected %v", actualValue, "value: b") + } + if selectedList.Size() != 2 { + t.Errorf("Got %v expected %v", selectedList.Size(), 3) + } +} - if actualValue, ok := list.Get(0); actualValue != "b" || !ok { +func TestListAny(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + any := list.Any(func(index int, value string) bool { + return value == "c" + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = list.Any(func(index int, value string) bool { + return value == "x" + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} +func TestListAll(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + all := list.All(func(index int, value string) bool { + return value >= "a" && value <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = list.All(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} +func TestListFind(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + foundIndex, foundValue := list.Find(func(index int, value string) bool { + return value == "c" + }) + if foundValue != "c" || foundIndex != 2 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, "c", 2) + } + foundIndex, foundValue = list.Find(func(index int, value string) bool { + return value == "x" + }) + if foundValue != "" || foundIndex != -1 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, nil, nil) + } +} +func TestListChaining(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + chainedList := list.Select(func(index int, value string) bool { + return value > "a" + }).Map(func(index int, value string) string { + return value + value + }) + if chainedList.Size() != 2 { + t.Errorf("Got %v expected %v", chainedList.Size(), 2) + } + if actualValue, ok := chainedList.Get(0); actualValue != "bb" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } + if actualValue, ok := chainedList.Get(1); actualValue != "cc" || !ok { t.Errorf("Got %v expected %v", actualValue, "c") } +} - list.Remove(2) +func TestListIteratorNextOnEmpty(t *testing.T) { + list := New[string]() + it := list.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty list") + } +} - if actualValue, ok := list.Get(2); actualValue != nil || ok { - t.Errorf("Got %v expected %v", actualValue, nil) +func TestListIteratorNext(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + it := list.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} - list.Remove(1) - list.Remove(0) +func TestListIteratorPrevOnEmpty(t *testing.T) { + list := New[string]() + it := list.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty list") + } +} - if actualValue := list.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestListIteratorPrev(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + it := list.Iterator() + for it.Next() { + } + count := 0 + for it.Prev() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} - if actualValue := list.Size(); actualValue != 0 { - t.Errorf("Got %v expected %v", actualValue, 0) +func TestListIteratorBegin(t *testing.T) { + list := New[string]() + it := list.Iterator() + it.Begin() + list.Add("a", "b", "c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestListIteratorEnd(t *testing.T) { + list := New[string]() + it := list.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) } list.Add("a", "b", "c") + it.End() + if index := it.Index(); index != list.Size() { + t.Errorf("Got %v expected %v", index, list.Size()) + } - if actualValue := list.Contains("a", "b", "c"); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) + it.Prev() + if index, value := it.Index(), it.Value(); index != list.Size()-1 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, list.Size()-1, "c") } +} - if actualValue := list.Contains("a", "b", "c", "d"); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) +func TestListIteratorFirst(t *testing.T) { + list := New[string]() + it := list.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } + list.Add("a", "b", "c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} - list.Clear() +func TestListIteratorLast(t *testing.T) { + list := New[string]() + it := list.Iterator() + if actualValue, expectedValue := it.Last(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + list.Add("a", "b", "c") + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "c") + } +} - if actualValue := list.Contains("a"); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) +func TestListIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") } - if actualValue, ok := list.Get(0); actualValue != nil || ok { - t.Errorf("Got %v expected %v", actualValue, false) + // NextTo (empty) + { + list := New[string]() + it := list.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } } - if actualValue := list.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) + // NextTo (not found) + { + list := New[string]() + list.Add("xx", "yy") + it := list.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // NextTo (found) + { + list := New[string]() + list.Add("aa", "bb", "cc") + it := list.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestListIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + list := New[string]() + it := list.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } } + // PrevTo (not found) + { + list := New[string]() + list.Add("xx", "yy") + it := list.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // PrevTo (found) + { + list := New[string]() + list.Add("aa", "bb", "cc") + it := list.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestListSerialization(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + + var err error + assert := func() { + if actualValue, expectedValue := list.Values(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := list.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := list.ToJSON() + assert() + + err = list.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]any{"a", "b", "c", list}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &list) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestListString(t *testing.T) { + c := New[int]() + c.Add(1) + if !strings.HasPrefix(c.String(), "DoublyLinkedList") { + t.Errorf("String should start with container name") + } } -func BenchmarkDoublyLinkedList(b *testing.B) { +func benchmarkGet(b *testing.B, list *List[int], size int) { for i := 0; i < b.N; i++ { - list := New() - for n := 0; n < 1000; n++ { - list.Add(i) + for n := 0; n < size; n++ { + list.Get(n) } - for !list.Empty() { - list.Remove(0) + } +} + +func benchmarkAdd(b *testing.B, list *List[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + list.Add(n) + } + } +} + +func benchmarkRemove(b *testing.B, list *List[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + list.Remove(n) } } } + +func BenchmarkDoublyLinkedListGet100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkDoublyLinkedListGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkDoublyLinkedListGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkDoublyLinkedListGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkDoublyLinkedListAdd100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkDoublyLinkedListAdd1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkDoublyLinkedListAdd10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkDoublyLinkedListAdd100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkDoublyLinkedListRemove100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkDoublyLinkedListRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkDoublyLinkedListRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkDoublyLinkedListRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} diff --git a/lists/doublylinkedlist/enumerable.go b/lists/doublylinkedlist/enumerable.go new file mode 100644 index 00000000..4e6e7784 --- /dev/null +++ b/lists/doublylinkedlist/enumerable.go @@ -0,0 +1,79 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package doublylinkedlist + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Enumerable implementation +var _ containers.EnumerableWithIndex[int] = (*List[int])(nil) + +// Each calls the given function once for each element, passing that element's index and value. +func (list *List[T]) Each(f func(index int, value T)) { + iterator := list.Iterator() + for iterator.Next() { + f(iterator.Index(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a +// container containing the values returned by the given function. +func (list *List[T]) Map(f func(index int, value T) T) *List[T] { + newList := &List[T]{} + iterator := list.Iterator() + for iterator.Next() { + newList.Add(f(iterator.Index(), iterator.Value())) + } + return newList +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (list *List[T]) Select(f func(index int, value T) bool) *List[T] { + newList := &List[T]{} + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + newList.Add(iterator.Value()) + } + } + return newList +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (list *List[T]) Any(f func(index int, value T) bool) bool { + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (list *List[T]) All(f func(index int, value T) bool) bool { + iterator := list.Iterator() + for iterator.Next() { + if !f(iterator.Index(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (index,value) for which the function is true or -1,nil otherwise +// if no element matches the criteria. +func (list *List[T]) Find(f func(index int, value T) bool) (index int, value T) { + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return iterator.Index(), iterator.Value() + } + } + var t T + return -1, t +} diff --git a/lists/doublylinkedlist/iterator.go b/lists/doublylinkedlist/iterator.go new file mode 100644 index 00000000..1b637073 --- /dev/null +++ b/lists/doublylinkedlist/iterator.go @@ -0,0 +1,131 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package doublylinkedlist + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator holding the iterator's state +type Iterator[T comparable] struct { + list *List[T] + index int + element *element[T] +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (list *List[T]) Iterator() Iterator[T] { + return Iterator[T]{list: list, index: -1, element: nil} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.list.size { + iterator.index++ + } + if !iterator.list.withinRange(iterator.index) { + iterator.element = nil + return false + } + if iterator.index != 0 { + iterator.element = iterator.element.next + } else { + iterator.element = iterator.list.first + } + return true +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + if iterator.index >= 0 { + iterator.index-- + } + if !iterator.list.withinRange(iterator.index) { + iterator.element = nil + return false + } + if iterator.index == iterator.list.size-1 { + iterator.element = iterator.list.last + } else { + iterator.element = iterator.element.prev + } + return iterator.list.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + return iterator.element.value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 + iterator.element = nil +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.index = iterator.list.size + iterator.element = iterator.list.last +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/lists/doublylinkedlist/serialization.go b/lists/doublylinkedlist/serialization.go new file mode 100644 index 00000000..38d95bf2 --- /dev/null +++ b/lists/doublylinkedlist/serialization.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package doublylinkedlist + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*List[int])(nil) +var _ containers.JSONDeserializer = (*List[int])(nil) + +// ToJSON outputs the JSON representation of list's elements. +func (list *List[T]) ToJSON() ([]byte, error) { + return json.Marshal(list.Values()) +} + +// FromJSON populates list's elements from the input JSON representation. +func (list *List[T]) FromJSON(data []byte) error { + var elements []T + err := json.Unmarshal(data, &elements) + if err == nil { + list.Clear() + list.Add(elements...) + } + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (list *List[T]) UnmarshalJSON(bytes []byte) error { + return list.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (list *List[T]) MarshalJSON() ([]byte, error) { + return list.ToJSON() +} diff --git a/lists/lists.go b/lists/lists.go index 7e03285f..cc310366 100644 --- a/lists/lists.go +++ b/lists/lists.go @@ -1,39 +1,34 @@ -/* -Copyright (c) Emir Pasic, All rights reserved. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library. See the file LICENSE included -with this distribution for more information. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// Package lists provides an abstract List interface. +// +// In computer science, a list or sequence is an abstract data type that represents an ordered sequence of values, where the same value may occur more than once. An instance of a list is a computer representation of the mathematical concept of a finite sequence; the (potentially) infinite analog of a list is a stream. Lists are a basic example of containers, as they contain other values. If the same value occurs multiple times, each occurrence is considered a distinct item. +// +// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29 package lists import ( - "github.com/emirpasic/gods/containers" - "github.com/emirpasic/gods/utils" + "github.com/emirpasic/gods/v2/containers" + "github.com/emirpasic/gods/v2/utils" ) -type Interface interface { - Get(index int) (interface{}, bool) +// List interface that all lists implement +type List[T comparable] interface { + Get(index int) (T, bool) Remove(index int) - Add(elements ...interface{}) - Contains(elements ...interface{}) bool - Sort(comparator utils.Comparator) + Add(values ...T) + Contains(values ...T) bool + Sort(comparator utils.Comparator[T]) Swap(index1, index2 int) + Insert(index int, values ...T) + Set(index int, value T) - containers.Interface + containers.Container[T] // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string } diff --git a/lists/singlylinkedlist/enumerable.go b/lists/singlylinkedlist/enumerable.go new file mode 100644 index 00000000..febb90d4 --- /dev/null +++ b/lists/singlylinkedlist/enumerable.go @@ -0,0 +1,78 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package singlylinkedlist + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Enumerable implementation +var _ containers.EnumerableWithIndex[int] = (*List[int])(nil) + +// Each calls the given function once for each element, passing that element's index and value. +func (list *List[T]) Each(f func(index int, value T)) { + iterator := list.Iterator() + for iterator.Next() { + f(iterator.Index(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a +// container containing the values returned by the given function. +func (list *List[T]) Map(f func(index int, value T) T) *List[T] { + newList := &List[T]{} + iterator := list.Iterator() + for iterator.Next() { + newList.Add(f(iterator.Index(), iterator.Value())) + } + return newList +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (list *List[T]) Select(f func(index int, value T) bool) *List[T] { + newList := &List[T]{} + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + newList.Add(iterator.Value()) + } + } + return newList +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (list *List[T]) Any(f func(index int, value T) bool) bool { + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (list *List[T]) All(f func(index int, value T) bool) bool { + iterator := list.Iterator() + for iterator.Next() { + if !f(iterator.Index(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (index,value) for which the function is true or -1,nil otherwise +// if no element matches the criteria. +func (list *List[T]) Find(f func(index int, value T) bool) (index int, value T) { + iterator := list.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return iterator.Index(), iterator.Value() + } + } + return -1, value +} diff --git a/lists/singlylinkedlist/iterator.go b/lists/singlylinkedlist/iterator.go new file mode 100644 index 00000000..dc3304a8 --- /dev/null +++ b/lists/singlylinkedlist/iterator.go @@ -0,0 +1,83 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package singlylinkedlist + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.IteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator holding the iterator's state +type Iterator[T comparable] struct { + list *List[T] + index int + element *element[T] +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (list *List[T]) Iterator() *Iterator[T] { + return &Iterator[T]{list: list, index: -1, element: nil} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.list.size { + iterator.index++ + } + if !iterator.list.withinRange(iterator.index) { + iterator.element = nil + return false + } + if iterator.index == 0 { + iterator.element = iterator.list.first + } else { + iterator.element = iterator.element.next + } + return true +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + return iterator.element.value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 + iterator.element = nil +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/lists/singlylinkedlist/serialization.go b/lists/singlylinkedlist/serialization.go new file mode 100644 index 00000000..3cc49652 --- /dev/null +++ b/lists/singlylinkedlist/serialization.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package singlylinkedlist + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*List[int])(nil) +var _ containers.JSONDeserializer = (*List[int])(nil) + +// ToJSON outputs the JSON representation of list's elements. +func (list *List[T]) ToJSON() ([]byte, error) { + return json.Marshal(list.Values()) +} + +// FromJSON populates list's elements from the input JSON representation. +func (list *List[T]) FromJSON(data []byte) error { + var elements []T + err := json.Unmarshal(data, &elements) + if err == nil { + list.Clear() + list.Add(elements...) + } + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (list *List[T]) UnmarshalJSON(bytes []byte) error { + return list.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (list *List[T]) MarshalJSON() ([]byte, error) { + return list.ToJSON() +} diff --git a/lists/singlylinkedlist/singlylinkedlist.go b/lists/singlylinkedlist/singlylinkedlist.go index a9b4d636..a4126021 100644 --- a/lists/singlylinkedlist/singlylinkedlist.go +++ b/lists/singlylinkedlist/singlylinkedlist.go @@ -1,66 +1,51 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of doubly linked list. -// Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/Linked_list#Singly_linked_list +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// Package singlylinkedlist implements the singly-linked list. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29 package singlylinkedlist import ( "fmt" - "github.com/emirpasic/gods/lists" - "github.com/emirpasic/gods/utils" + "slices" "strings" + + "github.com/emirpasic/gods/v2/lists" + "github.com/emirpasic/gods/v2/utils" ) -func assertInterfaceImplementation() { - var _ lists.Interface = (*List)(nil) -} +// Assert List implementation +var _ lists.List[int] = (*List[int])(nil) -type List struct { - first *element - last *element +// List holds the elements, where each element points to the next element +type List[T comparable] struct { + first *element[T] + last *element[T] size int } -type element struct { - value interface{} - next *element +type element[T comparable] struct { + value T + next *element[T] } -// Instantiates a new empty list -func New() *List { - return &List{} +// New instantiates a new list and adds the passed values, if any, to the list +func New[T comparable](values ...T) *List[T] { + list := &List[T]{} + if len(values) > 0 { + list.Add(values...) + } + return list } -// Appends a value (one or more) at the end of the list (same as Append()) -func (list *List) Add(values ...interface{}) { +// Add appends a value (one or more) at the end of the list (same as Append()) +func (list *List[T]) Add(values ...T) { for _, value := range values { - newElement := &element{value: value} + newElement := &element[T]{value: value} if list.size == 0 { list.first = newElement list.last = newElement @@ -72,16 +57,16 @@ func (list *List) Add(values ...interface{}) { } } -// Appends a value (one or more) at the end of the list (same as Add()) -func (list *List) Append(values ...interface{}) { +// Append appends a value (one or more) at the end of the list (same as Add()) +func (list *List[T]) Append(values ...T) { list.Add(values...) } -// Prepends a values (or more) -func (list *List) Prepend(values ...interface{}) { +// Prepend prepends a values (or more) +func (list *List[T]) Prepend(values ...T) { // in reverse to keep passed order i.e. ["c","d"] -> Prepend(["a","b"]) -> ["a","b","c",d"] for v := len(values) - 1; v >= 0; v-- { - newElement := &element{value: values[v], next: list.first} + newElement := &element[T]{value: values[v], next: list.first} list.first = newElement if list.size == 0 { list.last = newElement @@ -90,12 +75,13 @@ func (list *List) Prepend(values ...interface{}) { } } -// Returns the element at index. +// Get returns the element at index. // Second return parameter is true if index is within bounds of the array and array is not empty, otherwise false. -func (list *List) Get(index int) (interface{}, bool) { +func (list *List[T]) Get(index int) (T, bool) { if !list.withinRange(index) { - return nil, false + var t T + return t, false } element := list.first @@ -105,8 +91,8 @@ func (list *List) Get(index int) (interface{}, bool) { return element.value, true } -// Removes one or more elements from the list with the supplied indices. -func (list *List) Remove(index int) { +// Remove removes the element at the given index from the list. +func (list *List[T]) Remove(index int) { if !list.withinRange(index) { return @@ -117,7 +103,7 @@ func (list *List) Remove(index int) { return } - var beforeElement *element + var beforeElement *element[T] element := list.first for e := 0; e != index; e, element = e+1, element.next { beforeElement = element @@ -138,11 +124,11 @@ func (list *List) Remove(index int) { list.size-- } -// Check if values (one or more) are present in the set. +// Contains checks if values (one or more) are present in the set. // All values have to be present in the set for the method to return true. // Performance time complexity of n^2. // Returns true if no arguments are passed at all, i.e. set is always super-set of empty set. -func (list *List) Contains(values ...interface{}) bool { +func (list *List[T]) Contains(values ...T) bool { if len(values) == 0 { return true @@ -165,41 +151,54 @@ func (list *List) Contains(values ...interface{}) bool { return true } -// Returns all elements in the list. -func (list *List) Values() []interface{} { - values := make([]interface{}, list.size, list.size) +// Values returns all elements in the list. +func (list *List[T]) Values() []T { + values := make([]T, list.size, list.size) for e, element := 0, list.first; element != nil; e, element = e+1, element.next { values[e] = element.value } return values } -// Returns true if list does not contain any elements. -func (list *List) Empty() bool { +// IndexOf returns index of provided element +func (list *List[T]) IndexOf(value T) int { + if list.size == 0 { + return -1 + } + for index, element := range list.Values() { + if element == value { + return index + } + } + return -1 +} + +// Empty returns true if list does not contain any elements. +func (list *List[T]) Empty() bool { return list.size == 0 } -// Returns number of elements within the list. -func (list *List) Size() int { +// Size returns number of elements within the list. +func (list *List[T]) Size() int { return list.size } -// Removes all elements from the list. -func (list *List) Clear() { +// Clear removes all elements from the list. +func (list *List[T]) Clear() { list.size = 0 list.first = nil list.last = nil } -// Sorts values (in-place) using timsort. -func (list *List) Sort(comparator utils.Comparator) { +// Sort sort values (in-place) using. +func (list *List[T]) Sort(comparator utils.Comparator[T]) { if list.size < 2 { return } values := list.Values() - utils.Sort(values, comparator) + slices.SortFunc(values, comparator) list.Clear() @@ -207,10 +206,10 @@ func (list *List) Sort(comparator utils.Comparator) { } -// Swaps values of two elements at the given indices. -func (list *List) Swap(i, j int) { +// Swap swaps values of two elements at the given indices. +func (list *List[T]) Swap(i, j int) { if list.withinRange(i) && list.withinRange(j) && i != j { - var element1, element2 *element + var element1, element2 *element[T] for e, currentElement := 0, list.first; element1 == nil || element2 == nil; e, currentElement = e+1, currentElement.next { switch e { case i: @@ -223,7 +222,72 @@ func (list *List) Swap(i, j int) { } } -func (list *List) String() string { +// Insert inserts values at specified index position shifting the value at that position (if any) and any subsequent elements to the right. +// Does not do anything if position is negative or bigger than list's size +// Note: position equal to list's size is valid, i.e. append. +func (list *List[T]) Insert(index int, values ...T) { + + if !list.withinRange(index) { + // Append + if index == list.size { + list.Add(values...) + } + return + } + + list.size += len(values) + + var beforeElement *element[T] + foundElement := list.first + for e := 0; e != index; e, foundElement = e+1, foundElement.next { + beforeElement = foundElement + } + + if foundElement == list.first { + oldNextElement := list.first + for i, value := range values { + newElement := &element[T]{value: value} + if i == 0 { + list.first = newElement + } else { + beforeElement.next = newElement + } + beforeElement = newElement + } + beforeElement.next = oldNextElement + } else { + oldNextElement := beforeElement.next + for _, value := range values { + newElement := &element[T]{value: value} + beforeElement.next = newElement + beforeElement = newElement + } + beforeElement.next = oldNextElement + } +} + +// Set value at specified index +// Does not do anything if position is negative or bigger than list's size +// Note: position equal to list's size is valid, i.e. append. +func (list *List[T]) Set(index int, value T) { + + if !list.withinRange(index) { + // Append + if index == list.size { + list.Add(value) + } + return + } + + foundElement := list.first + for e := 0; e != index; { + e, foundElement = e+1, foundElement.next + } + foundElement.value = value +} + +// String returns a string representation of container +func (list *List[T]) String() string { str := "SinglyLinkedList\n" values := []string{} for element := list.first; element != nil; element = element.next { @@ -233,7 +297,7 @@ func (list *List) String() string { return str } -// Check that the index is withing bounds of the list -func (list *List) withinRange(index int) bool { - return index >= 0 && index < list.size && list.size != 0 +// Check that the index is within bounds of the list +func (list *List[T]) withinRange(index int) bool { + return index >= 0 && index < list.size } diff --git a/lists/singlylinkedlist/singlylinkedlist_test.go b/lists/singlylinkedlist/singlylinkedlist_test.go index 4956507a..6dd1d944 100644 --- a/lists/singlylinkedlist/singlylinkedlist_test.go +++ b/lists/singlylinkedlist/singlylinkedlist_test.go @@ -1,137 +1,694 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package singlylinkedlist import ( - "github.com/emirpasic/gods/utils" + "cmp" + "encoding/json" + "slices" + "strings" "testing" ) -func TestSinglyLinkedList(t *testing.T) { +func TestListNew(t *testing.T) { + list1 := New[int]() - list := New() + if actualValue := list1.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } - list.Sort(utils.StringComparator) + list2 := New[int](1, 2) - list.Add("e", "f", "g", "a", "b", "c", "d") + if actualValue := list2.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + + if actualValue, ok := list2.Get(0); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } + + if actualValue, ok := list2.Get(1); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + + if actualValue, ok := list2.Get(2); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestListAdd(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue := list.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } +} + +func TestListAppendAndPrepend(t *testing.T) { + list := New[string]() + list.Add("b") + list.Prepend("a") + list.Append("c") + if actualValue := list.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := list.Get(0); actualValue != "a" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } + if actualValue, ok := list.Get(1); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } +} + +func TestListRemove(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + list.Remove(2) + if actualValue, ok := list.Get(2); actualValue != "" || ok { + t.Errorf("Got %v expected %v", actualValue, "") + } + list.Remove(1) + list.Remove(0) + list.Remove(0) // no effect + if actualValue := list.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestListGet(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue, ok := list.Get(0); actualValue != "a" || !ok { + t.Errorf("Got %v expected %v", actualValue, "a") + } + if actualValue, ok := list.Get(1); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } + if actualValue, ok := list.Get(2); actualValue != "c" || !ok { + t.Errorf("Got %v expected %v", actualValue, "c") + } + if actualValue, ok := list.Get(3); actualValue != "" || ok { + t.Errorf("Got %v expected %v", actualValue, "") + } + list.Remove(0) + if actualValue, ok := list.Get(0); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } +} + +func TestListSwap(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + list.Swap(0, 1) + if actualValue, ok := list.Get(0); actualValue != "b" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } +} - list.Sort(utils.StringComparator) +func TestListSort(t *testing.T) { + list := New[string]() + list.Sort(cmp.Compare[string]) + list.Add("e", "f", "g", "a", "b", "c", "d") + list.Sort(cmp.Compare[string]) for i := 1; i < list.Size(); i++ { a, _ := list.Get(i - 1) b, _ := list.Get(i) - if a.(string) > b.(string) { + if a > b { t.Errorf("Not sorted! %s > %s", a, b) } } +} +func TestListClear(t *testing.T) { + list := New[string]() + list.Add("e", "f", "g", "a", "b", "c", "d") list.Clear() - if actualValue := list.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := list.Size(); actualValue != 0 { t.Errorf("Got %v expected %v", actualValue, 0) } +} +func TestListContains(t *testing.T) { + list := New[string]() list.Add("a") list.Add("b", "c") - - if actualValue := list.Empty(); actualValue != false { + if actualValue := list.Contains("a"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Contains(""); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Contains("a", "b", "c"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := list.Contains("a", "b", "c", "d"); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + list.Clear() + if actualValue := list.Contains("a"); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := list.Contains("a", "b", "c"); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } +} + +func TestListValues(t *testing.T) { + list := New[string]() + list.Add("a") + list.Add("b", "c") + if actualValue, expectedValue := list.Values(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestListIndexOf(t *testing.T) { + list := New[string]() + + expectedIndex := -1 + if index := list.IndexOf("a"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } + + list.Add("a") + list.Add("b", "c") + + expectedIndex = 0 + if index := list.IndexOf("a"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } + + expectedIndex = 1 + if index := list.IndexOf("b"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } + expectedIndex = 2 + if index := list.IndexOf("c"); index != expectedIndex { + t.Errorf("Got %v expected %v", index, expectedIndex) + } +} + +func TestListInsert(t *testing.T) { + list := New[string]() + list.Insert(0, "b", "c") + list.Insert(0, "a") + list.Insert(10, "x") // ignore if actualValue := list.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } + list.Insert(3, "d") // append + if actualValue := list.Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + if actualValue, expectedValue := strings.Join(list.Values(), ""), "abcd"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} - if actualValue, ok := list.Get(2); actualValue != "c" || !ok { - t.Errorf("Got %v expected %v", actualValue, "c") +func TestListSet(t *testing.T) { + list := New[string]() + list.Set(0, "a") + list.Set(1, "b") + if actualValue := list.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + list.Set(2, "c") // append + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + list.Set(4, "d") // ignore + list.Set(1, "bb") // update + if actualValue := list.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, expectedValue := list.Values(), []string{"a", "bb", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestListEach(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + list.Each(func(index int, value string) { + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} + +func TestListMap(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + mappedList := list.Map(func(index int, value string) string { + return "mapped: " + value + }) + if actualValue, _ := mappedList.Get(0); actualValue != "mapped: a" { + t.Errorf("Got %v expected %v", actualValue, "mapped: a") + } + if actualValue, _ := mappedList.Get(1); actualValue != "mapped: b" { + t.Errorf("Got %v expected %v", actualValue, "mapped: b") } + if actualValue, _ := mappedList.Get(2); actualValue != "mapped: c" { + t.Errorf("Got %v expected %v", actualValue, "mapped: c") + } + if mappedList.Size() != 3 { + t.Errorf("Got %v expected %v", mappedList.Size(), 3) + } +} - list.Swap(0, 2) - list.Swap(0, 2) - list.Swap(0, 1) +func TestListSelect(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + selectedList := list.Select(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if actualValue, _ := selectedList.Get(0); actualValue != "a" { + t.Errorf("Got %v expected %v", actualValue, "value: a") + } + if actualValue, _ := selectedList.Get(1); actualValue != "b" { + t.Errorf("Got %v expected %v", actualValue, "value: b") + } + if selectedList.Size() != 2 { + t.Errorf("Got %v expected %v", selectedList.Size(), 3) + } +} - if actualValue, ok := list.Get(0); actualValue != "b" || !ok { +func TestListAny(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + any := list.Any(func(index int, value string) bool { + return value == "c" + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = list.Any(func(index int, value string) bool { + return value == "x" + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} +func TestListAll(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + all := list.All(func(index int, value string) bool { + return value >= "a" && value <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = list.All(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} +func TestListFind(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + foundIndex, foundValue := list.Find(func(index int, value string) bool { + return value == "c" + }) + if foundValue != "c" || foundIndex != 2 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, "c", 2) + } + foundIndex, foundValue = list.Find(func(index int, value string) bool { + return value == "x" + }) + if foundValue != "" || foundIndex != -1 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, nil, nil) + } +} +func TestListChaining(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + chainedList := list.Select(func(index int, value string) bool { + return value > "a" + }).Map(func(index int, value string) string { + return value + value + }) + if chainedList.Size() != 2 { + t.Errorf("Got %v expected %v", chainedList.Size(), 2) + } + if actualValue, ok := chainedList.Get(0); actualValue != "bb" || !ok { + t.Errorf("Got %v expected %v", actualValue, "b") + } + if actualValue, ok := chainedList.Get(1); actualValue != "cc" || !ok { t.Errorf("Got %v expected %v", actualValue, "c") } +} - list.Remove(2) +func TestListIteratorNextOnEmpty(t *testing.T) { + list := New[string]() + it := list.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty list") + } +} - if actualValue, ok := list.Get(2); actualValue != nil || ok { - t.Errorf("Got %v expected %v", actualValue, nil) +func TestListIteratorNext(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") + it := list.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } +} - list.Remove(1) - list.Remove(0) +func TestListIteratorBegin(t *testing.T) { + list := New[string]() + it := list.Iterator() + it.Begin() + list.Add("a", "b", "c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} - if actualValue := list.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestListIteratorFirst(t *testing.T) { + list := New[string]() + it := list.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + list.Add("a", "b", "c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") } +} - if actualValue := list.Size(); actualValue != 0 { - t.Errorf("Got %v expected %v", actualValue, 0) +func TestListIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") } - list.Add("a", "b", "c") + // NextTo (empty) + { + list := New[string]() + it := list.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } - if actualValue := list.Contains("a", "b", "c"); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) + // NextTo (not found) + { + list := New[string]() + list.Add("xx", "yy") + it := list.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } } - if actualValue := list.Contains("a", "b", "c", "d"); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) + // NextTo (found) + { + list := New[string]() + list.Add("aa", "bb", "cc") + it := list.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } } +} - list.Clear() +func TestListSerialization(t *testing.T) { + list := New[string]() + list.Add("a", "b", "c") - if actualValue := list.Contains("a"); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) + var err error + assert := func() { + if actualValue, expectedValue := list.Values(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := list.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } } - if actualValue, ok := list.Get(0); actualValue != nil || ok { - t.Errorf("Got %v expected %v", actualValue, false) + assert() + + bytes, err := list.ToJSON() + assert() + + err = list.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]any{"a", "b", "c", list}) + if err != nil { + t.Errorf("Got error %v", err) } - if actualValue := list.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) + err = json.Unmarshal([]byte(`["a","b","c"]`), &list) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestListString(t *testing.T) { + c := New[int]() + c.Add(1) + if !strings.HasPrefix(c.String(), "SinglyLinkedList") { + t.Errorf("String should start with container name") } +} +func benchmarkGet(b *testing.B, list *List[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + list.Get(n) + } + } } -func BenchmarkSinglyLinkedList(b *testing.B) { +func benchmarkAdd(b *testing.B, list *List[int], size int) { for i := 0; i < b.N; i++ { - list := New() - for n := 0; n < 1000; n++ { - list.Add(i) + for n := 0; n < size; n++ { + list.Add(n) } - for !list.Empty() { - list.Remove(0) + } +} + +func benchmarkRemove(b *testing.B, list *List[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + list.Remove(n) } } } + +func BenchmarkSinglyLinkedListGet100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkSinglyLinkedListGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkSinglyLinkedListGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkSinglyLinkedListGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkGet(b, list, size) +} + +func BenchmarkSinglyLinkedListAdd100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkSinglyLinkedListAdd1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkSinglyLinkedListAdd10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkSinglyLinkedListAdd100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkAdd(b, list, size) +} + +func BenchmarkSinglyLinkedListRemove100(b *testing.B) { + b.StopTimer() + size := 100 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkSinglyLinkedListRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkSinglyLinkedListRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} + +func BenchmarkSinglyLinkedListRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + list := New[int]() + for n := 0; n < size; n++ { + list.Add(n) + } + b.StartTimer() + benchmarkRemove(b, list, size) +} diff --git a/maps/hashbidimap/hashbidimap.go b/maps/hashbidimap/hashbidimap.go new file mode 100644 index 00000000..cf947a82 --- /dev/null +++ b/maps/hashbidimap/hashbidimap.go @@ -0,0 +1,102 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package hashbidimap implements a bidirectional map backed by two hashmaps. +// +// A bidirectional map, or hash bag, is an associative data structure in which the (key,value) pairs form a one-to-one correspondence. +// Thus the binary relation is functional in each direction: value can also act as a key to key. +// A pair (a,b) thus provides a unique coupling between 'a' and 'b' so that 'b' can be found when 'a' is used as a key and 'a' can be found when 'b' is used as a key. +// +// Elements are unordered in the map. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/Bidirectional_map +package hashbidimap + +import ( + "fmt" + + "github.com/emirpasic/gods/v2/maps" + "github.com/emirpasic/gods/v2/maps/hashmap" +) + +// Assert Map implementation +var _ maps.BidiMap[string, int] = (*Map[string, int])(nil) + +// Map holds the elements in two hashmaps. +type Map[K, V comparable] struct { + forwardMap hashmap.Map[K, V] + inverseMap hashmap.Map[V, K] +} + +// New instantiates a bidirectional map. +func New[K, V comparable]() *Map[K, V] { + return &Map[K, V]{*hashmap.New[K, V](), *hashmap.New[V, K]()} +} + +// Put inserts element into the map. +func (m *Map[K, V]) Put(key K, value V) { + if valueByKey, ok := m.forwardMap.Get(key); ok { + m.inverseMap.Remove(valueByKey) + } + if keyByValue, ok := m.inverseMap.Get(value); ok { + m.forwardMap.Remove(keyByValue) + } + m.forwardMap.Put(key, value) + m.inverseMap.Put(value, key) +} + +// Get searches the element in the map by key and returns its value or nil if key is not found in map. +// Second return parameter is true if key was found, otherwise false. +func (m *Map[K, V]) Get(key K) (value V, found bool) { + return m.forwardMap.Get(key) +} + +// GetKey searches the element in the map by value and returns its key or nil if value is not found in map. +// Second return parameter is true if value was found, otherwise false. +func (m *Map[K, V]) GetKey(value V) (key K, found bool) { + return m.inverseMap.Get(value) +} + +// Remove removes the element from the map by key. +func (m *Map[K, V]) Remove(key K) { + if value, found := m.forwardMap.Get(key); found { + m.forwardMap.Remove(key) + m.inverseMap.Remove(value) + } +} + +// Empty returns true if map does not contain any elements +func (m *Map[K, V]) Empty() bool { + return m.Size() == 0 +} + +// Size returns number of elements in the map. +func (m *Map[K, V]) Size() int { + return m.forwardMap.Size() +} + +// Keys returns all keys (random order). +func (m *Map[K, V]) Keys() []K { + return m.forwardMap.Keys() +} + +// Values returns all values (random order). +func (m *Map[K, V]) Values() []V { + return m.inverseMap.Keys() +} + +// Clear removes all elements from the map. +func (m *Map[K, V]) Clear() { + m.forwardMap.Clear() + m.inverseMap.Clear() +} + +// String returns a string representation of container +func (m *Map[K, V]) String() string { + str := "HashBidiMap\n" + str += fmt.Sprintf("%v", m.forwardMap) + return str +} diff --git a/maps/hashbidimap/hashbidimap_test.go b/maps/hashbidimap/hashbidimap_test.go new file mode 100644 index 00000000..64fae28f --- /dev/null +++ b/maps/hashbidimap/hashbidimap_test.go @@ -0,0 +1,340 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hashbidimap + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/emirpasic/gods/v2/testutils" +) + +func TestMapPut(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + if actualValue := m.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4, 5, 6, 7}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d", "e", "f", "g"}) + + // key,expectedValue,expectedFound + tests1 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {8, "", false}, + } + + for _, test := range tests1 { + // retrievals + actualValue, actualFound := m.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } +} + +func TestMapRemove(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + m.Remove(5) + m.Remove(6) + m.Remove(7) + m.Remove(8) + m.Remove(5) + + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d"}) + + if actualValue := m.Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + + tests2 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "", false}, + {6, "", false}, + {7, "", false}, + {8, "", false}, + } + + for _, test := range tests2 { + actualValue, actualFound := m.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } + + m.Remove(1) + m.Remove(4) + m.Remove(2) + m.Remove(3) + m.Remove(2) + m.Remove(2) + + testutils.SameElements(t, m.Keys(), nil) + testutils.SameElements(t, m.Values(), nil) + if actualValue := m.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + if actualValue := m.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestMapGetKey(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + // key,expectedValue,expectedFound + tests1 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {0, "x", false}, + } + + for _, test := range tests1 { + // retrievals + actualValue, actualFound := m.GetKey(test[1].(string)) + if actualValue != test[0] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[0]) + } + } +} + +func TestMapSerialization(t *testing.T) { + m := New[string, float64]() + m.Put("a", 1.0) + m.Put("b", 2.0) + m.Put("c", 3.0) + + var err error + assert := func() { + testutils.SameElements(t, m.Keys(), []string{"a", "b", "c"}) + testutils.SameElements(t, m.Values(), []float64{1.0, 2.0, 3.0}) + if actualValue, expectedValue := m.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := m.ToJSON() + assert() + + err = m.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", m}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), &m) + if err != nil { + t.Errorf("Got error %v", err) + } +} + +func TestMapString(t *testing.T) { + c := New[string, int]() + c.Put("a", 1) + if !strings.HasPrefix(c.String(), "HashBidiMap") { + t.Errorf("String should start with container name") + } +} + +func benchmarkGet(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Get(n) + } + } +} + +func benchmarkPut(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Put(n, n) + } + } +} + +func benchmarkRemove(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Remove(n) + } + } +} + +func BenchmarkHashBidiMapGet100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashBidiMapGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashBidiMapGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashBidiMapGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashBidiMapPut100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashBidiMapPut1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashBidiMapPut10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashBidiMapPut100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashBidiMapRemove100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkHashBidiMapRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkHashBidiMapRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkHashBidiMapRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} diff --git a/maps/hashbidimap/serialization.go b/maps/hashbidimap/serialization.go new file mode 100644 index 00000000..9decc35a --- /dev/null +++ b/maps/hashbidimap/serialization.go @@ -0,0 +1,46 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hashbidimap + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Map[string, int])(nil) +var _ containers.JSONDeserializer = (*Map[string, int])(nil) + +// ToJSON outputs the JSON representation of the map. +func (m *Map[K, V]) ToJSON() ([]byte, error) { + return m.forwardMap.ToJSON() +} + +// FromJSON populates the map from the input JSON representation. +func (m *Map[K, V]) FromJSON(data []byte) error { + var elements map[K]V + err := json.Unmarshal(data, &elements) + if err != nil { + return err + } + + m.Clear() + for k, v := range elements { + m.Put(k, v) + } + + return nil +} + +// UnmarshalJSON @implements json.Unmarshaler +func (m *Map[K, V]) UnmarshalJSON(bytes []byte) error { + return m.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (m *Map[K, V]) MarshalJSON() ([]byte, error) { + return m.ToJSON() +} diff --git a/maps/hashmap/hashmap.go b/maps/hashmap/hashmap.go index 247cc016..e2bbbf52 100644 --- a/maps/hashmap/hashmap.go +++ b/maps/hashmap/hashmap.go @@ -1,109 +1,91 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of unorder map backed by a hash table. +// Package hashmap implements a map backed by a hash table. +// // Elements are unordered in the map. +// // Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/Associative_array - +// +// Reference: http://en.wikipedia.org/wiki/Associative_array package hashmap import ( "fmt" - "github.com/emirpasic/gods/maps" + + "github.com/emirpasic/gods/v2/maps" ) -func assertInterfaceImplementation() { - var _ maps.Interface = (*Map)(nil) -} +// Assert Map implementation +var _ maps.Map[string, int] = (*Map[string, int])(nil) -type Map struct { - m map[interface{}]interface{} +// Map holds the elements in go's native map +type Map[K comparable, V any] struct { + m map[K]V } -// Instantiates a hash map. -func New() *Map { - return &Map{m: make(map[interface{}]interface{})} +// New instantiates a hash map. +func New[K comparable, V any]() *Map[K, V] { + return &Map[K, V]{m: make(map[K]V)} } -// Inserts element into the map. -func (m *Map) Put(key interface{}, value interface{}) { +// Put inserts element into the map. +func (m *Map[K, V]) Put(key K, value V) { m.m[key] = value } -// Searches the elemnt in the map by key and returns its value or nil if key is not found in map. +// Get searches the element in the map by key and returns its value or nil if key is not found in map. // Second return parameter is true if key was found, otherwise false. -func (m *Map) Get(key interface{}) (value interface{}, found bool) { +func (m *Map[K, V]) Get(key K) (value V, found bool) { value, found = m.m[key] return } -// Remove the element from the map by key. -func (m *Map) Remove(key interface{}) { +// Remove removes the element from the map by key. +func (m *Map[K, V]) Remove(key K) { delete(m.m, key) } -// Returns true if map does not contain any elements -func (m *Map) Empty() bool { +// Empty returns true if map does not contain any elements +func (m *Map[K, V]) Empty() bool { return m.Size() == 0 } -// Returns number of elements in the map. -func (m *Map) Size() int { +// Size returns number of elements in the map. +func (m *Map[K, V]) Size() int { return len(m.m) } -// Returns all keys (random order). -func (m *Map) Keys() []interface{} { - keys := make([]interface{}, m.Size()) +// Keys returns all keys (random order). +func (m *Map[K, V]) Keys() []K { + keys := make([]K, m.Size()) count := 0 - for key, _ := range m.m { + for key := range m.m { keys[count] = key - count += 1 + count++ } return keys } -// Returns all values (random order). -func (m *Map) Values() []interface{} { - values := make([]interface{}, m.Size()) +// Values returns all values (random order). +func (m *Map[K, V]) Values() []V { + values := make([]V, m.Size()) count := 0 for _, value := range m.m { values[count] = value - count += 1 + count++ } return values } -// Removes all elements from the map. -func (m *Map) Clear() { - m.m = make(map[interface{}]interface{}) +// Clear removes all elements from the map. +func (m *Map[K, V]) Clear() { + clear(m.m) } -func (m *Map) String() string { +// String returns a string representation of container +func (m *Map[K, V]) String() string { str := "HashMap\n" str += fmt.Sprintf("%v", m.m) return str diff --git a/maps/hashmap/hashmap_test.go b/maps/hashmap/hashmap_test.go index 31c026b4..5e650682 100644 --- a/maps/hashmap/hashmap_test.go +++ b/maps/hashmap/hashmap_test.go @@ -1,41 +1,19 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package hashmap import ( - "fmt" + "encoding/json" + "strings" "testing" -) - -func TestHashMap(t *testing.T) { - m := New() + "github.com/emirpasic/gods/v2/testutils" +) - // insertions +func TestMapPut(t *testing.T) { + m := New[int, string]() m.Put(5, "e") m.Put(6, "f") m.Put(7, "g") @@ -45,20 +23,11 @@ func TestHashMap(t *testing.T) { m.Put(2, "b") m.Put(1, "a") //overwrite - // Test Size() if actualValue := m.Size(); actualValue != 7 { t.Errorf("Got %v expected %v", actualValue, 7) } - - // test Keys() - if actualValue, expactedValue := m.Keys(), []interface{}{1, 2, 3, 4, 5, 6, 7}; !sameElements(actualValue, expactedValue) { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } - - // test Values() - if actualValue, expactedValue := m.Values(), []interface{}{"a", "b", "c", "d", "e", "f", "g"}; !sameElements(actualValue, expactedValue) { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4, 5, 6, 7}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d", "e", "f", "g"}) // key,expectedValue,expectedFound tests1 := [][]interface{}{ @@ -69,36 +38,40 @@ func TestHashMap(t *testing.T) { {5, "e", true}, {6, "f", true}, {7, "g", true}, - {8, nil, false}, + {8, "", false}, } for _, test := range tests1 { // retrievals - actualValue, actualFound := m.Get(test[0]) + actualValue, actualFound := m.Get(test[0].(int)) if actualValue != test[1] || actualFound != test[2] { t.Errorf("Got %v expected %v", actualValue, test[1]) } } +} + +func TestMapRemove(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite - // removals m.Remove(5) m.Remove(6) m.Remove(7) m.Remove(8) m.Remove(5) - // test Keys() - if actualValue, expactedValue := m.Keys(), []interface{}{1, 2, 3, 4}; !sameElements(actualValue, expactedValue) { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d"}) - // test Values() - if actualValue, expactedValue := m.Values(), []interface{}{"a", "b", "c", "d"}; !sameElements(actualValue, expactedValue) { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } - // Test Size() if actualValue := m.Size(); actualValue != 4 { - t.Errorf("Got %v expected %v", actualValue, 7) + t.Errorf("Got %v expected %v", actualValue, 4) } tests2 := [][]interface{}{ @@ -106,21 +79,19 @@ func TestHashMap(t *testing.T) { {2, "b", true}, {3, "c", true}, {4, "d", true}, - {5, nil, false}, - {6, nil, false}, - {7, nil, false}, - {8, nil, false}, + {5, "", false}, + {6, "", false}, + {7, "", false}, + {8, "", false}, } for _, test := range tests2 { - // retrievals - actualValue, actualFound := m.Get(test[0]) + actualValue, actualFound := m.Get(test[0].(int)) if actualValue != test[1] || actualFound != test[2] { t.Errorf("Got %v expected %v", actualValue, test[1]) } } - // removals m.Remove(1) m.Remove(4) m.Remove(2) @@ -128,64 +99,210 @@ func TestHashMap(t *testing.T) { m.Remove(2) m.Remove(2) - // Test Keys() - if actualValue, expactedValue := fmt.Sprintf("%s", m.Keys()), "[]"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } - - // test Values() - if actualValue, expactedValue := fmt.Sprintf("%s", m.Values()), "[]"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } - - // Test Size() + testutils.SameElements(t, m.Keys(), nil) + testutils.SameElements(t, m.Values(), nil) if actualValue := m.Size(); actualValue != 0 { t.Errorf("Got %v expected %v", actualValue, 0) } - - // Test Empty() if actualValue := m.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } +} - m.Put(1, "a") - m.Put(2, "b") - m.Clear() +func TestMapSerialization(t *testing.T) { + m := New[string, float64]() + m.Put("a", 1.0) + m.Put("b", 2.0) + m.Put("c", 3.0) - // Test Empty() - if actualValue := m.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) + var err error + assert := func() { + testutils.SameElements(t, m.Keys(), []string{"a", "b", "c"}) + testutils.SameElements(t, m.Values(), []float64{1.0, 2.0, 3.0}) + if actualValue, expectedValue := m.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := m.ToJSON() + assert() + + err = m.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", m}) + if err != nil { + t.Errorf("Got error %v", err) } + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), &m) + if err != nil { + t.Errorf("Got error %v", err) + } } -func sameElements(a []interface{}, b []interface{}) bool { - if len(a) != len(b) { - return false +func TestMapString(t *testing.T) { + c := New[string, int]() + c.Put("a", 1) + if !strings.HasPrefix(c.String(), "HashMap") { + t.Errorf("String should start with container name") } - for _, av := range a { - found := false - for _, bv := range b { - if av == bv { - found = true - break - } - } - if !found { - return false +} + +func benchmarkGet(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Get(n) } } - return true } -func BenchmarkHashMap(b *testing.B) { +func benchmarkPut(b *testing.B, m *Map[int, int], size int) { for i := 0; i < b.N; i++ { - m := New() - for n := 0; n < 1000; n++ { + for n := 0; n < size; n++ { m.Put(n, n) } - for n := 0; n < 1000; n++ { + } +} + +func benchmarkRemove(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { m.Remove(n) } } } + +func BenchmarkHashMapGet100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashMapGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashMapGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashMapGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkHashMapPut100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashMapPut1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashMapPut10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashMapPut100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkHashMapRemove100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkHashMapRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkHashMapRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkHashMapRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} diff --git a/maps/hashmap/serialization.go b/maps/hashmap/serialization.go new file mode 100644 index 00000000..f1624998 --- /dev/null +++ b/maps/hashmap/serialization.go @@ -0,0 +1,35 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hashmap + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Map[string, int])(nil) +var _ containers.JSONDeserializer = (*Map[string, int])(nil) + +// ToJSON outputs the JSON representation of the map. +func (m *Map[K, V]) ToJSON() ([]byte, error) { + return json.Marshal(m.m) +} + +// FromJSON populates the map from the input JSON representation. +func (m *Map[K, V]) FromJSON(data []byte) error { + return json.Unmarshal(data, &m.m) +} + +// UnmarshalJSON @implements json.Unmarshaler +func (m *Map[K, V]) UnmarshalJSON(bytes []byte) error { + return m.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (m *Map[K, V]) MarshalJSON() ([]byte, error) { + return m.ToJSON() +} diff --git a/maps/linkedhashmap/enumerable.go b/maps/linkedhashmap/enumerable.go new file mode 100644 index 00000000..fc496e72 --- /dev/null +++ b/maps/linkedhashmap/enumerable.go @@ -0,0 +1,79 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashmap + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Enumerable implementation +var _ containers.EnumerableWithKey[string, int] = (*Map[string, int])(nil) + +// Each calls the given function once for each element, passing that element's key and value. +func (m *Map[K, V]) Each(f func(key K, value V)) { + iterator := m.Iterator() + for iterator.Next() { + f(iterator.Key(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a container +// containing the values returned by the given function as key/value pairs. +func (m *Map[K, V]) Map(f func(key1 K, value1 V) (K, V)) *Map[K, V] { + newMap := New[K, V]() + iterator := m.Iterator() + for iterator.Next() { + key2, value2 := f(iterator.Key(), iterator.Value()) + newMap.Put(key2, value2) + } + return newMap +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (m *Map[K, V]) Select(f func(key K, value V) bool) *Map[K, V] { + newMap := New[K, V]() + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + newMap.Put(iterator.Key(), iterator.Value()) + } + } + return newMap +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (m *Map[K, V]) Any(f func(key K, value V) bool) bool { + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (m *Map[K, V]) All(f func(key K, value V) bool) bool { + iterator := m.Iterator() + for iterator.Next() { + if !f(iterator.Key(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (key,value) for which the function is true or nil,nil otherwise if no element +// matches the criteria. +func (m *Map[K, V]) Find(f func(key K, value V) bool) (k K, v V) { + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + return iterator.Key(), iterator.Value() + } + } + return k, v +} diff --git a/maps/linkedhashmap/iterator.go b/maps/linkedhashmap/iterator.go new file mode 100644 index 00000000..9ac39318 --- /dev/null +++ b/maps/linkedhashmap/iterator.go @@ -0,0 +1,109 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashmap + +import ( + "github.com/emirpasic/gods/v2/containers" + "github.com/emirpasic/gods/v2/lists/doublylinkedlist" +) + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithKey[string, int] = (*Iterator[string, int])(nil) + +// Iterator holding the iterator's state +type Iterator[K comparable, V any] struct { + iterator doublylinkedlist.Iterator[K] + table map[K]V +} + +// Iterator returns a stateful iterator whose elements are key/value pairs. +func (m *Map[K, V]) Iterator() *Iterator[K, V] { + return &Iterator[K, V]{ + iterator: m.ordering.Iterator(), + table: m.table, + } +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Next() bool { + return iterator.iterator.Next() +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Prev() bool { + return iterator.iterator.Prev() +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Value() V { + key := iterator.iterator.Value() + return iterator.table[key] +} + +// Key returns the current element's key. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Key() K { + return iterator.iterator.Value() +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[K, V]) Begin() { + iterator.iterator.Begin() +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[K, V]) End() { + iterator.iterator.End() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator +func (iterator *Iterator[K, V]) First() bool { + return iterator.iterator.First() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Last() bool { + return iterator.iterator.Last() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) NextTo(f func(key K, value V) bool) bool { + for iterator.Next() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) PrevTo(f func(key K, value V) bool) bool { + for iterator.Prev() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} diff --git a/maps/linkedhashmap/linkedhashmap.go b/maps/linkedhashmap/linkedhashmap.go new file mode 100644 index 00000000..bf5ef4c3 --- /dev/null +++ b/maps/linkedhashmap/linkedhashmap.go @@ -0,0 +1,108 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package linkedhashmap is a map that preserves insertion-order. +// +// It is backed by a hash table to store values and doubly-linked list to store ordering. +// +// Structure is not thread safe. +// +// Reference: http://en.wikipedia.org/wiki/Associative_array +package linkedhashmap + +import ( + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/lists/doublylinkedlist" + "github.com/emirpasic/gods/v2/maps" +) + +// Assert Map implementation +var _ maps.Map[string, int] = (*Map[string, int])(nil) + +// Map holds the elements in a regular hash table, and uses doubly-linked list to store key ordering. +type Map[K comparable, V any] struct { + table map[K]V + ordering *doublylinkedlist.List[K] +} + +// New instantiates a linked-hash-map. +func New[K comparable, V any]() *Map[K, V] { + return &Map[K, V]{ + table: make(map[K]V), + ordering: doublylinkedlist.New[K](), + } +} + +// Put inserts key-value pair into the map. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (m *Map[K, V]) Put(key K, value V) { + if _, contains := m.table[key]; !contains { + m.ordering.Append(key) + } + m.table[key] = value +} + +// Get searches the element in the map by key and returns its value or nil if key is not found in tree. +// Second return parameter is true if key was found, otherwise false. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (m *Map[K, V]) Get(key K) (value V, found bool) { + value, found = m.table[key] + return value, found +} + +// Remove removes the element from the map by key. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (m *Map[K, V]) Remove(key K) { + if _, contains := m.table[key]; contains { + delete(m.table, key) + index := m.ordering.IndexOf(key) + m.ordering.Remove(index) + } +} + +// Empty returns true if map does not contain any elements +func (m *Map[K, V]) Empty() bool { + return m.Size() == 0 +} + +// Size returns number of elements in the map. +func (m *Map[K, V]) Size() int { + return m.ordering.Size() +} + +// Keys returns all keys in-order +func (m *Map[K, V]) Keys() []K { + return m.ordering.Values() +} + +// Values returns all values in-order based on the key. +func (m *Map[K, V]) Values() []V { + values := make([]V, m.Size()) + count := 0 + it := m.Iterator() + for it.Next() { + values[count] = it.Value() + count++ + } + return values +} + +// Clear removes all elements from the map. +func (m *Map[K, V]) Clear() { + clear(m.table) + m.ordering.Clear() +} + +// String returns a string representation of container +func (m *Map[K, V]) String() string { + str := "LinkedHashMap\nmap[" + it := m.Iterator() + for it.Next() { + str += fmt.Sprintf("%v:%v ", it.Key(), it.Value()) + } + return strings.TrimRight(str, " ") + "]" + +} diff --git a/maps/linkedhashmap/linkedhashmap_test.go b/maps/linkedhashmap/linkedhashmap_test.go new file mode 100644 index 00000000..a8d597e8 --- /dev/null +++ b/maps/linkedhashmap/linkedhashmap_test.go @@ -0,0 +1,727 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashmap + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/emirpasic/gods/v2/testutils" +) + +func TestMapPut(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + if actualValue := m.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4, 5, 6, 7}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d", "e", "f", "g"}) + + // key,expectedValue,expectedFound + tests1 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {8, "", false}, + } + + for _, test := range tests1 { + // retrievals + actualValue, actualFound := m.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } +} + +func TestMapRemove(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + m.Remove(5) + m.Remove(6) + m.Remove(7) + m.Remove(8) + m.Remove(5) + + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d"}) + + if actualValue := m.Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + + tests2 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "", false}, + {6, "", false}, + {7, "", false}, + {8, "", false}, + } + + for _, test := range tests2 { + actualValue, actualFound := m.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } + + m.Remove(1) + m.Remove(4) + m.Remove(2) + m.Remove(3) + m.Remove(2) + m.Remove(2) + + testutils.SameElements(t, m.Keys(), nil) + testutils.SameElements(t, m.Values(), nil) + if actualValue := m.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + if actualValue := m.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestMapEach(t *testing.T) { + m := New[string, int]() + m.Put("c", 1) + m.Put("a", 2) + m.Put("b", 3) + count := 0 + m.Each(func(key string, value int) { + count++ + if actualValue, expectedValue := count, value; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + switch value { + case 1: + if actualValue, expectedValue := key, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := key, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 3: + if actualValue, expectedValue := key, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} + +func TestMapMap(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + mappedMap := m.Map(func(key1 string, value1 int) (key2 string, value2 int) { + return key1, value1 * value1 + }) + if actualValue, _ := mappedMap.Get("c"); actualValue != 9 { + t.Errorf("Got %v expected %v", actualValue, "mapped: c") + } + if actualValue, _ := mappedMap.Get("a"); actualValue != 1 { + t.Errorf("Got %v expected %v", actualValue, "mapped: a") + } + if actualValue, _ := mappedMap.Get("b"); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, "mapped: b") + } + if mappedMap.Size() != 3 { + t.Errorf("Got %v expected %v", mappedMap.Size(), 3) + } +} + +func TestMapSelect(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("b", 1) + m.Put("a", 2) + selectedMap := m.Select(func(key string, value int) bool { + return key >= "a" && key <= "b" + }) + if actualValue, _ := selectedMap.Get("b"); actualValue != 1 { + t.Errorf("Got %v expected %v", actualValue, "value: a") + } + if actualValue, _ := selectedMap.Get("a"); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, "value: b") + } + if selectedMap.Size() != 2 { + t.Errorf("Got %v expected %v", selectedMap.Size(), 2) + } +} + +func TestMapAny(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + any := m.Any(func(key string, value int) bool { + return value == 3 + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = m.Any(func(key string, value int) bool { + return value == 4 + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} + +func TestMapAll(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + all := m.All(func(key string, value int) bool { + return key >= "a" && key <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = m.All(func(key string, value int) bool { + return key >= "a" && key <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} + +func TestMapFind(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + foundKey, foundValue := m.Find(func(key string, value int) bool { + return key == "c" + }) + if foundKey != "c" || foundValue != 3 { + t.Errorf("Got %v -> %v expected %v -> %v", foundKey, foundValue, "c", 3) + } + foundKey, foundValue = m.Find(func(key string, value int) bool { + return key == "x" + }) + if foundKey != "" || foundValue != 0 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundKey, "", 0) + } +} + +func TestMapChaining(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + chainedMap := m.Select(func(key string, value int) bool { + return value > 1 + }).Map(func(key string, value int) (string, int) { + return key + key, value * value + }) + if actualValue := chainedMap.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, found := chainedMap.Get("aa"); actualValue != 0 || found { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue, found := chainedMap.Get("bb"); actualValue != 4 || !found { + t.Errorf("Got %v expected %v", actualValue, 4) + } + if actualValue, found := chainedMap.Get("cc"); actualValue != 9 || !found { + t.Errorf("Got %v expected %v", actualValue, 9) + } +} + +func TestMapIteratorNextOnEmpty(t *testing.T) { + m := New[string, int]() + it := m.Iterator() + it = m.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty map") + } +} + +func TestMapIteratorPrevOnEmpty(t *testing.T) { + m := New[string, int]() + it := m.Iterator() + it = m.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty map") + } +} + +func TestMapIteratorNext(t *testing.T) { + m := New[string, int]() + m.Put("c", 1) + m.Put("a", 2) + m.Put("b", 3) + + it := m.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + value := it.Value() + switch key { + case "c": + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "a": + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "b": + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestMapIteratorPrev(t *testing.T) { + m := New[string, int]() + m.Put("c", 1) + m.Put("a", 2) + m.Put("b", 3) + + it := m.Iterator() + for it.Next() { + } + countDown := m.Size() + for it.Prev() { + key := it.Key() + value := it.Value() + switch key { + case "c": + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "a": + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "b": + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := value, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestMapIteratorBegin(t *testing.T) { + m := New[int, string]() + it := m.Iterator() + it.Begin() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + for it.Next() { + } + it.Begin() + it.Next() + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestMapIteratorEnd(t *testing.T) { + m := New[int, string]() + it := m.Iterator() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it.End() + it.Prev() + if key, value := it.Key(), it.Value(); key != 2 || value != "b" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 2, "b") + } +} + +func TestMapIteratorFirst(t *testing.T) { + m := New[int, string]() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it := m.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestMapIteratorLast(t *testing.T) { + m := New[int, string]() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it := m.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 2 || value != "b" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 2, "b") + } +} + +func TestMapIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + m := New[int, string]() + it := m.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // NextTo (not found) + { + m := New[int, string]() + m.Put(0, "xx") + m.Put(1, "yy") + it := m.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // NextTo (found) + { + m := New[int, string]() + m.Put(0, "aa") + m.Put(1, "bb") + m.Put(2, "cc") + it := m.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestMapIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + m := New[int, string]() + it := m.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // PrevTo (not found) + { + m := New[int, string]() + m.Put(0, "xx") + m.Put(1, "yy") + it := m.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // PrevTo (found) + { + m := New[int, string]() + m.Put(0, "aa") + m.Put(1, "bb") + m.Put(2, "cc") + it := m.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestMapSerialization(t *testing.T) { + for i := 0; i < 10; i++ { + original := New[string, string]() + original.Put("d", "4") + original.Put("e", "5") + original.Put("c", "3") + original.Put("b", "2") + original.Put("a", "1") + + serialized, err := original.ToJSON() + if err != nil { + t.Errorf("Got error %v", err) + } + + deserialized := New[string, string]() + err = deserialized.FromJSON(serialized) + if err != nil { + t.Errorf("Got error %v", err) + } + + if original.Size() != deserialized.Size() { + t.Errorf("Got map of size %d, expected %d", original.Size(), deserialized.Size()) + } + original.Each(func(key string, expected string) { + actual, ok := deserialized.Get(key) + if !ok || actual != expected { + t.Errorf("Did not find expected value %v for key %v in deserialied map (got %v)", expected, key, actual) + } + }) + } + + m := New[string, float64]() + m.Put("a", 1.0) + m.Put("b", 2.0) + m.Put("c", 3.0) + + _, err := json.Marshal([]interface{}{"a", "b", "c", m}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), &m) + if err != nil { + t.Errorf("Got error %v", err) + } +} + +func TestMapString(t *testing.T) { + c := New[string, int]() + c.Put("a", 1) + if !strings.HasPrefix(c.String(), "LinkedHashMap") { + t.Errorf("String should start with container name") + } +} + +func benchmarkGet(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Get(n) + } + } +} + +func benchmarkPut(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Put(n, n) + } + } +} + +func benchmarkRemove(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Remove(n) + } + } +} + +func BenchmarkTreeMapGet100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapPut100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapPut1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapPut10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapPut100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapRemove100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeMapRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeMapRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeMapRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} diff --git a/maps/linkedhashmap/serialization.go b/maps/linkedhashmap/serialization.go new file mode 100644 index 00000000..6a83df95 --- /dev/null +++ b/maps/linkedhashmap/serialization.go @@ -0,0 +1,110 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashmap + +import ( + "bytes" + "cmp" + "encoding/json" + "slices" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Map[string, int])(nil) +var _ containers.JSONDeserializer = (*Map[string, int])(nil) + +// ToJSON outputs the JSON representation of map. +func (m *Map[K, V]) ToJSON() ([]byte, error) { + var b []byte + buf := bytes.NewBuffer(b) + + buf.WriteRune('{') + + it := m.Iterator() + lastIndex := m.Size() - 1 + index := 0 + + for it.Next() { + km, err := json.Marshal(it.Key()) + if err != nil { + return nil, err + } + buf.Write(km) + + buf.WriteRune(':') + + vm, err := json.Marshal(it.Value()) + if err != nil { + return nil, err + } + buf.Write(vm) + + if index != lastIndex { + buf.WriteRune(',') + } + + index++ + } + + buf.WriteRune('}') + + return buf.Bytes(), nil +} + +// FromJSON populates map from the input JSON representation. +//func (m *Map[K, V]) FromJSON(data []byte) error { +// elements := make(map[string]interface{}) +// err := json.Unmarshal(data, &elements) +// if err == nil { +// m.Clear() +// for key, value := range elements { +// m.Put(key, value) +// } +// } +// return err +//} + +// FromJSON populates map from the input JSON representation. +func (m *Map[K, V]) FromJSON(data []byte) error { + elements := make(map[K]V) + err := json.Unmarshal(data, &elements) + if err != nil { + return err + } + + index := make(map[K]int) + var keys []K + for key := range elements { + keys = append(keys, key) + esc, _ := json.Marshal(key) + index[key] = bytes.Index(data, esc) + } + + byIndex := func(key1, key2 K) int { + return cmp.Compare(index[key1], index[key2]) + } + + slices.SortFunc(keys, byIndex) + + m.Clear() + + for _, key := range keys { + m.Put(key, elements[key]) + } + + return nil +} + +// UnmarshalJSON @implements json.Unmarshaler +func (m *Map[K, V]) UnmarshalJSON(bytes []byte) error { + return m.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (m *Map[K, V]) MarshalJSON() ([]byte, error) { + return m.ToJSON() +} diff --git a/maps/maps.go b/maps/maps.go index eddcc20e..b54f7dc5 100644 --- a/maps/maps.go +++ b/maps/maps.go @@ -1,42 +1,40 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package maps provides an abstract Map interface. +// +// In computer science, an associative array, map, symbol table, or dictionary is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears just once in the collection. +// +// Operations associated with this data type allow: +// - the addition of a pair to the collection +// - the removal of a pair from the collection +// - the modification of an existing pair +// - the lookup of a value associated with a particular key +// +// Reference: https://en.wikipedia.org/wiki/Associative_array package maps -import "github.com/emirpasic/gods/containers" +import "github.com/emirpasic/gods/v2/containers" -type Interface interface { - Put(key interface{}, value interface{}) - Get(key interface{}) (value interface{}, found bool) - Remove(key interface{}) - Keys() []interface{} +// Map interface that all maps implement +type Map[K comparable, V any] interface { + Put(key K, value V) + Get(key K) (value V, found bool) + Remove(key K) + Keys() []K - containers.Interface + containers.Container[V] // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string +} + +// BidiMap interface that all bidirectional maps implement (extends the Map interface) +type BidiMap[K comparable, V comparable] interface { + GetKey(value V) (key K, found bool) + + Map[K, V] } diff --git a/maps/treebidimap/enumerable.go b/maps/treebidimap/enumerable.go new file mode 100644 index 00000000..febeff46 --- /dev/null +++ b/maps/treebidimap/enumerable.go @@ -0,0 +1,79 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treebidimap + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Enumerable implementation +var _ containers.EnumerableWithKey[string, int] = (*Map[string, int])(nil) + +// Each calls the given function once for each element, passing that element's key and value. +func (m *Map[K, V]) Each(f func(key K, value V)) { + iterator := m.Iterator() + for iterator.Next() { + f(iterator.Key(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a container +// containing the values returned by the given function as key/value pairs. +func (m *Map[K, V]) Map(f func(key1 K, value1 V) (K, V)) *Map[K, V] { + newMap := NewWith[K, V](m.forwardMap.Comparator, m.inverseMap.Comparator) + iterator := m.Iterator() + for iterator.Next() { + key2, value2 := f(iterator.Key(), iterator.Value()) + newMap.Put(key2, value2) + } + return newMap +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (m *Map[K, V]) Select(f func(key K, value V) bool) *Map[K, V] { + newMap := NewWith[K, V](m.forwardMap.Comparator, m.inverseMap.Comparator) + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + newMap.Put(iterator.Key(), iterator.Value()) + } + } + return newMap +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (m *Map[K, V]) Any(f func(key K, value V) bool) bool { + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (m *Map[K, V]) All(f func(key K, value V) bool) bool { + iterator := m.Iterator() + for iterator.Next() { + if !f(iterator.Key(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (key,value) for which the function is true or nil,nil otherwise if no element +// matches the criteria. +func (m *Map[K, V]) Find(f func(key K, value V) bool) (k K, v V) { + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + return iterator.Key(), iterator.Value() + } + } + return k, v +} diff --git a/maps/treebidimap/iterator.go b/maps/treebidimap/iterator.go new file mode 100644 index 00000000..bf86ea71 --- /dev/null +++ b/maps/treebidimap/iterator.go @@ -0,0 +1,104 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treebidimap + +import ( + "github.com/emirpasic/gods/v2/containers" + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithKey[string, int] = (*Iterator[string, int])(nil) + +// Iterator holding the iterator's state +type Iterator[K comparable, V any] struct { + iterator *rbt.Iterator[K, V] +} + +// Iterator returns a stateful iterator whose elements are key/value pairs. +func (m *Map[K, V]) Iterator() *Iterator[K, V] { + return &Iterator[K, V]{iterator: m.forwardMap.Iterator()} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Next() bool { + return iterator.iterator.Next() +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Prev() bool { + return iterator.iterator.Prev() +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Value() V { + return iterator.iterator.Value() +} + +// Key returns the current element's key. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Key() K { + return iterator.iterator.Key() +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[K, V]) Begin() { + iterator.iterator.Begin() +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[K, V]) End() { + iterator.iterator.End() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator +func (iterator *Iterator[K, V]) First() bool { + return iterator.iterator.First() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Last() bool { + return iterator.iterator.Last() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) NextTo(f func(key K, value V) bool) bool { + for iterator.Next() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) PrevTo(f func(key K, value V) bool) bool { + for iterator.Prev() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} diff --git a/maps/treebidimap/serialization.go b/maps/treebidimap/serialization.go new file mode 100644 index 00000000..b72907b9 --- /dev/null +++ b/maps/treebidimap/serialization.go @@ -0,0 +1,46 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treebidimap + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Map[string, int])(nil) +var _ containers.JSONDeserializer = (*Map[string, int])(nil) + +// ToJSON outputs the JSON representation of the map. +func (m *Map[K, V]) ToJSON() ([]byte, error) { + return m.forwardMap.ToJSON() +} + +// FromJSON populates the map from the input JSON representation. +func (m *Map[K, V]) FromJSON(data []byte) error { + var elements map[K]V + err := json.Unmarshal(data, &elements) + if err != nil { + return err + } + + m.Clear() + for key, value := range elements { + m.Put(key, value) + } + + return nil +} + +// UnmarshalJSON @implements json.Unmarshaler +func (m *Map[K, V]) UnmarshalJSON(bytes []byte) error { + return m.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (m *Map[K, V]) MarshalJSON() ([]byte, error) { + return m.ToJSON() +} diff --git a/maps/treebidimap/treebidimap.go b/maps/treebidimap/treebidimap.go new file mode 100644 index 00000000..27d6c9ef --- /dev/null +++ b/maps/treebidimap/treebidimap.go @@ -0,0 +1,122 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package treebidimap implements a bidirectional map backed by two red-black tree. +// +// This structure guarantees that the map will be in both ascending key and value order. +// +// Other than key and value ordering, the goal with this structure is to avoid duplication of elements, which can be significant if contained elements are large. +// +// A bidirectional map, or hash bag, is an associative data structure in which the (key,value) pairs form a one-to-one correspondence. +// Thus the binary relation is functional in each direction: value can also act as a key to key. +// A pair (a,b) thus provides a unique coupling between 'a' and 'b' so that 'b' can be found when 'a' is used as a key and 'a' can be found when 'b' is used as a key. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/Bidirectional_map +package treebidimap + +import ( + "cmp" + "fmt" + + "strings" + + "github.com/emirpasic/gods/v2/maps" + "github.com/emirpasic/gods/v2/trees/redblacktree" + "github.com/emirpasic/gods/v2/utils" +) + +// Assert Map implementation +var _ maps.BidiMap[string, int] = (*Map[string, int])(nil) + +// Map holds the elements in two red-black trees. +type Map[K, V comparable] struct { + forwardMap redblacktree.Tree[K, V] + inverseMap redblacktree.Tree[V, K] +} + +// New instantiates a bidirectional map. +func New[K, V cmp.Ordered]() *Map[K, V] { + return &Map[K, V]{ + forwardMap: *redblacktree.New[K, V](), + inverseMap: *redblacktree.New[V, K](), + } +} + +// NewWith instantiates a bidirectional map. +func NewWith[K, V comparable](keyComparator utils.Comparator[K], valueComparator utils.Comparator[V]) *Map[K, V] { + return &Map[K, V]{ + forwardMap: *redblacktree.NewWith[K, V](keyComparator), + inverseMap: *redblacktree.NewWith[V, K](valueComparator), + } +} + +// Put inserts element into the map. +func (m *Map[K, V]) Put(key K, value V) { + if v, ok := m.forwardMap.Get(key); ok { + m.inverseMap.Remove(v) + } + if k, ok := m.inverseMap.Get(value); ok { + m.forwardMap.Remove(k) + } + m.forwardMap.Put(key, value) + m.inverseMap.Put(value, key) +} + +// Get searches the element in the map by key and returns its value or nil if key is not found in map. +// Second return parameter is true if key was found, otherwise false. +func (m *Map[K, V]) Get(key K) (value V, found bool) { + return m.forwardMap.Get(key) +} + +// GetKey searches the element in the map by value and returns its key or nil if value is not found in map. +// Second return parameter is true if value was found, otherwise false. +func (m *Map[K, V]) GetKey(value V) (key K, found bool) { + return m.inverseMap.Get(value) +} + +// Remove removes the element from the map by key. +func (m *Map[K, V]) Remove(key K) { + if v, found := m.forwardMap.Get(key); found { + m.forwardMap.Remove(key) + m.inverseMap.Remove(v) + } +} + +// Empty returns true if map does not contain any elements +func (m *Map[K, V]) Empty() bool { + return m.Size() == 0 +} + +// Size returns number of elements in the map. +func (m *Map[K, V]) Size() int { + return m.forwardMap.Size() +} + +// Keys returns all keys (ordered). +func (m *Map[K, V]) Keys() []K { + return m.forwardMap.Keys() +} + +// Values returns all values (ordered). +func (m *Map[K, V]) Values() []V { + return m.inverseMap.Keys() +} + +// Clear removes all elements from the map. +func (m *Map[K, V]) Clear() { + m.forwardMap.Clear() + m.inverseMap.Clear() +} + +// String returns a string representation of container +func (m *Map[K, V]) String() string { + str := "TreeBidiMap\nmap[" + it := m.Iterator() + for it.Next() { + str += fmt.Sprintf("%v:%v ", it.Key(), it.Value()) + } + return strings.TrimRight(str, " ") + "]" +} diff --git a/maps/treebidimap/treebidimap_test.go b/maps/treebidimap/treebidimap_test.go new file mode 100644 index 00000000..8c579ca6 --- /dev/null +++ b/maps/treebidimap/treebidimap_test.go @@ -0,0 +1,778 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treebidimap + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/emirpasic/gods/v2/testutils" +) + +func TestMapPut(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + if actualValue := m.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4, 5, 6, 7}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d", "e", "f", "g"}) + + // key,expectedValue,expectedFound + tests1 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {8, "", false}, + } + + for _, test := range tests1 { + // retrievals + actualValue, actualFound := m.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } +} + +func TestMapRemove(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + m.Remove(5) + m.Remove(6) + m.Remove(7) + m.Remove(8) + m.Remove(5) + + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d"}) + + if actualValue := m.Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + + tests2 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "", false}, + {6, "", false}, + {7, "", false}, + {8, "", false}, + } + + for _, test := range tests2 { + actualValue, actualFound := m.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } + + m.Remove(1) + m.Remove(4) + m.Remove(2) + m.Remove(3) + m.Remove(2) + m.Remove(2) + + testutils.SameElements(t, m.Keys(), nil) + testutils.SameElements(t, m.Values(), nil) + if actualValue := m.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + if actualValue := m.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestMapGetKey(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + // key,expectedValue,expectedFound + tests1 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {0, "x", false}, + } + + for _, test := range tests1 { + // retrievals + actualValue, actualFound := m.GetKey(test[1].(string)) + if actualValue != test[0] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[0]) + } + } +} + +func sameElements(a []interface{}, b []interface{}) bool { + if len(a) != len(b) { + return false + } + for _, av := range a { + found := false + for _, bv := range b { + if av == bv { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +func TestMapEach(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + count := 0 + m.Each(func(key string, value int) { + count++ + if actualValue, expectedValue := count, value; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + switch value { + case 1: + if actualValue, expectedValue := key, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := key, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 3: + if actualValue, expectedValue := key, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} + +func TestMapMap(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + mappedMap := m.Map(func(key1 string, value1 int) (key2 string, value2 int) { + return key1, value1 * value1 + }) + if actualValue, _ := mappedMap.Get("a"); actualValue != 1 { + t.Errorf("Got %v expected %v", actualValue, "mapped: a") + } + if actualValue, _ := mappedMap.Get("b"); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, "mapped: b") + } + if actualValue, _ := mappedMap.Get("c"); actualValue != 9 { + t.Errorf("Got %v expected %v", actualValue, "mapped: c") + } + if mappedMap.Size() != 3 { + t.Errorf("Got %v expected %v", mappedMap.Size(), 3) + } +} + +func TestMapSelect(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + selectedMap := m.Select(func(key string, value int) bool { + return key >= "a" && key <= "b" + }) + if actualValue, _ := selectedMap.Get("a"); actualValue != 1 { + t.Errorf("Got %v expected %v", actualValue, "value: a") + } + if actualValue, _ := selectedMap.Get("b"); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, "value: b") + } + if selectedMap.Size() != 2 { + t.Errorf("Got %v expected %v", selectedMap.Size(), 2) + } +} + +func TestMapAny(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + any := m.Any(func(key string, value int) bool { + return value == 3 + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = m.Any(func(key string, value int) bool { + return value == 4 + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} + +func TestMapAll(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + all := m.All(func(key string, value int) bool { + return key >= "a" && key <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = m.All(func(key string, value int) bool { + return key >= "a" && key <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} + +func TestMapFind(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + foundKey, foundValue := m.Find(func(key string, value int) bool { + return key == "c" + }) + if foundKey != "c" || foundValue != 3 { + t.Errorf("Got %v -> %v expected %v -> %v", foundKey, foundValue, "c", 3) + } + foundKey, foundValue = m.Find(func(key string, value int) bool { + return key == "x" + }) + if foundKey != "" || foundValue != 0 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundKey, nil, nil) + } +} + +func TestMapChaining(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + chainedMap := m.Select(func(key string, value int) bool { + return value > 1 + }).Map(func(key string, value int) (string, int) { + return key + key, value * value + }) + if actualValue := chainedMap.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, found := chainedMap.Get("aa"); actualValue != 0 || found { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue, found := chainedMap.Get("bb"); actualValue != 4 || !found { + t.Errorf("Got %v expected %v", actualValue, 4) + } + if actualValue, found := chainedMap.Get("cc"); actualValue != 9 || !found { + t.Errorf("Got %v expected %v", actualValue, 9) + } +} + +func TestMapIteratorNextOnEmpty(t *testing.T) { + m := New[string, string]() + it := m.Iterator() + it = m.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty map") + } +} + +func TestMapIteratorPrevOnEmpty(t *testing.T) { + m := New[string, string]() + it := m.Iterator() + it = m.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty map") + } +} + +func TestMapIteratorNext(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + + it := m.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + value := it.Value() + switch key { + case "a": + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "b": + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "c": + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestMapIteratorPrev(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + + it := m.Iterator() + for it.Next() { + } + countDown := m.Size() + for it.Prev() { + key := it.Key() + value := it.Value() + switch key { + case "a": + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "b": + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "c": + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := value, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestMapIteratorBegin(t *testing.T) { + m := New[int, string]() + it := m.Iterator() + it.Begin() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + for it.Next() { + } + it.Begin() + it.Next() + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestMapIteratorEnd(t *testing.T) { + m := New[int, string]() + it := m.Iterator() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it.End() + it.Prev() + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestMapIteratorFirst(t *testing.T) { + m := New[int, string]() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it := m.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestMapIteratorLast(t *testing.T) { + m := New[int, string]() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it := m.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestMapIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + m := New[int, string]() + it := m.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // NextTo (not found) + { + m := New[int, string]() + m.Put(0, "xx") + m.Put(1, "yy") + it := m.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // NextTo (found) + { + m := New[int, string]() + m.Put(0, "aa") + m.Put(1, "bb") + m.Put(2, "cc") + it := m.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestMapIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + m := New[int, string]() + it := m.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // PrevTo (not found) + { + m := New[int, string]() + m.Put(0, "xx") + m.Put(1, "yy") + it := m.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // PrevTo (found) + { + m := New[int, string]() + m.Put(0, "aa") + m.Put(1, "bb") + m.Put(2, "cc") + it := m.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestMapSerialization(t *testing.T) { + for i := 0; i < 10; i++ { + original := New[string, string]() + original.Put("d", "4") + original.Put("e", "5") + original.Put("c", "3") + original.Put("b", "2") + original.Put("a", "1") + + serialized, err := original.ToJSON() + if err != nil { + t.Errorf("Got error %v", err) + } + + deserialized := New[string, string]() + err = deserialized.FromJSON(serialized) + if err != nil { + t.Errorf("Got error %v", err) + } + + if original.Size() != deserialized.Size() { + t.Errorf("Got map of size %d, expected %d", original.Size(), deserialized.Size()) + } + original.Each(func(key string, expected string) { + actual, ok := deserialized.Get(key) + if !ok || actual != expected { + t.Errorf("Did not find expected value %v for key %v in deserialied map (got %q)", expected, key, actual) + } + }) + } + + m := New[string, float64]() + m.Put("a", 1.0) + m.Put("b", 2.0) + m.Put("c", 3.0) + + _, err := json.Marshal([]interface{}{"a", "b", "c", m}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), &m) + if err != nil { + t.Errorf("Got error %v", err) + } +} + +func TestMapString(t *testing.T) { + c := New[string, string]() + c.Put("a", "a") + if !strings.HasPrefix(c.String(), "TreeBidiMap") { + t.Errorf("String should start with container name") + } +} + +func benchmarkGet(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Get(n) + } + } +} + +func benchmarkPut(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Put(n, n) + } + } +} + +func benchmarkRemove(b *testing.B, m *Map[int, int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Remove(n) + } + } +} + +func BenchmarkTreeBidiMapGet100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeBidiMapGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeBidiMapGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeBidiMapGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeBidiMapPut100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeBidiMapPut1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeBidiMapPut10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeBidiMapPut100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeBidiMapRemove100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeBidiMapRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeBidiMapRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeBidiMapRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, int]() + for n := 0; n < size; n++ { + m.Put(n, n) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} diff --git a/maps/treemap/enumerable.go b/maps/treemap/enumerable.go new file mode 100644 index 00000000..7b0df161 --- /dev/null +++ b/maps/treemap/enumerable.go @@ -0,0 +1,82 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treemap + +import ( + "github.com/emirpasic/gods/v2/containers" + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +// Assert Enumerable implementation +var _ containers.EnumerableWithKey[string, int] = (*Map[string, int])(nil) + +// Each calls the given function once for each element, passing that element's key and value. +func (m *Map[K, V]) Each(f func(key K, value V)) { + iterator := m.Iterator() + for iterator.Next() { + f(iterator.Key(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a container +// containing the values returned by the given function as key/value pairs. +func (m *Map[K, V]) Map(f func(key1 K, value1 V) (K, V)) *Map[K, V] { + newMap := &Map[K, V]{tree: rbt.NewWith[K, V](m.tree.Comparator)} + iterator := m.Iterator() + for iterator.Next() { + key2, value2 := f(iterator.Key(), iterator.Value()) + newMap.Put(key2, value2) + } + return newMap +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (m *Map[K, V]) Select(f func(key K, value V) bool) *Map[K, V] { + newMap := &Map[K, V]{tree: rbt.NewWith[K, V](m.tree.Comparator)} + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + newMap.Put(iterator.Key(), iterator.Value()) + } + } + return newMap +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (m *Map[K, V]) Any(f func(key K, value V) bool) bool { + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (m *Map[K, V]) All(f func(key K, value V) bool) bool { + iterator := m.Iterator() + for iterator.Next() { + if !f(iterator.Key(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (key,value) for which the function is true or nil,nil otherwise if no element +// matches the criteria. +func (m *Map[K, V]) Find(f func(key K, value V) bool) (k K, v V) { + iterator := m.Iterator() + for iterator.Next() { + if f(iterator.Key(), iterator.Value()) { + return iterator.Key(), iterator.Value() + } + } + return k, v +} diff --git a/maps/treemap/iterator.go b/maps/treemap/iterator.go new file mode 100644 index 00000000..9bdf91b5 --- /dev/null +++ b/maps/treemap/iterator.go @@ -0,0 +1,104 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treemap + +import ( + "github.com/emirpasic/gods/v2/containers" + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithKey[string, int] = (*Iterator[string, int])(nil) + +// Iterator holding the iterator's state +type Iterator[K comparable, V any] struct { + iterator *rbt.Iterator[K, V] +} + +// Iterator returns a stateful iterator whose elements are key/value pairs. +func (m *Map[K, V]) Iterator() *Iterator[K, V] { + return &Iterator[K, V]{iterator: m.tree.Iterator()} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Next() bool { + return iterator.iterator.Next() +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Prev() bool { + return iterator.iterator.Prev() +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Value() V { + return iterator.iterator.Value() +} + +// Key returns the current element's key. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Key() K { + return iterator.iterator.Key() +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[K, V]) Begin() { + iterator.iterator.Begin() +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[K, V]) End() { + iterator.iterator.End() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator +func (iterator *Iterator[K, V]) First() bool { + return iterator.iterator.First() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Last() bool { + return iterator.iterator.Last() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) NextTo(f func(key K, value V) bool) bool { + for iterator.Next() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) PrevTo(f func(key K, value V) bool) bool { + for iterator.Prev() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} diff --git a/maps/treemap/serialization.go b/maps/treemap/serialization.go new file mode 100644 index 00000000..c63c4972 --- /dev/null +++ b/maps/treemap/serialization.go @@ -0,0 +1,33 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treemap + +import ( + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Map[string, int])(nil) +var _ containers.JSONDeserializer = (*Map[string, int])(nil) + +// ToJSON outputs the JSON representation of the map. +func (m *Map[K, V]) ToJSON() ([]byte, error) { + return m.tree.ToJSON() +} + +// FromJSON populates the map from the input JSON representation. +func (m *Map[K, V]) FromJSON(data []byte) error { + return m.tree.FromJSON(data) +} + +// UnmarshalJSON @implements json.Unmarshaler +func (m *Map[K, V]) UnmarshalJSON(bytes []byte) error { + return m.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (m *Map[K, V]) MarshalJSON() ([]byte, error) { + return m.ToJSON() +} diff --git a/maps/treemap/treemap.go b/maps/treemap/treemap.go index 6386d69a..f3766622 100644 --- a/maps/treemap/treemap.go +++ b/maps/treemap/treemap.go @@ -1,111 +1,147 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of order map backed by red-black tree. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package treemap implements a map backed by red-black tree. +// // Elements are ordered by key in the map. +// // Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/Associative_array - +// +// Reference: http://en.wikipedia.org/wiki/Associative_array package treemap import ( - "github.com/emirpasic/gods/maps" - rbt "github.com/emirpasic/gods/trees/redblacktree" - "github.com/emirpasic/gods/utils" + "cmp" + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/maps" + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" + "github.com/emirpasic/gods/v2/utils" ) -func assertInterfaceImplementation() { - var _ maps.Interface = (*Map)(nil) -} +// Assert Map implementation +var _ maps.Map[string, int] = (*Map[string, int])(nil) -type Map struct { - tree *rbt.Tree +// Map holds the elements in a red-black tree +type Map[K comparable, V any] struct { + tree *rbt.Tree[K, V] } -// Instantiates a tree map with the custom comparator. -func NewWith(comparator utils.Comparator) *Map { - return &Map{tree: rbt.NewWith(comparator)} +// New instantiates a tree map with the built-in comparator for K +func New[K cmp.Ordered, V any]() *Map[K, V] { + return &Map[K, V]{tree: rbt.New[K, V]()} } -// Instantiates a tree map with the IntComparator, i.e. keys are of type int. -func NewWithIntComparator() *Map { - return &Map{tree: rbt.NewWithIntComparator()} +// NewWith instantiates a tree map with the custom comparator. +func NewWith[K comparable, V any](comparator utils.Comparator[K]) *Map[K, V] { + return &Map[K, V]{tree: rbt.NewWith[K, V](comparator)} } -// Instantiates a tree map with the StringComparator, i.e. keys are of type string. -func NewWithStringComparator() *Map { - return &Map{tree: rbt.NewWithStringComparator()} -} - -// Inserts key-value pair into the map. +// Put inserts key-value pair into the map. // Key should adhere to the comparator's type assertion, otherwise method panics. -func (m *Map) Put(key interface{}, value interface{}) { +func (m *Map[K, V]) Put(key K, value V) { m.tree.Put(key, value) } -// Searches the element in the map by key and returns its value or nil if key is not found in tree. +// Get searches the element in the map by key and returns its value or nil if key is not found in tree. // Second return parameter is true if key was found, otherwise false. // Key should adhere to the comparator's type assertion, otherwise method panics. -func (m *Map) Get(key interface{}) (value interface{}, found bool) { +func (m *Map[K, V]) Get(key K) (value V, found bool) { return m.tree.Get(key) } -// Remove the element from the map by key. +// Remove removes the element from the map by key. // Key should adhere to the comparator's type assertion, otherwise method panics. -func (m *Map) Remove(key interface{}) { +func (m *Map[K, V]) Remove(key K) { m.tree.Remove(key) } -// Returns true if map does not contain any elements -func (m *Map) Empty() bool { +// Empty returns true if map does not contain any elements +func (m *Map[K, V]) Empty() bool { return m.tree.Empty() } -// Returns number of elements in the map. -func (m *Map) Size() int { +// Size returns number of elements in the map. +func (m *Map[K, V]) Size() int { return m.tree.Size() } -// Returns all keys in-order -func (m *Map) Keys() []interface{} { +// Keys returns all keys in-order +func (m *Map[K, V]) Keys() []K { return m.tree.Keys() } -// Returns all values in-order based on the key. -func (m *Map) Values() []interface{} { +// Values returns all values in-order based on the key. +func (m *Map[K, V]) Values() []V { return m.tree.Values() } -// Removes all elements from the map. -func (m *Map) Clear() { +// Clear removes all elements from the map. +func (m *Map[K, V]) Clear() { m.tree.Clear() } -func (m *Map) String() string { - str := "TreeMap\n" - str += m.tree.String() - return str +// Min returns the minimum key and its value from the tree map. +// Returns 0-value, 0-value, false if map is empty. +func (m *Map[K, V]) Min() (key K, value V, ok bool) { + if node := m.tree.Left(); node != nil { + return node.Key, node.Value, true + } + return key, value, false +} + +// Max returns the maximum key and its value from the tree map. +// Returns 0-value, 0-value, false if map is empty. +func (m *Map[K, V]) Max() (key K, value V, ok bool) { + if node := m.tree.Right(); node != nil { + return node.Key, node.Value, true + } + return key, value, false +} + +// Floor finds the floor key-value pair for the input key. +// In case that no floor is found, then both returned values will be nil. +// It's generally enough to check the first value (key) for nil, which determines if floor was found. +// +// Floor key is defined as the largest key that is smaller than or equal to the given key. +// A floor key may not be found, either because the map is empty, or because +// all keys in the map are larger than the given key. +// +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (m *Map[K, V]) Floor(key K) (foundKey K, foundValue V, ok bool) { + node, found := m.tree.Floor(key) + if found { + return node.Key, node.Value, true + } + return foundKey, foundValue, false +} + +// Ceiling finds the ceiling key-value pair for the input key. +// In case that no ceiling is found, then both returned values will be nil. +// It's generally enough to check the first value (key) for nil, which determines if ceiling was found. +// +// Ceiling key is defined as the smallest key that is larger than or equal to the given key. +// A ceiling key may not be found, either because the map is empty, or because +// all keys in the map are smaller than the given key. +// +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (m *Map[K, V]) Ceiling(key K) (foundKey K, foundValue V, ok bool) { + node, found := m.tree.Ceiling(key) + if found { + return node.Key, node.Value, true + } + return foundKey, foundValue, false +} + +// String returns a string representation of container +func (m *Map[K, V]) String() string { + str := "TreeMap\nmap[" + it := m.Iterator() + for it.Next() { + str += fmt.Sprintf("%v:%v ", it.Key(), it.Value()) + } + return strings.TrimRight(str, " ") + "]" + } diff --git a/maps/treemap/treemap_test.go b/maps/treemap/treemap_test.go index 03fb23c5..e608f0e6 100644 --- a/maps/treemap/treemap_test.go +++ b/maps/treemap/treemap_test.go @@ -1,41 +1,19 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package treemap import ( - "fmt" + "encoding/json" + "strings" "testing" -) - -func TestTreeMap(t *testing.T) { - m := NewWithIntComparator() + "github.com/emirpasic/gods/v2/testutils" +) - // insertions +func TestMapPut(t *testing.T) { + m := New[int, string]() m.Put(5, "e") m.Put(6, "f") m.Put(7, "g") @@ -45,20 +23,11 @@ func TestTreeMap(t *testing.T) { m.Put(2, "b") m.Put(1, "a") //overwrite - // Test Size() if actualValue := m.Size(); actualValue != 7 { t.Errorf("Got %v expected %v", actualValue, 7) } - - // test Keys() - if actualValue, expactedValue := fmt.Sprintf("%d%d%d%d%d%d%d", m.Keys()...), "1234567"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } - - // test Values() - if actualValue, expactedValue := fmt.Sprintf("%s%s%s%s%s%s%s", m.Values()...), "abcdefg"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4, 5, 6, 7}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d", "e", "f", "g"}) // key,expectedValue,expectedFound tests1 := [][]interface{}{ @@ -69,37 +38,113 @@ func TestTreeMap(t *testing.T) { {5, "e", true}, {6, "f", true}, {7, "g", true}, - {8, nil, false}, + {8, "", false}, } for _, test := range tests1 { // retrievals - actualValue, actualFound := m.Get(test[0]) + actualValue, actualFound := m.Get(test[0].(int)) if actualValue != test[1] || actualFound != test[2] { t.Errorf("Got %v expected %v", actualValue, test[1]) } } +} + +func TestMapMin(t *testing.T) { + m := New[int, string]() + + if k, v, ok := m.Min(); k != 0 || v != "" || ok { + t.Errorf("Got %v->%v->%v expected %v->%v-%v", k, v, ok, 0, "", false) + } + + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + actualKey, actualValue, actualOk := m.Min() + expectedKey, expectedValue, expectedOk := 1, "a", true + if actualKey != expectedKey { + t.Errorf("Got %v expected %v", actualKey, expectedKey) + } + if actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualOk != expectedOk { + t.Errorf("Got %v expected %v", actualOk, expectedOk) + } +} + +func TestMapMax(t *testing.T) { + m := New[int, string]() + + if k, v, ok := m.Max(); k != 0 || v != "" || ok { + t.Errorf("Got %v->%v->%v expected %v->%v-%v", k, v, ok, 0, "", false) + } + + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite + + actualKey, actualValue, actualOk := m.Max() + expectedKey, expectedValue, expectedOk := 7, "g", true + if actualKey != expectedKey { + t.Errorf("Got %v expected %v", actualKey, expectedKey) + } + if actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualOk != expectedOk { + t.Errorf("Got %v expected %v", actualOk, expectedOk) + } +} + +func TestMapClear(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + if actualValue, expectedValue := m.Size(), 4; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + m.Clear() + if actualValue, expectedValue := m.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestMapRemove(t *testing.T) { + m := New[int, string]() + m.Put(5, "e") + m.Put(6, "f") + m.Put(7, "g") + m.Put(3, "c") + m.Put(4, "d") + m.Put(1, "x") + m.Put(2, "b") + m.Put(1, "a") //overwrite - // removals m.Remove(5) m.Remove(6) m.Remove(7) m.Remove(8) m.Remove(5) - // Test Keys() - if actualValue, expactedValue := fmt.Sprintf("%d%d%d%d", m.Keys()...), "1234"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } - - // test Values() - if actualValue, expactedValue := fmt.Sprintf("%s%s%s%s", m.Values()...), "abcd"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) - } + testutils.SameElements(t, m.Keys(), []int{1, 2, 3, 4}) + testutils.SameElements(t, m.Values(), []string{"a", "b", "c", "d"}) - // Test Size() if actualValue := m.Size(); actualValue != 4 { - t.Errorf("Got %v expected %v", actualValue, 7) + t.Errorf("Got %v expected %v", actualValue, 4) } tests2 := [][]interface{}{ @@ -107,21 +152,19 @@ func TestTreeMap(t *testing.T) { {2, "b", true}, {3, "c", true}, {4, "d", true}, - {5, nil, false}, - {6, nil, false}, - {7, nil, false}, - {8, nil, false}, + {5, "", false}, + {6, "", false}, + {7, "", false}, + {8, "", false}, } for _, test := range tests2 { - // retrievals - actualValue, actualFound := m.Get(test[0]) + actualValue, actualFound := m.Get(test[0].(int)) if actualValue != test[1] || actualFound != test[2] { t.Errorf("Got %v expected %v", actualValue, test[1]) } } - // removals m.Remove(1) m.Remove(4) m.Remove(2) @@ -129,45 +172,700 @@ func TestTreeMap(t *testing.T) { m.Remove(2) m.Remove(2) - // Test Keys() - if actualValue, expactedValue := fmt.Sprintf("%s", m.Keys()), "[]"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) + testutils.SameElements(t, m.Keys(), nil) + testutils.SameElements(t, m.Values(), nil) + if actualValue := m.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) } + if actualValue := m.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} - // test Values() - if actualValue, expactedValue := fmt.Sprintf("%s", m.Values()), "[]"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) +func TestMapFloor(t *testing.T) { + m := New[int, string]() + m.Put(7, "g") + m.Put(3, "c") + m.Put(1, "a") + + // key,expectedKey,expectedValue,expectedFound + tests1 := [][]interface{}{ + {-1, 0, "", false}, + {0, 0, "", false}, + {1, 1, "a", true}, + {2, 1, "a", true}, + {3, 3, "c", true}, + {4, 3, "c", true}, + {7, 7, "g", true}, + {8, 7, "g", true}, } - // Test Size() - if actualValue := m.Size(); actualValue != 0 { - t.Errorf("Got %v expected %v", actualValue, 0) + for _, test := range tests1 { + // retrievals + actualKey, actualValue, actualOk := m.Floor(test[0].(int)) + if actualKey != test[1] || actualValue != test[2] || actualOk != test[3] { + t.Errorf("Got %v, %v, %v, expected %v, %v, %v", actualKey, actualValue, actualOk, test[1], test[2], test[3]) + } } +} - // Test Empty() - if actualValue := m.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestMapCeiling(t *testing.T) { + m := New[int, string]() + m.Put(7, "g") + m.Put(3, "c") + m.Put(1, "a") + + // key,expectedKey,expectedValue,expectedFound + tests1 := [][]interface{}{ + {-1, 1, "a", true}, + {0, 1, "a", true}, + {1, 1, "a", true}, + {2, 3, "c", true}, + {3, 3, "c", true}, + {4, 7, "g", true}, + {7, 7, "g", true}, + {8, 0, "", false}, + } + + for _, test := range tests1 { + // retrievals + actualKey, actualValue, actualOk := m.Ceiling(test[0].(int)) + if actualKey != test[1] || actualValue != test[2] || actualOk != test[3] { + t.Errorf("Got %v, %v, %v, expected %v, %v, %v", actualKey, actualValue, actualOk, test[1], test[2], test[3]) + } + } +} + +func TestMapEach(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + count := 0 + m.Each(func(key string, value int) { + count++ + if actualValue, expectedValue := count, value; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + switch value { + case 1: + if actualValue, expectedValue := key, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := key, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 3: + if actualValue, expectedValue := key, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} + +func TestMapMap(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + mappedMap := m.Map(func(key1 string, value1 int) (key2 string, value2 int) { + return key1, value1 * value1 + }) + if actualValue, _ := mappedMap.Get("a"); actualValue != 1 { + t.Errorf("Got %v expected %v", actualValue, "mapped: a") + } + if actualValue, _ := mappedMap.Get("b"); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, "mapped: b") } + if actualValue, _ := mappedMap.Get("c"); actualValue != 9 { + t.Errorf("Got %v expected %v", actualValue, "mapped: c") + } + if mappedMap.Size() != 3 { + t.Errorf("Got %v expected %v", mappedMap.Size(), 3) + } +} + +func TestMapSelect(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + selectedMap := m.Select(func(key string, value int) bool { + return key >= "a" && key <= "b" + }) + if actualValue, _ := selectedMap.Get("a"); actualValue != 1 { + t.Errorf("Got %v expected %v", actualValue, "value: a") + } + if actualValue, _ := selectedMap.Get("b"); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, "value: b") + } + if selectedMap.Size() != 2 { + t.Errorf("Got %v expected %v", selectedMap.Size(), 2) + } +} + +func TestMapAny(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + any := m.Any(func(key string, value int) bool { + return value == 3 + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = m.Any(func(key string, value int) bool { + return value == 4 + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} + +func TestMapAll(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + all := m.All(func(key string, value int) bool { + return key >= "a" && key <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = m.All(func(key string, value int) bool { + return key >= "a" && key <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} + +func TestMapFind(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + foundKey, foundValue := m.Find(func(key string, value int) bool { + return key == "c" + }) + if foundKey != "c" || foundValue != 3 { + t.Errorf("Got %v -> %v expected %v -> %v", foundKey, foundValue, "c", 3) + } + foundKey, foundValue = m.Find(func(key string, value int) bool { + return key == "x" + }) + if foundKey != "" || foundValue != 0 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundKey, nil, nil) + } +} + +func TestMapChaining(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + chainedMap := m.Select(func(key string, value int) bool { + return value > 1 + }).Map(func(key string, value int) (string, int) { + return key + key, value * value + }) + if actualValue := chainedMap.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, found := chainedMap.Get("aa"); actualValue != 0 || found { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue, found := chainedMap.Get("bb"); actualValue != 4 || !found { + t.Errorf("Got %v expected %v", actualValue, 4) + } + if actualValue, found := chainedMap.Get("cc"); actualValue != 9 || !found { + t.Errorf("Got %v expected %v", actualValue, 9) + } +} + +func TestMapIteratorNextOnEmpty(t *testing.T) { + m := New[string, int]() + it := m.Iterator() + it = m.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty map") + } +} + +func TestMapIteratorPrevOnEmpty(t *testing.T) { + m := New[string, int]() + it := m.Iterator() + it = m.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty map") + } +} + +func TestMapIteratorNext(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + it := m.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + value := it.Value() + switch key { + case "a": + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "b": + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "c": + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestMapIteratorPrev(t *testing.T) { + m := New[string, int]() + m.Put("c", 3) + m.Put("a", 1) + m.Put("b", 2) + + it := m.Iterator() + for it.Next() { + } + countDown := m.Size() + for it.Prev() { + key := it.Key() + value := it.Value() + switch key { + case "a": + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "b": + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case "c": + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := value, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestMapIteratorBegin(t *testing.T) { + m := New[int, string]() + it := m.Iterator() + it.Begin() + m.Put(3, "c") m.Put(1, "a") m.Put(2, "b") - m.Clear() + for it.Next() { + } + it.Begin() + it.Next() + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} - // Test Empty() - if actualValue := m.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestMapIteratorEnd(t *testing.T) { + m := New[int, string]() + it := m.Iterator() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it.End() + it.Prev() + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestMapIteratorFirst(t *testing.T) { + m := New[int, string]() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it := m.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestMapIteratorLast(t *testing.T) { + m := New[int, string]() + m.Put(3, "c") + m.Put(1, "a") + m.Put(2, "b") + it := m.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestMapIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + m := New[int, string]() + it := m.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // NextTo (not found) + { + m := New[int, string]() + m.Put(0, "xx") + m.Put(1, "yy") + it := m.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // NextTo (found) + { + m := New[int, string]() + m.Put(0, "aa") + m.Put(1, "bb") + m.Put(2, "cc") + it := m.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestMapIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + m := New[int, string]() + it := m.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // PrevTo (not found) + { + m := New[int, string]() + m.Put(0, "xx") + m.Put(1, "yy") + it := m.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + } + + // PrevTo (found) + { + m := New[int, string]() + m.Put(0, "aa") + m.Put(1, "bb") + m.Put(2, "cc") + it := m.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty map") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestMapSerialization(t *testing.T) { + for i := 0; i < 10; i++ { + original := New[string, string]() + original.Put("d", "4") + original.Put("e", "5") + original.Put("c", "3") + original.Put("b", "2") + original.Put("a", "1") + + assertSerialization(original, "A", t) + + serialized, err := original.ToJSON() + if err != nil { + t.Errorf("Got error %v", err) + } + assertSerialization(original, "B", t) + + deserialized := New[string, string]() + err = deserialized.FromJSON(serialized) + if err != nil { + t.Errorf("Got error %v", err) + } + assertSerialization(deserialized, "C", t) + } + + m := New[string, float64]() + m.Put("a", 1.0) + m.Put("b", 2.0) + m.Put("c", 3.0) + + _, err := json.Marshal([]interface{}{"a", "b", "c", m}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), &m) + if err != nil { + t.Errorf("Got error %v", err) + } +} + +func TestMapString(t *testing.T) { + c := New[string, int]() + c.Put("a", 1) + if !strings.HasPrefix(c.String(), "TreeMap") { + t.Errorf("String should start with container name") } +} +// noinspection GoBoolExpressions +func assertSerialization(m *Map[string, string], txt string, t *testing.T) { + if actualValue := m.Keys(); false || + actualValue[0] != "a" || + actualValue[1] != "b" || + actualValue[2] != "c" || + actualValue[3] != "d" || + actualValue[4] != "e" { + t.Errorf("[%s] Got %v expected %v", txt, actualValue, "[a,b,c,d,e]") + } + if actualValue := m.Values(); false || + actualValue[0] != "1" || + actualValue[1] != "2" || + actualValue[2] != "3" || + actualValue[3] != "4" || + actualValue[4] != "5" { + t.Errorf("[%s] Got %v expected %v", txt, actualValue, "[1,2,3,4,5]") + } + if actualValue, expectedValue := m.Size(), 5; actualValue != expectedValue { + t.Errorf("[%s] Got %v expected %v", txt, actualValue, expectedValue) + } } -func BenchmarkTreeMap(b *testing.B) { +func benchmarkGet(b *testing.B, m *Map[int, struct{}], size int) { for i := 0; i < b.N; i++ { - m := NewWithIntComparator() - for n := 0; n < 1000; n++ { - m.Put(n, n) + for n := 0; n < size; n++ { + m.Get(n) } - for n := 0; n < 1000; n++ { + } +} + +func benchmarkPut(b *testing.B, m *Map[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + } +} + +func benchmarkRemove(b *testing.B, m *Map[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { m.Remove(n) } } } + +func BenchmarkTreeMapGet100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, m, size) +} + +func BenchmarkTreeMapPut100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, struct{}]() + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapPut1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapPut10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapPut100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, m, size) +} + +func BenchmarkTreeMapRemove100(b *testing.B) { + b.StopTimer() + size := 100 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeMapRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeMapRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} + +func BenchmarkTreeMapRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + m := New[int, struct{}]() + for n := 0; n < size; n++ { + m.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, m, size) +} diff --git a/queues/arrayqueue/arrayqueue.go b/queues/arrayqueue/arrayqueue.go new file mode 100644 index 00000000..6c57c722 --- /dev/null +++ b/queues/arrayqueue/arrayqueue.go @@ -0,0 +1,88 @@ +// Copyright (c) 2021, Aryan Ahadinia. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package arrayqueue implements a queue backed by array list. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/Queue_(abstract_data_type) +package arrayqueue + +import ( + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/lists/arraylist" + "github.com/emirpasic/gods/v2/queues" +) + +// Assert Queue implementation +var _ queues.Queue[int] = (*Queue[int])(nil) + +// Queue holds elements in an array-list +type Queue[T comparable] struct { + list *arraylist.List[T] +} + +// New instantiates a new empty queue +func New[T comparable]() *Queue[T] { + return &Queue[T]{list: arraylist.New[T]()} +} + +// Enqueue adds a value to the end of the queue +func (queue *Queue[T]) Enqueue(value T) { + queue.list.Add(value) +} + +// Dequeue removes first element of the queue and returns it, or nil if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to dequeue. +func (queue *Queue[T]) Dequeue() (value T, ok bool) { + value, ok = queue.list.Get(0) + if ok { + queue.list.Remove(0) + } + return +} + +// Peek returns first element of the queue without removing it, or nil if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to peek. +func (queue *Queue[T]) Peek() (value T, ok bool) { + return queue.list.Get(0) +} + +// Empty returns true if queue does not contain any elements. +func (queue *Queue[T]) Empty() bool { + return queue.list.Empty() +} + +// Size returns number of elements within the queue. +func (queue *Queue[T]) Size() int { + return queue.list.Size() +} + +// Clear removes all elements from the queue. +func (queue *Queue[T]) Clear() { + queue.list.Clear() +} + +// Values returns all elements in the queue (FIFO order). +func (queue *Queue[T]) Values() []T { + return queue.list.Values() +} + +// String returns a string representation of container +func (queue *Queue[T]) String() string { + str := "ArrayQueue\n" + values := []string{} + for _, value := range queue.list.Values() { + values = append(values, fmt.Sprintf("%v", value)) + } + str += strings.Join(values, ", ") + return str +} + +// Check that the index is within bounds of the list +func (queue *Queue[T]) withinRange(index int) bool { + return index >= 0 && index < queue.list.Size() +} diff --git a/queues/arrayqueue/arrayqueue_test.go b/queues/arrayqueue/arrayqueue_test.go new file mode 100644 index 00000000..8c295da5 --- /dev/null +++ b/queues/arrayqueue/arrayqueue_test.go @@ -0,0 +1,494 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arrayqueue + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/emirpasic/gods/v2/testutils" +) + +func TestQueueEnqueue(t *testing.T) { + queue := New[int]() + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + + if actualValue := queue.Values(); actualValue[0] != 1 || actualValue[1] != 2 || actualValue[2] != 3 { + t.Errorf("Got %v expected %v", actualValue, "[1,2,3]") + } + if actualValue := queue.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := queue.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Peek(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } +} + +func TestQueuePeek(t *testing.T) { + queue := New[int]() + if actualValue, ok := queue.Peek(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + if actualValue, ok := queue.Peek(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } +} + +func TestQueueDequeue(t *testing.T) { + queue := New[int]() + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + queue.Dequeue() + if actualValue, ok := queue.Peek(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, ok := queue.Dequeue(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, ok := queue.Dequeue(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Dequeue(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := queue.Values(); len(actualValue) != 0 { + t.Errorf("Got %v expected %v", actualValue, "[]") + } +} + +func TestQueueIteratorOnEmpty(t *testing.T) { + queue := New[int]() + it := queue.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty queue") + } +} + +func TestQueueIteratorNext(t *testing.T) { + queue := New[string]() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + it := queue.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + queue.Clear() + it = queue.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty queue") + } +} + +func TestQueueIteratorPrev(t *testing.T) { + queue := New[string]() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + it := queue.Iterator() + for it.Next() { + } + count := 0 + for it.Prev() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, 3-count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestQueueIteratorBegin(t *testing.T) { + queue := New[string]() + it := queue.Iterator() + it.Begin() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestQueueIteratorEnd(t *testing.T) { + queue := New[string]() + it := queue.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + it.End() + if index := it.Index(); index != queue.Size() { + t.Errorf("Got %v expected %v", index, queue.Size()) + } + + it.Prev() + if index, value := it.Index(), it.Value(); index != queue.Size()-1 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, queue.Size()-1, "c") + } +} + +func TestQueueIteratorFirst(t *testing.T) { + queue := New[string]() + it := queue.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestQueueIteratorLast(t *testing.T) { + queue := New[string]() + it := queue.Iterator() + if actualValue, expectedValue := it.Last(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "c") + } +} + +func TestQueueIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + queue := New[string]() + it := queue.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // NextTo (not found) + { + queue := New[string]() + queue.Enqueue("xx") + queue.Enqueue("yy") + it := queue.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // NextTo (found) + { + queue := New[string]() + queue.Enqueue("aa") + queue.Enqueue("bb") + queue.Enqueue("cc") + it := queue.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestQueueIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + queue := New[string]() + it := queue.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // PrevTo (not found) + { + queue := New[string]() + queue.Enqueue("xx") + queue.Enqueue("yy") + it := queue.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // PrevTo (found) + { + queue := New[string]() + queue.Enqueue("aa") + queue.Enqueue("bb") + queue.Enqueue("cc") + it := queue.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestQueueSerialization(t *testing.T) { + queue := New[string]() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + var err error + assert := func() { + testutils.SameElements(t, queue.Values(), []string{"a", "b", "c"}) + if actualValue, expectedValue := queue.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := queue.ToJSON() + assert() + + err = queue.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", queue}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &queue) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestQueueString(t *testing.T) { + c := New[int]() + c.Enqueue(1) + if !strings.HasPrefix(c.String(), "ArrayQueue") { + t.Errorf("String should start with container name") + } +} + +func benchmarkEnqueue(b *testing.B, queue *Queue[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + } +} + +func benchmarkDequeue(b *testing.B, queue *Queue[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Dequeue() + } + } +} + +func BenchmarkArrayQueueDequeue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := New[int]() + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} diff --git a/queues/arrayqueue/iterator.go b/queues/arrayqueue/iterator.go new file mode 100644 index 00000000..bc684685 --- /dev/null +++ b/queues/arrayqueue/iterator.go @@ -0,0 +1,111 @@ +// Copyright (c) 2021, Aryan Ahadinia. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arrayqueue + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + queue *Queue[T] + index int +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (queue *Queue[T]) Iterator() *Iterator[T] { + return &Iterator[T]{queue: queue, index: -1} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.queue.Size() { + iterator.index++ + } + return iterator.queue.withinRange(iterator.index) +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + if iterator.index >= 0 { + iterator.index-- + } + return iterator.queue.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + value, _ := iterator.queue.list.Get(iterator.index) + return value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.index = iterator.queue.Size() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/queues/arrayqueue/serialization.go b/queues/arrayqueue/serialization.go new file mode 100644 index 00000000..49abb16f --- /dev/null +++ b/queues/arrayqueue/serialization.go @@ -0,0 +1,33 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arrayqueue + +import ( + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Queue[int])(nil) +var _ containers.JSONDeserializer = (*Queue[int])(nil) + +// ToJSON outputs the JSON representation of the queue. +func (queue *Queue[T]) ToJSON() ([]byte, error) { + return queue.list.ToJSON() +} + +// FromJSON populates the queue from the input JSON representation. +func (queue *Queue[T]) FromJSON(data []byte) error { + return queue.list.FromJSON(data) +} + +// UnmarshalJSON @implements json.Unmarshaler +func (queue *Queue[T]) UnmarshalJSON(bytes []byte) error { + return queue.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (queue *Queue[T]) MarshalJSON() ([]byte, error) { + return queue.ToJSON() +} diff --git a/queues/circularbuffer/circularbuffer.go b/queues/circularbuffer/circularbuffer.go new file mode 100644 index 00000000..ef71fdd4 --- /dev/null +++ b/queues/circularbuffer/circularbuffer.go @@ -0,0 +1,148 @@ +// Copyright (c) 2021, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package circularbuffer implements the circular buffer. +// +// In computer science, a circular buffer, circular queue, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. This structure lends itself easily to buffering data streams. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/Circular_buffer +package circularbuffer + +import ( + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/queues" +) + +// Assert Queue implementation +var _ queues.Queue[int] = (*Queue[int])(nil) + +// Queue holds values in a slice. +type Queue[T comparable] struct { + values []T + start int + end int + full bool + maxSize int + size int +} + +// New instantiates a new empty queue with the specified size of maximum number of elements that it can hold. +// This max size of the buffer cannot be changed. +func New[T comparable](maxSize int) *Queue[T] { + if maxSize < 1 { + panic("Invalid maxSize, should be at least 1") + } + queue := &Queue[T]{maxSize: maxSize} + queue.Clear() + return queue +} + +// Enqueue adds a value to the end of the queue +func (queue *Queue[T]) Enqueue(value T) { + if queue.Full() { + queue.Dequeue() + } + queue.values[queue.end] = value + queue.end = queue.end + 1 + if queue.end >= queue.maxSize { + queue.end = 0 + } + if queue.end == queue.start { + queue.full = true + } + + queue.size = queue.calculateSize() +} + +// Dequeue removes first element of the queue and returns it, or the 0-value if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to dequeue. +func (queue *Queue[T]) Dequeue() (value T, ok bool) { + if queue.Empty() { + return value, false + } + + value, ok = queue.values[queue.start], true + queue.start = queue.start + 1 + if queue.start >= queue.maxSize { + queue.start = 0 + } + queue.full = false + queue.size = queue.size - 1 + + return +} + +// Peek returns first element of the queue without removing it, or nil if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to peek. +func (queue *Queue[T]) Peek() (value T, ok bool) { + if queue.Empty() { + return value, false + } + return queue.values[queue.start], true +} + +// Empty returns true if queue does not contain any elements. +func (queue *Queue[T]) Empty() bool { + return queue.Size() == 0 +} + +// Full returns true if the queue is full, i.e. has reached the maximum number of elements that it can hold. +func (queue *Queue[T]) Full() bool { + return queue.Size() == queue.maxSize +} + +// Size returns number of elements within the queue. +func (queue *Queue[T]) Size() int { + return queue.size +} + +// Clear removes all elements from the queue. +func (queue *Queue[T]) Clear() { + queue.values = make([]T, queue.maxSize, queue.maxSize) + queue.start = 0 + queue.end = 0 + queue.full = false + queue.size = 0 +} + +// Values returns all elements in the queue (FIFO order). +func (queue *Queue[T]) Values() []T { + values := make([]T, queue.Size(), queue.Size()) + for i := 0; i < queue.Size(); i++ { + values[i] = queue.values[(queue.start+i)%queue.maxSize] + } + return values +} + +// String returns a string representation of container +func (queue *Queue[T]) String() string { + str := "CircularBuffer\n" + var values []string + for _, value := range queue.Values() { + values = append(values, fmt.Sprintf("%v", value)) + } + str += strings.Join(values, ", ") + return str +} + +// Check that the index is within bounds of the list +func (queue *Queue[T]) withinRange(index int) bool { + return index >= 0 && index < queue.size +} + +func (queue *Queue[T]) calculateSize() int { + if queue.end < queue.start { + return queue.maxSize - queue.start + queue.end + } else if queue.end == queue.start { + if queue.full { + return queue.maxSize + } + return 0 + } + return queue.end - queue.start +} diff --git a/queues/circularbuffer/circularbuffer_test.go b/queues/circularbuffer/circularbuffer_test.go new file mode 100644 index 00000000..d48bb3ae --- /dev/null +++ b/queues/circularbuffer/circularbuffer_test.go @@ -0,0 +1,629 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package circularbuffer + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/emirpasic/gods/v2/testutils" +) + +func TestQueueEnqueue(t *testing.T) { + queue := New[int](3) + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + + if actualValue := queue.Values(); actualValue[0] != 1 || actualValue[1] != 2 || actualValue[2] != 3 { + t.Errorf("Got %v expected %v", actualValue, "[1,2,3]") + } + if actualValue := queue.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := queue.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Peek(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } +} + +func TestQueuePeek(t *testing.T) { + queue := New[int](3) + if actualValue, ok := queue.Peek(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + if actualValue, ok := queue.Peek(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } +} + +func TestQueueDequeue(t *testing.T) { + assert := func(actualValue interface{}, expectedValue interface{}) { + if actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + + queue := New[int](3) + assert(queue.Empty(), true) + assert(queue.Empty(), true) + assert(queue.Full(), false) + assert(queue.Size(), 0) + queue.Enqueue(1) + assert(queue.Size(), 1) + queue.Enqueue(2) + assert(queue.Size(), 2) + + queue.Enqueue(3) + assert(queue.Size(), 3) + assert(queue.Empty(), false) + assert(queue.Full(), true) + + queue.Dequeue() + assert(queue.Size(), 2) + + if actualValue, ok := queue.Peek(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + assert(queue.Size(), 2) + + if actualValue, ok := queue.Dequeue(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + assert(queue.Size(), 1) + + if actualValue, ok := queue.Dequeue(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } + assert(queue.Size(), 0) + assert(queue.Empty(), true) + assert(queue.Full(), false) + + if actualValue, ok := queue.Dequeue(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + assert(queue.Size(), 0) + + assert(queue.Empty(), true) + assert(queue.Full(), false) + assert(len(queue.Values()), 0) +} + +func TestQueueDequeueFull(t *testing.T) { + assert := func(actualValue interface{}, expectedValue interface{}) { + if actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + + queue := New[int](2) + assert(queue.Empty(), true) + assert(queue.Full(), false) + assert(queue.Size(), 0) + + queue.Enqueue(1) + assert(queue.Size(), 1) + + queue.Enqueue(2) + assert(queue.Size(), 2) + assert(queue.Full(), true) + if actualValue, ok := queue.Peek(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + queue.Enqueue(3) // overwrites 1 + assert(queue.Size(), 2) + + if actualValue, ok := queue.Dequeue(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, expectedValue := queue.Size(), 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + if actualValue, ok := queue.Peek(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, expectedValue := queue.Size(), 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + if actualValue, ok := queue.Dequeue(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } + assert(queue.Size(), 0) + + if actualValue, ok := queue.Dequeue(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + assert(queue.Empty(), true) + assert(queue.Full(), false) + assert(len(queue.Values()), 0) +} + +func TestQueueIteratorOnEmpty(t *testing.T) { + queue := New[int](3) + it := queue.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty queue") + } +} + +func TestQueueIteratorNext(t *testing.T) { + queue := New[string](3) + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + it := queue.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + queue.Clear() + it = queue.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty queue") + } +} + +func TestQueueIteratorPrev(t *testing.T) { + queue := New[string](3) + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + it := queue.Iterator() + for it.Next() { + } + count := 0 + for it.Prev() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, 3-count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestQueueIteratorBegin(t *testing.T) { + queue := New[string](3) + it := queue.Iterator() + it.Begin() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestQueueIteratorEnd(t *testing.T) { + queue := New[string](3) + it := queue.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + it.End() + if index := it.Index(); index != queue.Size() { + t.Errorf("Got %v expected %v", index, queue.Size()) + } + + it.Prev() + if index, value := it.Index(), it.Value(); index != queue.Size()-1 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, queue.Size()-1, "c") + } +} + +func TestQueueIteratorFirst(t *testing.T) { + queue := New[string](3) + it := queue.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestQueueIteratorLast(t *testing.T) { + queue := New[string](3) + it := queue.Iterator() + if actualValue, expectedValue := it.Last(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "c") + } +} + +func TestQueueIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + queue := New[string](3) + it := queue.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // NextTo (not found) + { + queue := New[string](3) + queue.Enqueue("xx") + queue.Enqueue("yy") + it := queue.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // NextTo (found) + { + queue := New[string](3) + queue.Enqueue("aa") + queue.Enqueue("bb") + queue.Enqueue("cc") + it := queue.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestQueueIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + queue := New[string](3) + it := queue.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // PrevTo (not found) + { + queue := New[string](3) + queue.Enqueue("xx") + queue.Enqueue("yy") + it := queue.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // PrevTo (found) + { + queue := New[string](3) + queue.Enqueue("aa") + queue.Enqueue("bb") + queue.Enqueue("cc") + it := queue.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestQueueIterator(t *testing.T) { + assert := func(actualValue interface{}, expectedValue interface{}) { + if actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + + queue := New[string](2) + + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") // overwrites "a" + + it := queue.Iterator() + + if actualIndex, expectedIndex := it.Index(), -1; actualIndex != expectedIndex { + t.Errorf("Got %v expected %v", actualIndex, expectedIndex) + } + + assert(it.Next(), true) + + if actualValue, actualIndex, expectedValue, expectedIndex := it.Value(), it.Index(), "b", 0; actualValue != expectedValue || actualIndex != expectedIndex { + t.Errorf("Got %v expected %v, Got %v expected %v", actualValue, expectedValue, actualIndex, expectedIndex) + } + + assert(it.Next(), true) + + if actualValue, actualIndex, expectedValue, expectedIndex := it.Value(), it.Index(), "c", 1; actualValue != expectedValue || actualIndex != expectedIndex { + t.Errorf("Got %v expected %v, Got %v expected %v", actualValue, expectedValue, actualIndex, expectedIndex) + } + + assert(it.Next(), false) + + if actualIndex, expectedIndex := it.Index(), 2; actualIndex != expectedIndex { + t.Errorf("Got %v expected %v", actualIndex, expectedIndex) + } + + assert(it.Next(), false) + + assert(it.Prev(), true) + + if actualValue, actualIndex, expectedValue, expectedIndex := it.Value(), it.Index(), "c", 1; actualValue != expectedValue || actualIndex != expectedIndex { + t.Errorf("Got %v expected %v, Got %v expected %v", actualValue, expectedValue, actualIndex, expectedIndex) + } + + assert(it.Prev(), true) + + if actualValue, actualIndex, expectedValue, expectedIndex := it.Value(), it.Index(), "b", 0; actualValue != expectedValue || actualIndex != expectedIndex { + t.Errorf("Got %v expected %v, Got %v expected %v", actualValue, expectedValue, actualIndex, expectedIndex) + } + + assert(it.Prev(), false) + + if actualIndex, expectedIndex := it.Index(), -1; actualIndex != expectedIndex { + t.Errorf("Got %v expected %v", actualIndex, expectedIndex) + } +} + +func TestQueueSerialization(t *testing.T) { + queue := New[string](3) + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + var err error + assert := func() { + testutils.SameElements(t, queue.Values(), []string{"a", "b", "c"}) + if actualValue, expectedValue := queue.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := queue.ToJSON() + assert() + + err = queue.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", queue}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &queue) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestQueueString(t *testing.T) { + c := New[int](3) + c.Enqueue(1) + if !strings.HasPrefix(c.String(), "CircularBuffer") { + t.Errorf("String should start with container name") + } +} + +func benchmarkEnqueue(b *testing.B, queue *Queue[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + } +} + +func benchmarkDequeue(b *testing.B, queue *Queue[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Dequeue() + } + } +} + +func BenchmarkArrayQueueDequeue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := New[int](3) + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := New[int](3) + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := New[int](3) + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := New[int](3) + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := New[int](3) + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := New[int](3) + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := New[int](3) + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := New[int](3) + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} diff --git a/queues/circularbuffer/iterator.go b/queues/circularbuffer/iterator.go new file mode 100644 index 00000000..be6af87d --- /dev/null +++ b/queues/circularbuffer/iterator.go @@ -0,0 +1,112 @@ +// Copyright (c) 2021, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package circularbuffer + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + queue *Queue[T] + index int +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (queue *Queue[T]) Iterator() *Iterator[T] { + return &Iterator[T]{queue: queue, index: -1} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.queue.size { + iterator.index++ + } + return iterator.queue.withinRange(iterator.index) +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + if iterator.index >= 0 { + iterator.index-- + } + return iterator.queue.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + index := (iterator.index + iterator.queue.start) % iterator.queue.maxSize + value := iterator.queue.values[index] + return value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.index = iterator.queue.size +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/queues/circularbuffer/serialization.go b/queues/circularbuffer/serialization.go new file mode 100644 index 00000000..a020bac0 --- /dev/null +++ b/queues/circularbuffer/serialization.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package circularbuffer + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Queue[int])(nil) +var _ containers.JSONDeserializer = (*Queue[int])(nil) + +// ToJSON outputs the JSON representation of queue's elements. +func (queue *Queue[T]) ToJSON() ([]byte, error) { + return json.Marshal(queue.values[:queue.maxSize]) +} + +// FromJSON populates list's elements from the input JSON representation. +func (queue *Queue[T]) FromJSON(data []byte) error { + var values []T + err := json.Unmarshal(data, &values) + if err == nil { + for _, value := range values { + queue.Enqueue(value) + } + } + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (queue *Queue[T]) UnmarshalJSON(bytes []byte) error { + return queue.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (queue *Queue[T]) MarshalJSON() ([]byte, error) { + return queue.ToJSON() +} diff --git a/queues/linkedlistqueue/iterator.go b/queues/linkedlistqueue/iterator.go new file mode 100644 index 00000000..29bfffda --- /dev/null +++ b/queues/linkedlistqueue/iterator.go @@ -0,0 +1,73 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedlistqueue + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.IteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + queue *Queue[T] + index int +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (queue *Queue[T]) Iterator() *Iterator[T] { + return &Iterator[T]{queue: queue, index: -1} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.queue.Size() { + iterator.index++ + } + return iterator.queue.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + value, _ := iterator.queue.list.Get(iterator.index) + return value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/queues/linkedlistqueue/linkedlistqueue.go b/queues/linkedlistqueue/linkedlistqueue.go new file mode 100644 index 00000000..32ad9f32 --- /dev/null +++ b/queues/linkedlistqueue/linkedlistqueue.go @@ -0,0 +1,88 @@ +// Copyright (c) 2021, Aryan Ahadinia. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package linkedlistqueue implements a queue backed by a singly-linked list. +// +// Structure is not thread safe. +// +// Reference: https://en.wikipedia.org/wiki/Queue_(abstract_data_type) +package linkedlistqueue + +import ( + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/lists/singlylinkedlist" + "github.com/emirpasic/gods/v2/queues" +) + +// Assert Queue implementation +var _ queues.Queue[int] = (*Queue[int])(nil) + +// Queue holds elements in a singly-linked-list +type Queue[T comparable] struct { + list *singlylinkedlist.List[T] +} + +// New instantiates a new empty queue +func New[T comparable]() *Queue[T] { + return &Queue[T]{list: singlylinkedlist.New[T]()} +} + +// Enqueue adds a value to the end of the queue +func (queue *Queue[T]) Enqueue(value T) { + queue.list.Add(value) +} + +// Dequeue removes first element of the queue and returns it, or nil if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to dequeue. +func (queue *Queue[T]) Dequeue() (value T, ok bool) { + value, ok = queue.list.Get(0) + if ok { + queue.list.Remove(0) + } + return +} + +// Peek returns first element of the queue without removing it, or nil if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to peek. +func (queue *Queue[T]) Peek() (value T, ok bool) { + return queue.list.Get(0) +} + +// Empty returns true if queue does not contain any elements. +func (queue *Queue[T]) Empty() bool { + return queue.list.Empty() +} + +// Size returns number of elements within the queue. +func (queue *Queue[T]) Size() int { + return queue.list.Size() +} + +// Clear removes all elements from the queue. +func (queue *Queue[T]) Clear() { + queue.list.Clear() +} + +// Values returns all elements in the queue (FIFO order). +func (queue *Queue[T]) Values() []T { + return queue.list.Values() +} + +// String returns a string representation of container +func (queue *Queue[T]) String() string { + str := "LinkedListQueue\n" + values := []string{} + for _, value := range queue.list.Values() { + values = append(values, fmt.Sprintf("%v", value)) + } + str += strings.Join(values, ", ") + return str +} + +// Check that the index is within bounds of the list +func (queue *Queue[T]) withinRange(index int) bool { + return index >= 0 && index < queue.list.Size() +} diff --git a/queues/linkedlistqueue/linkedlistqueue_test.go b/queues/linkedlistqueue/linkedlistqueue_test.go new file mode 100644 index 00000000..60ae2f75 --- /dev/null +++ b/queues/linkedlistqueue/linkedlistqueue_test.go @@ -0,0 +1,357 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedlistqueue + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/emirpasic/gods/v2/testutils" +) + +func TestQueueEnqueue(t *testing.T) { + queue := New[int]() + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + + if actualValue := queue.Values(); actualValue[0] != 1 || actualValue[1] != 2 || actualValue[2] != 3 { + t.Errorf("Got %v expected %v", actualValue, "[1,2,3]") + } + if actualValue := queue.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := queue.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Peek(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } +} + +func TestQueuePeek(t *testing.T) { + queue := New[int]() + if actualValue, ok := queue.Peek(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + if actualValue, ok := queue.Peek(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } +} + +func TestQueueDequeue(t *testing.T) { + queue := New[int]() + queue.Enqueue(1) + queue.Enqueue(2) + queue.Enqueue(3) + queue.Dequeue() + if actualValue, ok := queue.Peek(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, ok := queue.Dequeue(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, ok := queue.Dequeue(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Dequeue(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := queue.Values(); len(actualValue) != 0 { + t.Errorf("Got %v expected %v", actualValue, "[]") + } +} + +func TestQueueIteratorOnEmpty(t *testing.T) { + queue := New[int]() + it := queue.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty queue") + } +} + +func TestQueueIteratorNext(t *testing.T) { + queue := New[string]() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + it := queue.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + queue.Clear() + it = queue.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty queue") + } +} + +func TestQueueIteratorBegin(t *testing.T) { + queue := New[string]() + it := queue.Iterator() + it.Begin() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestQueueIteratorFirst(t *testing.T) { + queue := New[string]() + it := queue.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestQueueIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + queue := New[string]() + it := queue.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // NextTo (not found) + { + queue := New[string]() + queue.Enqueue("xx") + queue.Enqueue("yy") + it := queue.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + } + + // NextTo (found) + { + queue := New[string]() + queue.Enqueue("aa") + queue.Enqueue("bb") + queue.Enqueue("cc") + it := queue.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty queue") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestQueueSerialization(t *testing.T) { + queue := New[string]() + queue.Enqueue("a") + queue.Enqueue("b") + queue.Enqueue("c") + + var err error + assert := func() { + testutils.SameElements(t, queue.Values(), []string{"a", "b", "c"}) + if actualValue, expectedValue := queue.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := queue.ToJSON() + assert() + + err = queue.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", queue}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &queue) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestQueueString(t *testing.T) { + c := New[int]() + c.Enqueue(1) + if !strings.HasPrefix(c.String(), "LinkedListQueue") { + t.Errorf("String should start with container name") + } +} + +func benchmarkEnqueue(b *testing.B, queue *Queue[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + } +} + +func benchmarkDequeue(b *testing.B, queue *Queue[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Dequeue() + } + } +} + +func BenchmarkArrayQueueDequeue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueDequeue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := New[int]() + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkArrayQueueEnqueue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := New[int]() + for n := 0; n < size; n++ { + queue.Enqueue(n) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} diff --git a/queues/linkedlistqueue/serialization.go b/queues/linkedlistqueue/serialization.go new file mode 100644 index 00000000..8e9c157d --- /dev/null +++ b/queues/linkedlistqueue/serialization.go @@ -0,0 +1,33 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedlistqueue + +import ( + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Queue[int])(nil) +var _ containers.JSONDeserializer = (*Queue[int])(nil) + +// ToJSON outputs the JSON representation of the queue. +func (queue *Queue[T]) ToJSON() ([]byte, error) { + return queue.list.ToJSON() +} + +// FromJSON populates the queue from the input JSON representation. +func (queue *Queue[T]) FromJSON(data []byte) error { + return queue.list.FromJSON(data) +} + +// UnmarshalJSON @implements json.Unmarshaler +func (queue *Queue[T]) UnmarshalJSON(bytes []byte) error { + return queue.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (queue *Queue[T]) MarshalJSON() ([]byte, error) { + return queue.ToJSON() +} diff --git a/queues/priorityqueue/iterator.go b/queues/priorityqueue/iterator.go new file mode 100644 index 00000000..1a55e07f --- /dev/null +++ b/queues/priorityqueue/iterator.go @@ -0,0 +1,92 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package priorityqueue + +import ( + "github.com/emirpasic/gods/v2/containers" + "github.com/emirpasic/gods/v2/trees/binaryheap" +) + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + iterator *binaryheap.Iterator[T] +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (queue *Queue[T]) Iterator() *Iterator[T] { + return &Iterator[T]{iterator: queue.heap.Iterator()} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + return iterator.iterator.Next() +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + return iterator.iterator.Prev() +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + return iterator.iterator.Value() +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.iterator.Index() +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.iterator.Begin() +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.iterator.End() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + return iterator.iterator.First() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + return iterator.iterator.Last() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + return iterator.iterator.NextTo(f) +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + return iterator.iterator.PrevTo(f) +} diff --git a/queues/priorityqueue/priorityqueue.go b/queues/priorityqueue/priorityqueue.go new file mode 100644 index 00000000..4427f75e --- /dev/null +++ b/queues/priorityqueue/priorityqueue.go @@ -0,0 +1,92 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package priorityqueue implements a priority queue backed by binary queue. +// +// An unbounded priority queue based on a priority queue. +// The elements of the priority queue are ordered by a comparator provided at queue construction time. +// +// The heap of this queue is the least/smallest element with respect to the specified ordering. +// If multiple elements are tied for least value, the heap is one of those elements arbitrarily. +// +// Structure is not thread safe. +// +// References: https://en.wikipedia.org/wiki/Priority_queue +package priorityqueue + +import ( + "cmp" + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/queues" + "github.com/emirpasic/gods/v2/trees/binaryheap" + "github.com/emirpasic/gods/v2/utils" +) + +// Assert Queue implementation +var _ queues.Queue[int] = (*Queue[int])(nil) + +// Queue holds elements in an array-list +type Queue[T comparable] struct { + heap *binaryheap.Heap[T] + Comparator utils.Comparator[T] +} + +func New[T cmp.Ordered]() *Queue[T] { + return NewWith[T](cmp.Compare[T]) +} + +// NewWith instantiates a new empty queue with the custom comparator. +func NewWith[T comparable](comparator utils.Comparator[T]) *Queue[T] { + return &Queue[T]{heap: binaryheap.NewWith(comparator), Comparator: comparator} +} + +// Enqueue adds a value to the end of the queue +func (queue *Queue[T]) Enqueue(value T) { + queue.heap.Push(value) +} + +// Dequeue removes first element of the queue and returns it, or nil if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to dequeue. +func (queue *Queue[T]) Dequeue() (value T, ok bool) { + return queue.heap.Pop() +} + +// Peek returns top element on the queue without removing it, or nil if queue is empty. +// Second return parameter is true, unless the queue was empty and there was nothing to peek. +func (queue *Queue[T]) Peek() (value T, ok bool) { + return queue.heap.Peek() +} + +// Empty returns true if queue does not contain any elements. +func (queue *Queue[T]) Empty() bool { + return queue.heap.Empty() +} + +// Size returns number of elements within the queue. +func (queue *Queue[T]) Size() int { + return queue.heap.Size() +} + +// Clear removes all elements from the queue. +func (queue *Queue[T]) Clear() { + queue.heap.Clear() +} + +// Values returns all elements in the queue. +func (queue *Queue[T]) Values() []T { + return queue.heap.Values() +} + +// String returns a string representation of container +func (queue *Queue[T]) String() string { + str := "PriorityQueue\n" + values := make([]string, queue.heap.Size(), queue.heap.Size()) + for index, value := range queue.heap.Values() { + values[index] = fmt.Sprintf("%v", value) + } + str += strings.Join(values, ", ") + return str +} diff --git a/queues/priorityqueue/priorityqueue_test.go b/queues/priorityqueue/priorityqueue_test.go new file mode 100644 index 00000000..1a21e571 --- /dev/null +++ b/queues/priorityqueue/priorityqueue_test.go @@ -0,0 +1,573 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package priorityqueue + +import ( + "cmp" + "encoding/json" + "fmt" + "math/rand" + "strings" + "testing" +) + +type Element struct { + priority int + name string +} + +func (element Element) String() string { + return fmt.Sprintf("{%v %v}", element.priority, element.name) +} + +// Comparator function (sort by priority value in descending order) +func byPriority(a, b Element) int { + return -cmp.Compare(a.priority, b.priority) // Note "-" for descending order +} + +func TestBinaryQueueEnqueue(t *testing.T) { + queue := NewWith[Element](byPriority) + + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + + a := Element{name: "a", priority: 1} + c := Element{name: "c", priority: 3} + b := Element{name: "b", priority: 2} + + queue.Enqueue(a) + queue.Enqueue(c) + queue.Enqueue(b) + + it := queue.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value.name, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value.name, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value.name, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + + if actualValue := queue.Values(); actualValue[0].name != "c" || actualValue[1].name != "b" || actualValue[2].name != "a" { + t.Errorf("Got %v expected %v", actualValue, `[{3 c} {2 b} {1 a}]`) + } +} + +func TestBinaryQueueEnqueueBulk(t *testing.T) { + queue := New[int]() + + queue.Enqueue(15) + queue.Enqueue(20) + queue.Enqueue(3) + queue.Enqueue(1) + queue.Enqueue(2) + + if actualValue, ok := queue.Dequeue(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } + if actualValue, ok := queue.Dequeue(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, ok := queue.Dequeue(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Dequeue(); actualValue != 15 || !ok { + t.Errorf("Got %v expected %v", actualValue, 15) + } + if actualValue, ok := queue.Dequeue(); actualValue != 20 || !ok { + t.Errorf("Got %v expected %v", actualValue, 20) + } + + queue.Clear() + if actualValue := queue.Empty(); !actualValue { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestBinaryQueueDequeue(t *testing.T) { + queue := New[int]() + + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + + queue.Enqueue(3) + queue.Enqueue(2) + queue.Enqueue(1) + queue.Dequeue() // removes 1 + + if actualValue, ok := queue.Dequeue(); actualValue != 2 || !ok { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue, ok := queue.Dequeue(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Dequeue(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue := queue.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := queue.Values(); len(actualValue) != 0 { + t.Errorf("Got %v expected %v", actualValue, "[]") + } +} + +func TestBinaryQueueRandom(t *testing.T) { + queue := New[int]() + + rand.Seed(3) + for i := 0; i < 10000; i++ { + r := int(rand.Int31n(30)) + queue.Enqueue(r) + } + + prev, _ := queue.Dequeue() + for !queue.Empty() { + curr, _ := queue.Dequeue() + if prev > curr { + t.Errorf("Queue property invalidated. prev: %v current: %v", prev, curr) + } + prev = curr + } +} + +func TestBinaryQueueIteratorOnEmpty(t *testing.T) { + queue := New[int]() + it := queue.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty queue") + } +} + +func TestBinaryQueueIteratorNext(t *testing.T) { + queue := New[int]() + queue.Enqueue(3) + queue.Enqueue(2) + queue.Enqueue(1) + + it := queue.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBinaryQueueIteratorPrev(t *testing.T) { + queue := New[int]() + queue.Enqueue(3) + queue.Enqueue(2) + queue.Enqueue(1) + + it := queue.Iterator() + for it.Next() { + } + count := 0 + for it.Prev() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, 3-count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBinaryQueueIteratorBegin(t *testing.T) { + queue := New[int]() + it := queue.Iterator() + it.Begin() + queue.Enqueue(2) + queue.Enqueue(3) + queue.Enqueue(1) + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != 1 { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, 1) + } +} + +func TestBinaryQueueIteratorEnd(t *testing.T) { + queue := New[int]() + it := queue.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + queue.Enqueue(3) + queue.Enqueue(2) + queue.Enqueue(1) + it.End() + if index := it.Index(); index != queue.Size() { + t.Errorf("Got %v expected %v", index, queue.Size()) + } + + it.Prev() + if index, value := it.Index(), it.Value(); index != queue.Size()-1 || value != 3 { + t.Errorf("Got %v,%v expected %v,%v", index, value, queue.Size()-1, 3) + } +} + +func TestBinaryQueueIteratorFirst(t *testing.T) { + queue := New[int]() + it := queue.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + queue.Enqueue(3) // [3] + queue.Enqueue(2) // [2,3] + queue.Enqueue(1) // [1,3,2](2 swapped with 1, hence last) + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != 1 { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, 1) + } +} + +func TestBinaryQueueIteratorLast(t *testing.T) { + tree := New[int]() + it := tree.Iterator() + if actualValue, expectedValue := it.Last(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + tree.Enqueue(2) + tree.Enqueue(3) + tree.Enqueue(1) + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != 3 { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, 3) + } +} + +func TestBinaryQueueIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + tree := New[string]() + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // NextTo (not found) + { + tree := New[string]() + tree.Enqueue("xx") + tree.Enqueue("yy") + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // NextTo (found) + { + tree := New[string]() + tree.Enqueue("aa") + tree.Enqueue("bb") + tree.Enqueue("cc") + it := tree.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestBinaryQueueIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + tree := New[string]() + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // PrevTo (not found) + { + tree := New[string]() + tree.Enqueue("xx") + tree.Enqueue("yy") + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // PrevTo (found) + { + tree := New[string]() + tree.Enqueue("aa") + tree.Enqueue("bb") + tree.Enqueue("cc") + it := tree.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestBinaryQueueSerialization(t *testing.T) { + queue := New[string]() + + queue.Enqueue("c") + queue.Enqueue("b") + queue.Enqueue("a") + + var err error + assert := func() { + if actualValue := queue.Values(); actualValue[0] != "a" || actualValue[1] != "b" || actualValue[2] != "c" { + t.Errorf("Got %v expected %v", actualValue, "[1,3,2]") + } + if actualValue := queue.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := queue.Peek(); actualValue != "a" || !ok { + t.Errorf("Got %v expected %v", actualValue, "a") + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := queue.ToJSON() + assert() + + err = queue.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", queue}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &queue) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestBTreeString(t *testing.T) { + c := New[int]() + c.Enqueue(1) + if !strings.HasPrefix(c.String(), "PriorityQueue") { + t.Errorf("String should start with container name") + } +} + +func benchmarkEnqueue(b *testing.B, queue *Queue[Element], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + } +} + +func benchmarkDequeue(b *testing.B, queue *Queue[Element], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + queue.Dequeue() + } + } +} + +func BenchmarkBinaryQueueDequeue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := NewWith[Element](byPriority) + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkBinaryQueueDequeue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := NewWith[Element](byPriority) + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkBinaryQueueDequeue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := NewWith[Element](byPriority) + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkBinaryQueueDequeue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := NewWith[Element](byPriority) + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + b.StartTimer() + benchmarkDequeue(b, queue, size) +} + +func BenchmarkBinaryQueueEnqueue100(b *testing.B) { + b.StopTimer() + size := 100 + queue := NewWith(byPriority) + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkBinaryQueueEnqueue1000(b *testing.B) { + b.StopTimer() + size := 1000 + queue := NewWith[Element](byPriority) + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkBinaryQueueEnqueue10000(b *testing.B) { + b.StopTimer() + size := 10000 + queue := NewWith[Element](byPriority) + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} + +func BenchmarkBinaryQueueEnqueue100000(b *testing.B) { + b.StopTimer() + size := 100000 + queue := NewWith[Element](byPriority) + for n := 0; n < size; n++ { + queue.Enqueue(Element{}) + } + b.StartTimer() + benchmarkEnqueue(b, queue, size) +} diff --git a/queues/priorityqueue/serialization.go b/queues/priorityqueue/serialization.go new file mode 100644 index 00000000..f22548df --- /dev/null +++ b/queues/priorityqueue/serialization.go @@ -0,0 +1,33 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package priorityqueue + +import ( + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Queue[int])(nil) +var _ containers.JSONDeserializer = (*Queue[int])(nil) + +// ToJSON outputs the JSON representation of the queue. +func (queue *Queue[T]) ToJSON() ([]byte, error) { + return queue.heap.ToJSON() +} + +// FromJSON populates the queue from the input JSON representation. +func (queue *Queue[T]) FromJSON(data []byte) error { + return queue.heap.FromJSON(data) +} + +// UnmarshalJSON @implements json.Unmarshaler +func (queue *Queue[T]) UnmarshalJSON(bytes []byte) error { + return queue.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (queue *Queue[T]) MarshalJSON() ([]byte, error) { + return queue.ToJSON() +} diff --git a/queues/queues.go b/queues/queues.go new file mode 100644 index 00000000..6f2deb43 --- /dev/null +++ b/queues/queues.go @@ -0,0 +1,27 @@ +// Copyright (c) 2021, Aryan Ahadinia. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package queues provides an abstract Queue interface. +// +// In computer science, a queue is a collection of entities that are maintained in a sequence and can be modified by the addition of entities at one end of the sequence and the removal of entities from the other end of the sequence. By convention, the end of the sequence at which elements are added is called the back, tail, or rear of the queue, and the end at which elements are removed is called the head or front of the queue, analogously to the words used when people line up to wait for goods or services. +// The operation of adding an element to the rear of the queue is known as enqueue, and the operation of removing an element from the front is known as dequeue. Other operations may also be allowed, often including a peek or front operation that returns the value of the next element to be dequeued without remove it. +// +// Reference: https://en.wikipedia.org/wiki/Queue_(abstract_data_type) +package queues + +import "github.com/emirpasic/gods/v2/containers" + +// Queue interface that all queues implement +type Queue[T comparable] interface { + Enqueue(value T) + Dequeue() (value T, ok bool) + Peek() (value T, ok bool) + + containers.Container[T] + // Empty() bool + // Size() int + // Clear() + // Values() []interface{} + // String() string +} diff --git a/sets/hashset/hashset.go b/sets/hashset/hashset.go index dfd1069f..32523abf 100644 --- a/sets/hashset/hashset.go +++ b/sets/hashset/hashset.go @@ -1,74 +1,58 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of set backed by a hash table. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package hashset implements a set backed by a hash table. +// // Structure is not thread safe. +// // References: http://en.wikipedia.org/wiki/Set_%28abstract_data_type%29 - package hashset import ( "fmt" - "github.com/emirpasic/gods/sets" "strings" + + "github.com/emirpasic/gods/v2/sets" ) -func assertInterfaceImplementation() { - var _ sets.Interface = (*Set)(nil) -} +// Assert Set implementation +var _ sets.Set[int] = (*Set[int])(nil) -type Set struct { - items map[interface{}]struct{} +// Set holds elements in go's native map +type Set[T comparable] struct { + items map[T]struct{} } var itemExists = struct{}{} -// Instantiates a new empty set -func New() *Set { - return &Set{items: make(map[interface{}]struct{})} +// New instantiates a new empty set and adds the passed values, if any, to the set +func New[T comparable](values ...T) *Set[T] { + set := &Set[T]{items: make(map[T]struct{})} + if len(values) > 0 { + set.Add(values...) + } + return set } -// Adds the items (one or more) to the set. -func (set *Set) Add(items ...interface{}) { +// Add adds the items (one or more) to the set. +func (set *Set[T]) Add(items ...T) { for _, item := range items { set.items[item] = itemExists } } -// Removes the items (one or more) from the set. -func (set *Set) Remove(items ...interface{}) { +// Remove removes the items (one or more) from the set. +func (set *Set[T]) Remove(items ...T) { for _, item := range items { delete(set.items, item) } } -// Check if items (one or more) are present in the set. +// Contains check if items (one or more) are present in the set. // All items have to be present in the set for the method to return true. // Returns true if no arguments are passed at all, i.e. set is always superset of empty set. -func (set *Set) Contains(items ...interface{}) bool { +func (set *Set[T]) Contains(items ...T) bool { for _, item := range items { if _, contains := set.items[item]; !contains { return false @@ -77,38 +61,94 @@ func (set *Set) Contains(items ...interface{}) bool { return true } -// Returns true if set does not contain any elements. -func (set *Set) Empty() bool { +// Empty returns true if set does not contain any elements. +func (set *Set[T]) Empty() bool { return set.Size() == 0 } -// Returns number of elements within the set. -func (set *Set) Size() int { +// Size returns number of elements within the set. +func (set *Set[T]) Size() int { return len(set.items) } -// Clears all values in the set. -func (set *Set) Clear() { - set.items = make(map[interface{}]struct{}) +// Clear clears all values in the set. +func (set *Set[T]) Clear() { + set.items = make(map[T]struct{}) } -// Returns all items in the set. -func (set *Set) Values() []interface{} { - values := make([]interface{}, set.Size()) +// Values returns all items in the set. +func (set *Set[T]) Values() []T { + values := make([]T, set.Size()) count := 0 - for item, _ := range set.items { + for item := range set.items { values[count] = item - count += 1 + count++ } return values } -func (set *Set) String() string { +// String returns a string representation of container +func (set *Set[T]) String() string { str := "HashSet\n" items := []string{} - for k, _ := range set.items { + for k := range set.items { items = append(items, fmt.Sprintf("%v", k)) } str += strings.Join(items, ", ") return str } + +// Intersection returns the intersection between two sets. +// The new set consists of all elements that are both in "set" and "another". +// Ref: https://en.wikipedia.org/wiki/Intersection_(set_theory) +func (set *Set[T]) Intersection(another *Set[T]) *Set[T] { + result := New[T]() + + // Iterate over smaller set (optimization) + if set.Size() <= another.Size() { + for item := range set.items { + if _, contains := another.items[item]; contains { + result.Add(item) + } + } + } else { + for item := range another.items { + if _, contains := set.items[item]; contains { + result.Add(item) + } + } + } + + return result +} + +// Union returns the union of two sets. +// The new set consists of all elements that are in "set" or "another" (possibly both). +// Ref: https://en.wikipedia.org/wiki/Union_(set_theory) +func (set *Set[T]) Union(another *Set[T]) *Set[T] { + result := New[T]() + + for item := range set.items { + result.Add(item) + } + for item := range another.items { + result.Add(item) + } + + return result +} + +// Difference returns the difference between two sets. +// The new set consists of all elements that are in "set" but not in "another". +// Ref: https://proofwiki.org/wiki/Definition:Set_Difference +func (set *Set[T]) Difference(another *Set[T]) *Set[T] { + result := New[T]() + + for item := range set.items { + if _, contains := another.items[item]; !contains { + result.Add(item) + } + } + + return result +} diff --git a/sets/hashset/hashset_test.go b/sets/hashset/hashset_test.go index d38006e0..21d84297 100644 --- a/sets/hashset/hashset_test.go +++ b/sets/hashset/hashset_test.go @@ -1,103 +1,346 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package hashset import ( - "fmt" + "encoding/json" + "strings" "testing" ) -func TestHashSet(t *testing.T) { +func TestSetNew(t *testing.T) { + set := New(2, 1) - set := New() + if actualValue := set.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue := set.Contains(1); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(2); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(3); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, true) + } +} - // insertions +func TestSetAdd(t *testing.T) { + set := New[int]() set.Add() set.Add(1) set.Add(2) set.Add(2, 3) set.Add() - if actualValue := set.Empty(); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } - if actualValue := set.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } +} - // Asking if a set is superset of nothing, thus it's true +func TestSetContains(t *testing.T) { + set := New[int]() + set.Add(3, 1, 2) + set.Add(2, 3) + set.Add() if actualValue := set.Contains(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := set.Contains(1); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := set.Contains(1, 2, 3); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := set.Contains(1, 2, 3, 4); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } +} +func TestSetRemove(t *testing.T) { + set := New[int]() + set.Add(3, 1, 2) set.Remove() + if actualValue := set.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } set.Remove(1) + if actualValue := set.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + set.Remove(3) + set.Remove(3) + set.Remove() + set.Remove(2) + if actualValue := set.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestSetSerialization(t *testing.T) { + set := New[string]() + set.Add("a", "b", "c") - if actualValue, expactedValues := fmt.Sprintf("%d%d", set.Values()...), [2]string{"23", "32"}; actualValue != expactedValues[0] && actualValue != expactedValues[1] { - t.Errorf("Got %v expected %v", actualValue, expactedValues) + var err error + assert := func() { + if actualValue, expectedValue := set.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := set.Contains("a", "b", "c"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if err != nil { + t.Errorf("Got error %v", err) + } } - if actualValue := set.Contains(1); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) + assert() + + bytes, err := set.ToJSON() + assert() + + err = set.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", set}) + if err != nil { + t.Errorf("Got error %v", err) } - set.Remove(1, 2, 3) + err = json.Unmarshal([]byte(`["a","b","c"]`), &set) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} - if actualValue := set.Contains(3); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) +func TestSetString(t *testing.T) { + c := New[int]() + c.Add(1) + if !strings.HasPrefix(c.String(), "HashSet") { + t.Errorf("String should start with container name") + } +} + +func TestSetIntersection(t *testing.T) { + set := New[string]() + another := New[string]() + + intersection := set.Intersection(another) + if actualValue, expectedValue := intersection.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + intersection = set.Intersection(another) + + if actualValue, expectedValue := intersection.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := intersection.Contains("c", "d"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestSetUnion(t *testing.T) { + set := New[string]() + another := New[string]() + + union := set.Union(another) + if actualValue, expectedValue := union.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + union = set.Union(another) + + if actualValue, expectedValue := union.Size(), 6; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := union.Contains("a", "b", "c", "d", "e", "f"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestSetDifference(t *testing.T) { + set := New[string]() + another := New[string]() + + difference := set.Difference(another) + if actualValue, expectedValue := difference.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } - if actualValue := set.Empty(); actualValue != true { + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + difference = set.Difference(another) + + if actualValue, expectedValue := difference.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := difference.Contains("a", "b"); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } +} +func benchmarkContains(b *testing.B, set *Set[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Contains(n) + } + } } -func BenchmarkHashSet(b *testing.B) { +func benchmarkAdd(b *testing.B, set *Set[int], size int) { for i := 0; i < b.N; i++ { - set := New() - for n := 0; n < 1000; n++ { - set.Add(i) + for n := 0; n < size; n++ { + set.Add(n) } - for n := 0; n < 1000; n++ { + } +} + +func benchmarkRemove(b *testing.B, set *Set[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { set.Remove(n) } } } + +func BenchmarkHashSetContains100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetAdd100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetRemove100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} diff --git a/sets/hashset/serialization.go b/sets/hashset/serialization.go new file mode 100644 index 00000000..4b81ce2f --- /dev/null +++ b/sets/hashset/serialization.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hashset + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Set[int])(nil) +var _ containers.JSONDeserializer = (*Set[int])(nil) + +// ToJSON outputs the JSON representation of the set. +func (set *Set[T]) ToJSON() ([]byte, error) { + return json.Marshal(set.Values()) +} + +// FromJSON populates the set from the input JSON representation. +func (set *Set[T]) FromJSON(data []byte) error { + var elements []T + err := json.Unmarshal(data, &elements) + if err == nil { + set.Clear() + set.Add(elements...) + } + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (set *Set[T]) UnmarshalJSON(bytes []byte) error { + return set.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (set *Set[T]) MarshalJSON() ([]byte, error) { + return set.ToJSON() +} diff --git a/sets/linkedhashset/enumerable.go b/sets/linkedhashset/enumerable.go new file mode 100644 index 00000000..2e51edfe --- /dev/null +++ b/sets/linkedhashset/enumerable.go @@ -0,0 +1,79 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashset + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Enumerable implementation +var _ containers.EnumerableWithIndex[int] = (*Set[int])(nil) + +// Each calls the given function once for each element, passing that element's index and value. +func (set *Set[T]) Each(f func(index int, value T)) { + iterator := set.Iterator() + for iterator.Next() { + f(iterator.Index(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a +// container containing the values returned by the given function. +func (set *Set[T]) Map(f func(index int, value T) T) *Set[T] { + newSet := New[T]() + iterator := set.Iterator() + for iterator.Next() { + newSet.Add(f(iterator.Index(), iterator.Value())) + } + return newSet +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (set *Set[T]) Select(f func(index int, value T) bool) *Set[T] { + newSet := New[T]() + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + newSet.Add(iterator.Value()) + } + } + return newSet +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (set *Set[T]) Any(f func(index int, value T) bool) bool { + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (set *Set[T]) All(f func(index int, value T) bool) bool { + iterator := set.Iterator() + for iterator.Next() { + if !f(iterator.Index(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (index,value) for which the function is true or -1,nil otherwise +// if no element matches the criteria. +func (set *Set[T]) Find(f func(index int, value T) bool) (int, T) { + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return iterator.Index(), iterator.Value() + } + } + var t T + return -1, t +} diff --git a/sets/linkedhashset/iterator.go b/sets/linkedhashset/iterator.go new file mode 100644 index 00000000..378ec853 --- /dev/null +++ b/sets/linkedhashset/iterator.go @@ -0,0 +1,104 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashset + +import ( + "github.com/emirpasic/gods/v2/containers" + "github.com/emirpasic/gods/v2/lists/doublylinkedlist" +) + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator holding the iterator's state +type Iterator[T comparable] struct { + iterator doublylinkedlist.Iterator[T] +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (set *Set[T]) Iterator() Iterator[T] { + return Iterator[T]{iterator: set.ordering.Iterator()} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + return iterator.iterator.Next() +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + return iterator.iterator.Prev() +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + return iterator.iterator.Value() +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.iterator.Index() +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.iterator.Begin() +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.iterator.End() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + return iterator.iterator.First() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + return iterator.iterator.Last() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/sets/linkedhashset/linkedhashset.go b/sets/linkedhashset/linkedhashset.go new file mode 100644 index 00000000..ea681a9f --- /dev/null +++ b/sets/linkedhashset/linkedhashset.go @@ -0,0 +1,173 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package linkedhashset is a set that preserves insertion-order. +// +// It is backed by a hash table to store values and doubly-linked list to store ordering. +// +// Note that insertion-order is not affected if an element is re-inserted into the set. +// +// Structure is not thread safe. +// +// References: http://en.wikipedia.org/wiki/Set_%28abstract_data_type%29 +package linkedhashset + +import ( + "fmt" + "strings" + + "github.com/emirpasic/gods/v2/lists/doublylinkedlist" + "github.com/emirpasic/gods/v2/sets" +) + +// Assert Set implementation +var _ sets.Set[int] = (*Set[int])(nil) + +// Set holds elements in go's native map +type Set[T comparable] struct { + table map[T]struct{} + ordering *doublylinkedlist.List[T] +} + +var itemExists = struct{}{} + +// New instantiates a new empty set and adds the passed values, if any, to the set +func New[T comparable](values ...T) *Set[T] { + set := &Set[T]{ + table: make(map[T]struct{}), + ordering: doublylinkedlist.New[T](), + } + if len(values) > 0 { + set.Add(values...) + } + return set +} + +// Add adds the items (one or more) to the set. +// Note that insertion-order is not affected if an element is re-inserted into the set. +func (set *Set[T]) Add(items ...T) { + for _, item := range items { + if _, contains := set.table[item]; !contains { + set.table[item] = itemExists + set.ordering.Append(item) + } + } +} + +// Remove removes the items (one or more) from the set. +// Slow operation, worst-case O(n^2). +func (set *Set[T]) Remove(items ...T) { + for _, item := range items { + if _, contains := set.table[item]; contains { + delete(set.table, item) + index := set.ordering.IndexOf(item) + set.ordering.Remove(index) + } + } +} + +// Contains check if items (one or more) are present in the set. +// All items have to be present in the set for the method to return true. +// Returns true if no arguments are passed at all, i.e. set is always superset of empty set. +func (set *Set[T]) Contains(items ...T) bool { + for _, item := range items { + if _, contains := set.table[item]; !contains { + return false + } + } + return true +} + +// Empty returns true if set does not contain any elements. +func (set *Set[T]) Empty() bool { + return set.Size() == 0 +} + +// Size returns number of elements within the set. +func (set *Set[T]) Size() int { + return set.ordering.Size() +} + +// Clear clears all values in the set. +func (set *Set[T]) Clear() { + set.table = make(map[T]struct{}) + set.ordering.Clear() +} + +// Values returns all items in the set. +func (set *Set[T]) Values() []T { + values := make([]T, set.Size()) + it := set.Iterator() + for it.Next() { + values[it.Index()] = it.Value() + } + return values +} + +// String returns a string representation of container +func (set *Set[T]) String() string { + str := "LinkedHashSet\n" + items := []string{} + it := set.Iterator() + for it.Next() { + items = append(items, fmt.Sprintf("%v", it.Value())) + } + str += strings.Join(items, ", ") + return str +} + +// Intersection returns the intersection between two sets. +// The new set consists of all elements that are both in "set" and "another". +// Ref: https://en.wikipedia.org/wiki/Intersection_(set_theory) +func (set *Set[T]) Intersection(another *Set[T]) *Set[T] { + result := New[T]() + + // Iterate over smaller set (optimization) + if set.Size() <= another.Size() { + for item := range set.table { + if _, contains := another.table[item]; contains { + result.Add(item) + } + } + } else { + for item := range another.table { + if _, contains := set.table[item]; contains { + result.Add(item) + } + } + } + + return result +} + +// Union returns the union of two sets. +// The new set consists of all elements that are in "set" or "another" (possibly both). +// Ref: https://en.wikipedia.org/wiki/Union_(set_theory) +func (set *Set[T]) Union(another *Set[T]) *Set[T] { + result := New[T]() + + for item := range set.table { + result.Add(item) + } + for item := range another.table { + result.Add(item) + } + + return result +} + +// Difference returns the difference between two sets. +// The new set consists of all elements that are in "set" but not in "another". +// Ref: https://proofwiki.org/wiki/Definition:Set_Difference +func (set *Set[T]) Difference(another *Set[T]) *Set[T] { + result := New[T]() + + for item := range set.table { + if _, contains := another.table[item]; !contains { + result.Add(item) + } + } + + return result +} diff --git a/sets/linkedhashset/linkedhashset_test.go b/sets/linkedhashset/linkedhashset_test.go new file mode 100644 index 00000000..a3a8fac4 --- /dev/null +++ b/sets/linkedhashset/linkedhashset_test.go @@ -0,0 +1,702 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashset + +import ( + "encoding/json" + "fmt" + "strings" + "testing" +) + +func TestSetNew(t *testing.T) { + set := New(2, 1) + + if actualValue := set.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + if actualValue := set.Contains(1); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(2); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(3); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestSetAdd(t *testing.T) { + set := New[int]() + set.Add() + set.Add(1) + set.Add(2) + set.Add(2, 3) + set.Add() + if actualValue := set.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := set.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } +} + +func TestSetContains(t *testing.T) { + set := New[int]() + set.Add(3, 1, 2) + set.Add(2, 3) + set.Add() + if actualValue := set.Contains(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(1); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(1, 2, 3); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(1, 2, 3, 4); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } +} + +func TestSetRemove(t *testing.T) { + set := New[int]() + set.Add(3, 1, 2) + set.Remove() + if actualValue := set.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + set.Remove(1) + if actualValue := set.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + set.Remove(3) + set.Remove(3) + set.Remove() + set.Remove(2) + if actualValue := set.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestSetEach(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + set.Each(func(index int, value string) { + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} + +func TestSetMap(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + mappedSet := set.Map(func(index int, value string) string { + return "mapped: " + value + }) + if actualValue, expectedValue := mappedSet.Contains("mapped: c", "mapped: b", "mapped: a"), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := mappedSet.Contains("mapped: c", "mapped: b", "mapped: x"), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if mappedSet.Size() != 3 { + t.Errorf("Got %v expected %v", mappedSet.Size(), 3) + } +} + +func TestSetSelect(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + selectedSet := set.Select(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if actualValue, expectedValue := selectedSet.Contains("a", "b"), true; actualValue != expectedValue { + fmt.Println("A: ", selectedSet.Contains("b")) + t.Errorf("Got %v (%v) expected %v (%v)", actualValue, selectedSet.Values(), expectedValue, "[a b]") + } + if actualValue, expectedValue := selectedSet.Contains("a", "b", "c"), false; actualValue != expectedValue { + t.Errorf("Got %v (%v) expected %v (%v)", actualValue, selectedSet.Values(), expectedValue, "[a b]") + } + if selectedSet.Size() != 2 { + t.Errorf("Got %v expected %v", selectedSet.Size(), 3) + } +} + +func TestSetAny(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + any := set.Any(func(index int, value string) bool { + return value == "c" + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = set.Any(func(index int, value string) bool { + return value == "x" + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} + +func TestSetAll(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + all := set.All(func(index int, value string) bool { + return value >= "a" && value <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = set.All(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} + +func TestSetFind(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + foundIndex, foundValue := set.Find(func(index int, value string) bool { + return value == "c" + }) + if foundValue != "c" || foundIndex != 0 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, "c", 0) + } + foundIndex, foundValue = set.Find(func(index int, value string) bool { + return value == "x" + }) + if foundValue != "" || foundIndex != -1 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, nil, nil) + } +} + +func TestSetChaining(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") +} + +func TestSetIteratorPrevOnEmpty(t *testing.T) { + set := New[string]() + it := set.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty set") + } +} + +func TestSetIteratorNext(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + it := set.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestSetIteratorPrev(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + it := set.Iterator() + for it.Prev() { + } + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestSetIteratorBegin(t *testing.T) { + set := New[string]() + it := set.Iterator() + it.Begin() + set.Add("a", "b", "c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestSetIteratorEnd(t *testing.T) { + set := New[string]() + it := set.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + set.Add("a", "b", "c") + it.End() + if index := it.Index(); index != set.Size() { + t.Errorf("Got %v expected %v", index, set.Size()) + } + + it.Prev() + if index, value := it.Index(), it.Value(); index != set.Size()-1 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, set.Size()-1, "c") + } +} + +func TestSetIteratorFirst(t *testing.T) { + set := New[string]() + set.Add("a", "b", "c") + it := set.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestSetIteratorLast(t *testing.T) { + set := New[string]() + set.Add("a", "b", "c") + it := set.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 3, "c") + } +} + +func TestSetIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + set := New[string]() + it := set.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // NextTo (not found) + { + set := New[string]() + set.Add("xx", "yy") + it := set.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // NextTo (found) + { + set := New[string]() + set.Add("aa", "bb", "cc") + it := set.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestSetIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + set := New[string]() + it := set.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // PrevTo (not found) + { + set := New[string]() + set.Add("xx", "yy") + it := set.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // PrevTo (found) + { + set := New[string]() + set.Add("aa", "bb", "cc") + it := set.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestSetSerialization(t *testing.T) { + set := New[string]() + set.Add("a", "b", "c") + + var err error + assert := func() { + if actualValue, expectedValue := set.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := set.Contains("a", "b", "c"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := set.ToJSON() + assert() + + err = set.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", set}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &set) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestSetString(t *testing.T) { + c := New[int]() + c.Add(1) + if !strings.HasPrefix(c.String(), "LinkedHashSet") { + t.Errorf("String should start with container name") + } +} + +func TestSetIntersection(t *testing.T) { + set := New[string]() + another := New[string]() + + intersection := set.Intersection(another) + if actualValue, expectedValue := intersection.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + intersection = set.Intersection(another) + + if actualValue, expectedValue := intersection.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := intersection.Contains("c", "d"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestSetUnion(t *testing.T) { + set := New[string]() + another := New[string]() + + union := set.Union(another) + if actualValue, expectedValue := union.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + union = set.Union(another) + + if actualValue, expectedValue := union.Size(), 6; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := union.Contains("a", "b", "c", "d", "e", "f"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func TestSetDifference(t *testing.T) { + set := New[string]() + another := New[string]() + + difference := set.Difference(another) + if actualValue, expectedValue := difference.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + difference = set.Difference(another) + + if actualValue, expectedValue := difference.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := difference.Contains("a", "b"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func benchmarkContains(b *testing.B, set *Set[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Contains(n) + } + } +} + +func benchmarkAdd(b *testing.B, set *Set[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Add(n) + } + } +} + +func benchmarkRemove(b *testing.B, set *Set[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Remove(n) + } + } +} + +func BenchmarkHashSetContains100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetAdd100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetRemove100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} diff --git a/sets/linkedhashset/serialization.go b/sets/linkedhashset/serialization.go new file mode 100644 index 00000000..c29e1cbd --- /dev/null +++ b/sets/linkedhashset/serialization.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedhashset + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Set[int])(nil) +var _ containers.JSONDeserializer = (*Set[int])(nil) + +// ToJSON outputs the JSON representation of the set. +func (set *Set[T]) ToJSON() ([]byte, error) { + return json.Marshal(set.Values()) +} + +// FromJSON populates the set from the input JSON representation. +func (set *Set[T]) FromJSON(data []byte) error { + var elements []T + err := json.Unmarshal(data, &elements) + if err == nil { + set.Clear() + set.Add(elements...) + } + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (set *Set[T]) UnmarshalJSON(bytes []byte) error { + return set.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (set *Set[T]) MarshalJSON() ([]byte, error) { + return set.ToJSON() +} diff --git a/sets/sets.go b/sets/sets.go index 2a6e2dc1..e63bb5fe 100644 --- a/sets/sets.go +++ b/sets/sets.go @@ -1,33 +1,28 @@ -/* -Copyright (c) Emir Pasic, All rights reserved. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library. See the file LICENSE included -with this distribution for more information. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// Package sets provides an abstract Set interface. +// +// In computer science, a set is an abstract data type that can store certain values and no repeated values. It is a computer implementation of the mathematical concept of a finite set. Unlike most other collection types, rather than retrieving a specific element from a set, one typically tests a value for membership in a set. +// +// Reference: https://en.wikipedia.org/wiki/Set_%28abstract_data_type%29 package sets -import "github.com/emirpasic/gods/containers" +import ( + "github.com/emirpasic/gods/v2/containers" +) -type Interface interface { - Add(elements ...interface{}) - Remove(elements ...interface{}) - Contains(elements ...interface{}) bool +// Set interface that all sets implement +type Set[T comparable] interface { + Add(elements ...T) + Remove(elements ...T) + Contains(elements ...T) bool - containers.Interface + containers.Container[T] // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string } diff --git a/sets/treeset/enumerable.go b/sets/treeset/enumerable.go new file mode 100644 index 00000000..b41a495a --- /dev/null +++ b/sets/treeset/enumerable.go @@ -0,0 +1,82 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treeset + +import ( + "github.com/emirpasic/gods/v2/containers" + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +// Assert Enumerable implementation +var _ containers.EnumerableWithIndex[int] = (*Set[int])(nil) + +// Each calls the given function once for each element, passing that element's index and value. +func (set *Set[T]) Each(f func(index int, value T)) { + iterator := set.Iterator() + for iterator.Next() { + f(iterator.Index(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a +// container containing the values returned by the given function. +func (set *Set[T]) Map(f func(index int, value T) T) *Set[T] { + newSet := &Set[T]{tree: rbt.NewWith[T, struct{}](set.tree.Comparator)} + iterator := set.Iterator() + for iterator.Next() { + newSet.Add(f(iterator.Index(), iterator.Value())) + } + return newSet +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (set *Set[T]) Select(f func(index int, value T) bool) *Set[T] { + newSet := &Set[T]{tree: rbt.NewWith[T, struct{}](set.tree.Comparator)} + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + newSet.Add(iterator.Value()) + } + } + return newSet +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (set *Set[T]) Any(f func(index int, value T) bool) bool { + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (set *Set[T]) All(f func(index int, value T) bool) bool { + iterator := set.Iterator() + for iterator.Next() { + if !f(iterator.Index(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (index,value) for which the function is true or -1,nil otherwise +// if no element matches the criteria. +func (set *Set[T]) Find(f func(index int, value T) bool) (int, T) { + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return iterator.Index(), iterator.Value() + } + } + var t T + return -1, t +} diff --git a/sets/treeset/iterator.go b/sets/treeset/iterator.go new file mode 100644 index 00000000..435b46b0 --- /dev/null +++ b/sets/treeset/iterator.go @@ -0,0 +1,116 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treeset + +import ( + "github.com/emirpasic/gods/v2/containers" + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + index int + iterator *rbt.Iterator[T, struct{}] + tree *rbt.Tree[T, struct{}] +} + +// Iterator holding the iterator's state +func (set *Set[T]) Iterator() Iterator[T] { + return Iterator[T]{index: -1, iterator: set.tree.Iterator(), tree: set.tree} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.tree.Size() { + iterator.index++ + } + return iterator.iterator.Next() +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + if iterator.index >= 0 { + iterator.index-- + } + return iterator.iterator.Prev() +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + return iterator.iterator.Key() +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 + iterator.iterator.Begin() +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.index = iterator.tree.Size() + iterator.iterator.End() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/sets/treeset/serialization.go b/sets/treeset/serialization.go new file mode 100644 index 00000000..5543fd36 --- /dev/null +++ b/sets/treeset/serialization.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package treeset + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Set[int])(nil) +var _ containers.JSONDeserializer = (*Set[int])(nil) + +// ToJSON outputs the JSON representation of the set. +func (set *Set[T]) ToJSON() ([]byte, error) { + return json.Marshal(set.Values()) +} + +// FromJSON populates the set from the input JSON representation. +func (set *Set[T]) FromJSON(data []byte) error { + var elements []T + err := json.Unmarshal(data, &elements) + if err == nil { + set.Clear() + set.Add(elements...) + } + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (set *Set[T]) UnmarshalJSON(bytes []byte) error { + return set.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (set *Set[T]) MarshalJSON() ([]byte, error) { + return set.ToJSON() +} diff --git a/sets/treeset/treeset.go b/sets/treeset/treeset.go index 9236eb1d..6b63a7e1 100644 --- a/sets/treeset/treeset.go +++ b/sets/treeset/treeset.go @@ -1,78 +1,66 @@ -/* -Copyright (c) Emir Pasic, All rights reserved. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library. See the file LICENSE included -with this distribution for more information. -*/ - -// Implementation of an ordered set backed by a red-black tree. +// Package treeset implements a tree backed by a red-black tree. +// // Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/Set_%28abstract_data_type%29 - +// +// Reference: http://en.wikipedia.org/wiki/Set_%28abstract_data_type%29 package treeset import ( + "cmp" "fmt" - "github.com/emirpasic/gods/sets" - rbt "github.com/emirpasic/gods/trees/redblacktree" - "github.com/emirpasic/gods/utils" + "reflect" "strings" + + "github.com/emirpasic/gods/v2/sets" + rbt "github.com/emirpasic/gods/v2/trees/redblacktree" + "github.com/emirpasic/gods/v2/utils" ) -func assertInterfaceImplementation() { - var _ sets.Interface = (*Set)(nil) -} +// Assert Set implementation +var _ sets.Set[int] = (*Set[int])(nil) -type Set struct { - tree *rbt.Tree +// Set holds elements in a red-black tree +type Set[T comparable] struct { + tree *rbt.Tree[T, struct{}] } var itemExists = struct{}{} -// Instantiates a new empty set with the custom comparator. -func NewWith(comparator utils.Comparator) *Set { - return &Set{tree: rbt.NewWith(comparator)} -} - -// Instantiates a new empty set with the IntComparator, i.e. keys are of type int. -func NewWithIntComparator() *Set { - return &Set{tree: rbt.NewWithIntComparator()} +func New[T cmp.Ordered](values ...T) *Set[T] { + return NewWith[T](cmp.Compare[T], values...) } -// Instantiates a new empty set with the StringComparator, i.e. keys are of type string. -func NewWithStringComparator() *Set { - return &Set{tree: rbt.NewWithStringComparator()} +// NewWith instantiates a new empty set with the custom comparator. +func NewWith[T comparable](comparator utils.Comparator[T], values ...T) *Set[T] { + set := &Set[T]{tree: rbt.NewWith[T, struct{}](comparator)} + if len(values) > 0 { + set.Add(values...) + } + return set } -// Adds the items (one or more) to the set. -func (set *Set) Add(items ...interface{}) { +// Add adds the items (one or more) to the set. +func (set *Set[T]) Add(items ...T) { for _, item := range items { set.tree.Put(item, itemExists) } } -// Removes the items (one or more) from the set. -func (set *Set) Remove(items ...interface{}) { +// Remove removes the items (one or more) from the set. +func (set *Set[T]) Remove(items ...T) { for _, item := range items { set.tree.Remove(item) } } -// Check wether items (one or more) are present in the set. +// Contains checks weather items (one or more) are present in the set. // All items have to be present in the set for the method to return true. // Returns true if no arguments are passed at all, i.e. set is always superset of empty set. -func (set *Set) Contains(items ...interface{}) bool { +func (set *Set[T]) Contains(items ...T) bool { for _, item := range items { if _, contains := set.tree.Get(item); !contains { return false @@ -81,27 +69,28 @@ func (set *Set) Contains(items ...interface{}) bool { return true } -// Returns true if set does not contain any elements. -func (set *Set) Empty() bool { +// Empty returns true if set does not contain any elements. +func (set *Set[T]) Empty() bool { return set.tree.Size() == 0 } -// Returns number of elements within the set. -func (set *Set) Size() int { +// Size returns number of elements within the set. +func (set *Set[T]) Size() int { return set.tree.Size() } -// Clears all values in the set. -func (set *Set) Clear() { +// Clear clears all values in the set. +func (set *Set[T]) Clear() { set.tree.Clear() } -// Returns all items in the set. -func (set *Set) Values() []interface{} { +// Values returns all items in the set. +func (set *Set[T]) Values() []T { return set.tree.Keys() } -func (set *Set) String() string { +// String returns a string representation of container +func (set *Set[T]) String() string { str := "TreeSet\n" items := []string{} for _, v := range set.tree.Keys() { @@ -110,3 +99,79 @@ func (set *Set) String() string { str += strings.Join(items, ", ") return str } + +// Intersection returns the intersection between two sets. +// The new set consists of all elements that are both in "set" and "another". +// The two sets should have the same comparators, otherwise the result is empty set. +// Ref: https://en.wikipedia.org/wiki/Intersection_(set_theory) +func (set *Set[T]) Intersection(another *Set[T]) *Set[T] { + result := NewWith(set.tree.Comparator) + + setComparator := reflect.ValueOf(set.tree.Comparator) + anotherComparator := reflect.ValueOf(another.tree.Comparator) + if setComparator.Pointer() != anotherComparator.Pointer() { + return result + } + + // Iterate over smaller set (optimization) + if set.Size() <= another.Size() { + for it := set.Iterator(); it.Next(); { + if another.Contains(it.Value()) { + result.Add(it.Value()) + } + } + } else { + for it := another.Iterator(); it.Next(); { + if set.Contains(it.Value()) { + result.Add(it.Value()) + } + } + } + + return result +} + +// Union returns the union of two sets. +// The new set consists of all elements that are in "set" or "another" (possibly both). +// The two sets should have the same comparators, otherwise the result is empty set. +// Ref: https://en.wikipedia.org/wiki/Union_(set_theory) +func (set *Set[T]) Union(another *Set[T]) *Set[T] { + result := NewWith(set.tree.Comparator) + + setComparator := reflect.ValueOf(set.tree.Comparator) + anotherComparator := reflect.ValueOf(another.tree.Comparator) + if setComparator.Pointer() != anotherComparator.Pointer() { + return result + } + + for it := set.Iterator(); it.Next(); { + result.Add(it.Value()) + } + for it := another.Iterator(); it.Next(); { + result.Add(it.Value()) + } + + return result +} + +// Difference returns the difference between two sets. +// The two sets should have the same comparators, otherwise the result is empty set. +// The new set consists of all elements that are in "set" but not in "another". +// Ref: https://proofwiki.org/wiki/Definition:Set_Difference +func (set *Set[T]) Difference(another *Set[T]) *Set[T] { + result := NewWith(set.tree.Comparator) + + setComparator := reflect.ValueOf(set.tree.Comparator) + anotherComparator := reflect.ValueOf(another.tree.Comparator) + if setComparator.Pointer() != anotherComparator.Pointer() { + return result + } + + for it := set.Iterator(); it.Next(); { + if !another.Contains(it.Value()) { + result.Add(it.Value()) + } + } + + return result +} diff --git a/sets/treeset/treeset_test.go b/sets/treeset/treeset_test.go index 4ad42652..709ed84d 100644 --- a/sets/treeset/treeset_test.go +++ b/sets/treeset/treeset_test.go @@ -1,98 +1,707 @@ -/* -Copyright (c) Emir Pasic, All rights reserved. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library. See the file LICENSE included -with this distribution for more information. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package treeset import ( + "encoding/json" "fmt" + "strings" "testing" -) -func TestTreeSet(t *testing.T) { + "github.com/emirpasic/gods/v2/testutils" +) - set := NewWithIntComparator() +func TestSetNew(t *testing.T) { + set := New[int](2, 1) + if actualValue := set.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + values := set.Values() + if actualValue := values[0]; actualValue != 1 { + t.Errorf("Got %v expected %v", actualValue, 1) + } + if actualValue := values[1]; actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } +} - // insertions +func TestSetAdd(t *testing.T) { + set := New[int]() set.Add() set.Add(1) set.Add(2) set.Add(2, 3) set.Add() - if actualValue := set.Empty(); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } - if actualValue := set.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } + testutils.SameElements(t, set.Values(), []int{1, 2, 3}) +} - // Asking if a set is superset of nothing, thus it's true +func TestSetContains(t *testing.T) { + set := New[int]() + set.Add(3, 1, 2) if actualValue := set.Contains(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := set.Contains(1); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := set.Contains(1, 2, 3); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := set.Contains(1, 2, 3, 4); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } +} + +func TestSetRemove(t *testing.T) { + set := New[int]() + set.Add(3, 1, 2) + set.Remove() + if actualValue := set.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + set.Remove(1) + if actualValue := set.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + set.Remove(3) + set.Remove(3) + set.Remove() + set.Remove(2) + if actualValue := set.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} - // repeat 10 time since map in golang has a random iteration order each time and we want to make sure that the set is ordered - for i := 1; i <= 10; i++ { - if actualValue, expactedValue := fmt.Sprintf("%d%d%d", set.Values()...), "123"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) +func TestSetEach(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + set.Each(func(index int, value string) { + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") } + }) +} + +func TestSetMap(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + mappedSet := set.Map(func(index int, value string) string { + return "mapped: " + value + }) + if actualValue, expectedValue := mappedSet.Contains("mapped: a", "mapped: b", "mapped: c"), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := mappedSet.Contains("mapped: a", "mapped: b", "mapped: x"), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if mappedSet.Size() != 3 { + t.Errorf("Got %v expected %v", mappedSet.Size(), 3) } +} - set.Remove() - set.Remove(1) +func TestSetSelect(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + selectedSet := set.Select(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if actualValue, expectedValue := selectedSet.Contains("a", "b"), true; actualValue != expectedValue { + fmt.Println("A: ", selectedSet.Contains("b")) + t.Errorf("Got %v (%v) expected %v (%v)", actualValue, selectedSet.Values(), expectedValue, "[a b]") + } + if actualValue, expectedValue := selectedSet.Contains("a", "b", "c"), false; actualValue != expectedValue { + t.Errorf("Got %v (%v) expected %v (%v)", actualValue, selectedSet.Values(), expectedValue, "[a b]") + } + if selectedSet.Size() != 2 { + t.Errorf("Got %v expected %v", selectedSet.Size(), 3) + } +} - if actualValue := set.Contains(1); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) +func TestSetAny(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + any := set.Any(func(index int, value string) bool { + return value == "c" + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = set.Any(func(index int, value string) bool { + return value == "x" + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) } +} - set.Remove(1, 2, 3) +func TestSetAll(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + all := set.All(func(index int, value string) bool { + return value >= "a" && value <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = set.All(func(index int, value string) bool { + return value >= "a" && value <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} - if actualValue := set.Contains(3); actualValue != false { - t.Errorf("Got %v expected %v", actualValue, false) +func TestSetFind(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + foundIndex, foundValue := set.Find(func(index int, value string) bool { + return value == "c" + }) + if foundValue != "c" || foundIndex != 2 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, "c", 2) + } + foundIndex, foundValue = set.Find(func(index int, value string) bool { + return value == "x" + }) + if foundValue != "" || foundIndex != -1 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, nil, nil) + } +} + +func TestSetChaining(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") +} + +func TestSetIteratorNextOnEmpty(t *testing.T) { + set := New[string]() + it := set.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty set") + } +} + +func TestSetIteratorPrevOnEmpty(t *testing.T) { + set := New[string]() + it := set.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty set") + } +} + +func TestSetIteratorNext(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + it := set.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestSetIteratorPrev(t *testing.T) { + set := New[string]() + set.Add("c", "a", "b") + it := set.Iterator() + for it.Prev() { + } + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestSetIteratorBegin(t *testing.T) { + set := New[string]() + it := set.Iterator() + it.Begin() + set.Add("a", "b", "c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestSetIteratorEnd(t *testing.T) { + set := New[string]() + it := set.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + set.Add("a", "b", "c") + it.End() + if index := it.Index(); index != set.Size() { + t.Errorf("Got %v expected %v", index, set.Size()) } - if actualValue := set.Empty(); actualValue != true { + it.Prev() + if index, value := it.Index(), it.Value(); index != set.Size()-1 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, set.Size()-1, "c") + } +} + +func TestSetIteratorFirst(t *testing.T) { + set := New[string]() + set.Add("a", "b", "c") + it := set.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestSetIteratorLast(t *testing.T) { + set := New[string]() + set.Add("a", "b", "c") + it := set.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "c") + } +} + +func TestSetIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + set := New[string]() + it := set.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // NextTo (not found) + { + set := New[string]() + set.Add("xx", "yy") + it := set.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // NextTo (found) + { + set := New[string]() + set.Add("aa", "bb", "cc") + it := set.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestSetIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + set := New[string]() + it := set.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // PrevTo (not found) + { + set := New[string]() + set.Add("xx", "yy") + it := set.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + } + + // PrevTo (found) + { + set := New[string]() + set.Add("aa", "bb", "cc") + it := set.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty set") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestSetSerialization(t *testing.T) { + set := New[string]() + set.Add("a", "b", "c") + + var err error + assert := func() { + if actualValue, expectedValue := set.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := set.Contains("a", "b", "c"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := set.ToJSON() + assert() + + err = set.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", set}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["1","2","3"]`), &set) + if err != nil { + t.Errorf("Got error %v", err) + } +} + +func TestSetString(t *testing.T) { + c := New[int]() + c.Add(1) + if !strings.HasPrefix(c.String(), "TreeSet") { + t.Errorf("String should start with container name") + } +} + +func TestSetIntersection(t *testing.T) { + set := New[string]() + another := New[string]() + + intersection := set.Intersection(another) + if actualValue, expectedValue := intersection.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + intersection = set.Intersection(another) + + if actualValue, expectedValue := intersection.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := intersection.Contains("c", "d"); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } +} + +func TestSetUnion(t *testing.T) { + set := New[string]() + another := New[string]() + + union := set.Union(another) + if actualValue, expectedValue := union.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + union = set.Union(another) + if actualValue, expectedValue := union.Size(), 6; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := union.Contains("a", "b", "c", "d", "e", "f"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } } -func BenchmarkTreeSet(b *testing.B) { +func TestSetDifference(t *testing.T) { + set := New[string]() + another := New[string]() + + difference := set.Difference(another) + if actualValue, expectedValue := difference.Size(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + set.Add("a", "b", "c", "d") + another.Add("c", "d", "e", "f") + + difference = set.Difference(another) + + if actualValue, expectedValue := difference.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := difference.Contains("a", "b"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } +} + +func benchmarkContains(b *testing.B, set *Set[int], size int) { for i := 0; i < b.N; i++ { - set := NewWithIntComparator() - for n := 0; n < 1000; n++ { - set.Add(i) + for n := 0; n < size; n++ { + set.Contains(n) } - for n := 0; n < 1000; n++ { + } +} + +func benchmarkAdd(b *testing.B, set *Set[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Add(n) + } + } +} + +func benchmarkRemove(b *testing.B, set *Set[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { set.Remove(n) } } } + +func BenchmarkTreeSetContains100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkTreeSetContains1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkTreeSetContains10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkTreeSetContains100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkTreeSetAdd100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkTreeSetAdd1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkTreeSetAdd10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkTreeSetAdd100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkTreeSetRemove100(b *testing.B) { + b.StopTimer() + size := 100 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkTreeSetRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkTreeSetRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkTreeSetRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New[int]() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} diff --git a/stacks/arraystack/arraystack.go b/stacks/arraystack/arraystack.go index bb3a89da..ec80cb20 100644 --- a/stacks/arraystack/arraystack.go +++ b/stacks/arraystack/arraystack.go @@ -1,100 +1,81 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of stack backed by ArrayList. +// Package arraystack implements a stack backed by array list. +// // Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29 - +// +// Reference: https://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29#Array package arraystack import ( "fmt" - "github.com/emirpasic/gods/lists/arraylist" - "github.com/emirpasic/gods/stacks" "strings" + + "github.com/emirpasic/gods/v2/lists/arraylist" + "github.com/emirpasic/gods/v2/stacks" ) -func assertInterfaceImplementation() { - var _ stacks.Interface = (*Stack)(nil) -} +// Assert Stack implementation +var _ stacks.Stack[int] = (*Stack[int])(nil) -type Stack struct { - list *arraylist.List +// Stack holds elements in an array-list +type Stack[T comparable] struct { + list *arraylist.List[T] } -// Instantiates a new empty stack -func New() *Stack { - return &Stack{list: arraylist.New()} +// New instantiates a new empty stack +func New[T comparable]() *Stack[T] { + return &Stack[T]{list: arraylist.New[T]()} } -// Pushes a value onto the top of the stack -func (stack *Stack) Push(value interface{}) { +// Push adds a value onto the top of the stack +func (stack *Stack[T]) Push(value T) { stack.list.Add(value) } -// Pops (removes) top element on stack and returns it, or nil if stack is empty. +// Pop removes top element on stack and returns it, or nil if stack is empty. // Second return parameter is true, unless the stack was empty and there was nothing to pop. -func (stack *Stack) Pop() (value interface{}, ok bool) { +func (stack *Stack[T]) Pop() (value T, ok bool) { value, ok = stack.list.Get(stack.list.Size() - 1) stack.list.Remove(stack.list.Size() - 1) return } -// Returns top element on the stack without removing it, or nil if stack is empty. +// Peek returns top element on the stack without removing it, or nil if stack is empty. // Second return parameter is true, unless the stack was empty and there was nothing to peek. -func (stack *Stack) Peek() (value interface{}, ok bool) { +func (stack *Stack[T]) Peek() (value T, ok bool) { return stack.list.Get(stack.list.Size() - 1) } -// Returns true if stack does not contain any elements. -func (stack *Stack) Empty() bool { +// Empty returns true if stack does not contain any elements. +func (stack *Stack[T]) Empty() bool { return stack.list.Empty() } -// Returns number of elements within the stack. -func (stack *Stack) Size() int { +// Size returns number of elements within the stack. +func (stack *Stack[T]) Size() int { return stack.list.Size() } -// Removes all elements from the stack. -func (stack *Stack) Clear() { +// Clear removes all elements from the stack. +func (stack *Stack[T]) Clear() { stack.list.Clear() } -// Returns all elements in the stack (LIFO order). -func (stack *Stack) Values() []interface{} { +// Values returns all elements in the stack (LIFO order). +func (stack *Stack[T]) Values() []T { size := stack.list.Size() - elements := make([]interface{}, size, size) + elements := make([]T, size, size) for i := 1; i <= size; i++ { elements[size-i], _ = stack.list.Get(i - 1) // in reverse (LIFO) } return elements } -func (stack *Stack) String() string { +// String returns a string representation of container +func (stack *Stack[T]) String() string { str := "ArrayStack\n" values := []string{} for _, value := range stack.list.Values() { @@ -103,3 +84,8 @@ func (stack *Stack) String() string { str += strings.Join(values, ", ") return str } + +// Check that the index is within bounds of the list +func (stack *Stack[T]) withinRange(index int) bool { + return index >= 0 && index < stack.list.Size() +} diff --git a/stacks/arraystack/arraystack_test.go b/stacks/arraystack/arraystack_test.go index 62836db7..4e754608 100644 --- a/stacks/arraystack/arraystack_test.go +++ b/stacks/arraystack/arraystack_test.go @@ -1,101 +1,494 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package arraystack import ( + "encoding/json" + "strings" "testing" -) - -func TestArrayStack(t *testing.T) { - stack := New() + "github.com/emirpasic/gods/v2/testutils" +) +func TestStackPush(t *testing.T) { + stack := New[int]() if actualValue := stack.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - - // insertions stack.Push(1) stack.Push(2) stack.Push(3) - if actualValue := stack.Values(); actualValue[0].(int) != 3 || actualValue[1].(int) != 2 || actualValue[2].(int) != 1 { + if actualValue := stack.Values(); actualValue[0] != 3 || actualValue[1] != 2 || actualValue[2] != 1 { t.Errorf("Got %v expected %v", actualValue, "[3,2,1]") } - if actualValue := stack.Empty(); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } - if actualValue := stack.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } + if actualValue, ok := stack.Peek(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } +} +func TestStackPeek(t *testing.T) { + stack := New[int]() + if actualValue, ok := stack.Peek(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + stack.Push(1) + stack.Push(2) + stack.Push(3) if actualValue, ok := stack.Peek(); actualValue != 3 || !ok { t.Errorf("Got %v expected %v", actualValue, 3) } +} +func TestStackPop(t *testing.T) { + stack := New[int]() + stack.Push(1) + stack.Push(2) + stack.Push(3) stack.Pop() - if actualValue, ok := stack.Peek(); actualValue != 2 || !ok { t.Errorf("Got %v expected %v", actualValue, 2) } - if actualValue, ok := stack.Pop(); actualValue != 2 || !ok { t.Errorf("Got %v expected %v", actualValue, 2) } - if actualValue, ok := stack.Pop(); actualValue != 1 || !ok { t.Errorf("Got %v expected %v", actualValue, 1) } - - if actualValue, ok := stack.Pop(); actualValue != nil || ok { + if actualValue, ok := stack.Pop(); actualValue != 0 || ok { t.Errorf("Got %v expected %v", actualValue, nil) } - if actualValue := stack.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := stack.Values(); len(actualValue) != 0 { t.Errorf("Got %v expected %v", actualValue, "[]") } +} + +func TestStackIteratorOnEmpty(t *testing.T) { + stack := New[string]() + it := stack.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty stack") + } +} + +func TestStackIteratorNext(t *testing.T) { + stack := New[string]() + stack.Push("a") + stack.Push("b") + stack.Push("c") + + it := stack.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + stack.Clear() + it = stack.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty stack") + } +} + +func TestStackIteratorPrev(t *testing.T) { + stack := New[string]() + stack.Push("a") + stack.Push("b") + stack.Push("c") + + it := stack.Iterator() + for it.Next() { + } + count := 0 + for it.Prev() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, 3-count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestStackIteratorBegin(t *testing.T) { + stack := New[string]() + it := stack.Iterator() + it.Begin() + stack.Push("a") + stack.Push("b") + stack.Push("c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "c") + } +} + +func TestStackIteratorEnd(t *testing.T) { + stack := New[string]() + it := stack.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + stack.Push("a") + stack.Push("b") + stack.Push("c") + it.End() + if index := it.Index(); index != stack.Size() { + t.Errorf("Got %v expected %v", index, stack.Size()) + } + + it.Prev() + if index, value := it.Index(), it.Value(); index != stack.Size()-1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, stack.Size()-1, "a") + } +} + +func TestStackIteratorFirst(t *testing.T) { + stack := New[string]() + it := stack.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + stack.Push("a") + stack.Push("b") + stack.Push("c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "c") + } +} + +func TestStackIteratorLast(t *testing.T) { + stack := New[string]() + it := stack.Iterator() + if actualValue, expectedValue := it.Last(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + stack.Push("a") + stack.Push("b") + stack.Push("c") + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "a") + } +} + +func TestStackIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + stack := New[string]() + it := stack.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + } + + // NextTo (not found) + { + stack := New[string]() + stack.Push("xx") + stack.Push("yy") + it := stack.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + } + + // NextTo (found) + { + stack := New[string]() + stack.Push("aa") + stack.Push("bb") + stack.Push("cc") + it := stack.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "aa") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestStackIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + stack := New[string]() + it := stack.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + } + + // PrevTo (not found) + { + stack := New[string]() + stack.Push("xx") + stack.Push("yy") + it := stack.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + } + // PrevTo (found) + { + stack := New[string]() + stack.Push("aa") + stack.Push("bb") + stack.Push("cc") + it := stack.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "cc") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } } -func BenchmarkArrayStack(b *testing.B) { +func TestStackSerialization(t *testing.T) { + stack := New[string]() + stack.Push("a") + stack.Push("b") + stack.Push("c") + + var err error + assert := func() { + testutils.SameElements(t, stack.Values(), []string{"c", "b", "a"}) + if actualValue, expectedValue := stack.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := stack.ToJSON() + assert() + + err = stack.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", stack}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &stack) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestStackString(t *testing.T) { + c := New[int]() + c.Push(1) + if !strings.HasPrefix(c.String(), "ArrayStack") { + t.Errorf("String should start with container name") + } +} + +func benchmarkPush(b *testing.B, stack *Stack[int], size int) { for i := 0; i < b.N; i++ { - stack := New() - for n := 0; n < 1000; n++ { - stack.Push(i) + for n := 0; n < size; n++ { + stack.Push(n) } - for !stack.Empty() { + } +} + +func benchmarkPop(b *testing.B, stack *Stack[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { stack.Pop() } } +} +func BenchmarkArrayStackPop100(b *testing.B) { + b.StopTimer() + size := 100 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkArrayStackPop1000(b *testing.B) { + b.StopTimer() + size := 1000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkArrayStackPop10000(b *testing.B) { + b.StopTimer() + size := 10000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkArrayStackPop100000(b *testing.B) { + b.StopTimer() + size := 100000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkArrayStackPush100(b *testing.B) { + b.StopTimer() + size := 100 + stack := New[int]() + b.StartTimer() + benchmarkPush(b, stack, size) +} + +func BenchmarkArrayStackPush1000(b *testing.B) { + b.StopTimer() + size := 1000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPush(b, stack, size) +} + +func BenchmarkArrayStackPush10000(b *testing.B) { + b.StopTimer() + size := 10000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPush(b, stack, size) +} + +func BenchmarkArrayStackPush100000(b *testing.B) { + b.StopTimer() + size := 100000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPush(b, stack, size) } diff --git a/stacks/arraystack/iterator.go b/stacks/arraystack/iterator.go new file mode 100644 index 00000000..651e41a0 --- /dev/null +++ b/stacks/arraystack/iterator.go @@ -0,0 +1,111 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arraystack + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + stack *Stack[T] + index int +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (stack *Stack[T]) Iterator() *Iterator[T] { + return &Iterator[T]{stack: stack, index: -1} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.stack.Size() { + iterator.index++ + } + return iterator.stack.withinRange(iterator.index) +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + if iterator.index >= 0 { + iterator.index-- + } + return iterator.stack.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + value, _ := iterator.stack.list.Get(iterator.stack.list.Size() - iterator.index - 1) // in reverse (LIFO) + return value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.index = iterator.stack.Size() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/stacks/arraystack/serialization.go b/stacks/arraystack/serialization.go new file mode 100644 index 00000000..ff0c6385 --- /dev/null +++ b/stacks/arraystack/serialization.go @@ -0,0 +1,33 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package arraystack + +import ( + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Stack[int])(nil) +var _ containers.JSONDeserializer = (*Stack[int])(nil) + +// ToJSON outputs the JSON representation of the stack. +func (stack *Stack[T]) ToJSON() ([]byte, error) { + return stack.list.ToJSON() +} + +// FromJSON populates the stack from the input JSON representation. +func (stack *Stack[T]) FromJSON(data []byte) error { + return stack.list.FromJSON(data) +} + +// UnmarshalJSON @implements json.Unmarshaler +func (stack *Stack[T]) UnmarshalJSON(bytes []byte) error { + return stack.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (stack *Stack[T]) MarshalJSON() ([]byte, error) { + return stack.ToJSON() +} diff --git a/stacks/linkedliststack/iterator.go b/stacks/linkedliststack/iterator.go new file mode 100644 index 00000000..5350e330 --- /dev/null +++ b/stacks/linkedliststack/iterator.go @@ -0,0 +1,73 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedliststack + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.IteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + stack *Stack[T] + index int +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (stack *Stack[T]) Iterator() *Iterator[T] { + return &Iterator[T]{stack: stack, index: -1} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.stack.Size() { + iterator.index++ + } + return iterator.stack.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + value, _ := iterator.stack.list.Get(iterator.index) // in reverse (LIFO) + return value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} diff --git a/stacks/linkedliststack/linkedliststack.go b/stacks/linkedliststack/linkedliststack.go index bf96b4c8..ec373dd5 100644 --- a/stacks/linkedliststack/linkedliststack.go +++ b/stacks/linkedliststack/linkedliststack.go @@ -1,96 +1,76 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of stack backed by our singly linked list. -// Used by red-black tree during in-order traversal. +// Package linkedliststack implements a stack backed by a singly-linked list. +// // Structure is not thread safe. -// References: http://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29 - +// +// Reference:https://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29#Linked_list package linkedliststack import ( "fmt" - "github.com/emirpasic/gods/lists/singlylinkedlist" - "github.com/emirpasic/gods/stacks" "strings" + + "github.com/emirpasic/gods/v2/lists/singlylinkedlist" + "github.com/emirpasic/gods/v2/stacks" ) -func assertInterfaceImplementation() { - var _ stacks.Interface = (*Stack)(nil) -} +// Assert Stack implementation +var _ stacks.Stack[int] = (*Stack[int])(nil) -type Stack struct { - list *singlylinkedlist.List +// Stack holds elements in a singly-linked-list +type Stack[T comparable] struct { + list *singlylinkedlist.List[T] } -// Instantiates a new empty stack -func New() *Stack { - return &Stack{list: &singlylinkedlist.List{}} +// New nnstantiates a new empty stack +func New[T comparable]() *Stack[T] { + return &Stack[T]{list: singlylinkedlist.New[T]()} } -// Pushes a value onto the top of the stack -func (stack *Stack) Push(value interface{}) { +// Push adds a value onto the top of the stack +func (stack *Stack[T]) Push(value T) { stack.list.Prepend(value) } -// Pops (removes) top element on stack and returns it, or nil if stack is empty. +// Pop removes top element on stack and returns it, or nil if stack is empty. // Second return parameter is true, unless the stack was empty and there was nothing to pop. -func (stack *Stack) Pop() (value interface{}, ok bool) { +func (stack *Stack[T]) Pop() (value T, ok bool) { value, ok = stack.list.Get(0) stack.list.Remove(0) return } -// Returns top element on the stack without removing it, or nil if stack is empty. +// Peek returns top element on the stack without removing it, or nil if stack is empty. // Second return parameter is true, unless the stack was empty and there was nothing to peek. -func (stack *Stack) Peek() (value interface{}, ok bool) { +func (stack *Stack[T]) Peek() (value T, ok bool) { return stack.list.Get(0) } -// Returns true if stack does not contain any elements. -func (stack *Stack) Empty() bool { +// Empty returns true if stack does not contain any elements. +func (stack *Stack[T]) Empty() bool { return stack.list.Empty() } -// Returns number of elements within the stack. -func (stack *Stack) Size() int { +// Size returns number of elements within the stack. +func (stack *Stack[T]) Size() int { return stack.list.Size() } -// Removes all elements from the stack. -func (stack *Stack) Clear() { +// Clear removes all elements from the stack. +func (stack *Stack[T]) Clear() { stack.list.Clear() } -// Returns all elements in the stack (LIFO order). -func (stack *Stack) Values() []interface{} { +// Values returns all elements in the stack (LIFO order). +func (stack *Stack[T]) Values() []T { return stack.list.Values() } -func (stack *Stack) String() string { +// String returns a string representation of container +func (stack *Stack[T]) String() string { str := "LinkedListStack\n" values := []string{} for _, value := range stack.list.Values() { @@ -99,3 +79,8 @@ func (stack *Stack) String() string { str += strings.Join(values, ", ") return str } + +// Check that the index is within bounds of the list +func (stack *Stack[T]) withinRange(index int) bool { + return index >= 0 && index < stack.list.Size() +} diff --git a/stacks/linkedliststack/linkedliststack_test.go b/stacks/linkedliststack/linkedliststack_test.go index dea7ba0a..f215ba37 100644 --- a/stacks/linkedliststack/linkedliststack_test.go +++ b/stacks/linkedliststack/linkedliststack_test.go @@ -1,100 +1,350 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package linkedliststack import ( + "encoding/json" + "strings" "testing" -) - -func TestLinkedListStack(t *testing.T) { - stack := New() + "github.com/emirpasic/gods/v2/testutils" +) +func TestStackPush(t *testing.T) { + stack := New[int]() if actualValue := stack.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - - // insertions stack.Push(1) stack.Push(2) stack.Push(3) - if actualValue := stack.Values(); actualValue[0].(int) != 3 || actualValue[1].(int) != 2 || actualValue[2].(int) != 1 { + if actualValue := stack.Values(); actualValue[0] != 3 || actualValue[1] != 2 || actualValue[2] != 1 { t.Errorf("Got %v expected %v", actualValue, "[3,2,1]") } - if actualValue := stack.Empty(); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } - if actualValue := stack.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } + if actualValue, ok := stack.Peek(); actualValue != 3 || !ok { + t.Errorf("Got %v expected %v", actualValue, 3) + } +} +func TestStackPeek(t *testing.T) { + stack := New[int]() + if actualValue, ok := stack.Peek(); actualValue != 0 || ok { + t.Errorf("Got %v expected %v", actualValue, nil) + } + stack.Push(1) + stack.Push(2) + stack.Push(3) if actualValue, ok := stack.Peek(); actualValue != 3 || !ok { t.Errorf("Got %v expected %v", actualValue, 3) } +} +func TestStackPop(t *testing.T) { + stack := New[int]() + stack.Push(1) + stack.Push(2) + stack.Push(3) stack.Pop() - if actualValue, ok := stack.Peek(); actualValue != 2 || !ok { t.Errorf("Got %v expected %v", actualValue, 2) } - if actualValue, ok := stack.Pop(); actualValue != 2 || !ok { t.Errorf("Got %v expected %v", actualValue, 2) } - if actualValue, ok := stack.Pop(); actualValue != 1 || !ok { t.Errorf("Got %v expected %v", actualValue, 1) } - - if actualValue, ok := stack.Pop(); actualValue != nil || ok { + if actualValue, ok := stack.Pop(); actualValue != 0 || ok { t.Errorf("Got %v expected %v", actualValue, nil) } - if actualValue := stack.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := stack.Values(); len(actualValue) != 0 { t.Errorf("Got %v expected %v", actualValue, "[]") } +} +func TestStackIterator(t *testing.T) { + stack := New[string]() + stack.Push("a") + stack.Push("b") + stack.Push("c") + + // Iterator + it := stack.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + stack.Clear() + it = stack.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty stack") + } } -func BenchmarkLinkedListStack(b *testing.B) { +func TestStackIteratorBegin(t *testing.T) { + stack := New[string]() + it := stack.Iterator() + it.Begin() + stack.Push("a") + stack.Push("b") + stack.Push("c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "c") + } +} + +func TestStackIteratorFirst(t *testing.T) { + stack := New[string]() + it := stack.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + stack.Push("a") + stack.Push("b") + stack.Push("c") + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "c") + } +} + +func TestStackIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + stack := New[string]() + it := stack.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + } + + // NextTo (not found) + { + stack := New[string]() + stack.Push("xx") + stack.Push("yy") + it := stack.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + } + + // NextTo (found) + { + stack := New[string]() + stack.Push("aa") + stack.Push("bb") + stack.Push("cc") + it := stack.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty stack") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "aa") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestStackSerialization(t *testing.T) { + stack := New[string]() + stack.Push("a") + stack.Push("b") + stack.Push("c") + + var err error + assert := func() { + testutils.SameElements(t, stack.Values(), []string{"c", "b", "a"}) + if actualValue, expectedValue := stack.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := stack.ToJSON() + assert() + + err = stack.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", stack}) + if err != nil { + t.Errorf("Got error %v", err) + } + + err = json.Unmarshal([]byte(`["a","b","c"]`), &stack) + if err != nil { + t.Errorf("Got error %v", err) + } + assert() +} + +func TestStackString(t *testing.T) { + c := New[int]() + c.Push(1) + if !strings.HasPrefix(c.String(), "LinkedListStack") { + t.Errorf("String should start with container name") + } +} + +func benchmarkPush(b *testing.B, stack *Stack[int], size int) { for i := 0; i < b.N; i++ { - stack := New() - for n := 0; n < 1000; n++ { - stack.Push(i) + for n := 0; n < size; n++ { + stack.Push(n) } - for !stack.Empty() { + } +} + +func benchmarkPop(b *testing.B, stack *Stack[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { stack.Pop() } } } + +func BenchmarkLinkedListStackPop100(b *testing.B) { + b.StopTimer() + size := 100 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkLinkedListStackPop1000(b *testing.B) { + b.StopTimer() + size := 1000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkLinkedListStackPop10000(b *testing.B) { + b.StopTimer() + size := 10000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkLinkedListStackPop100000(b *testing.B) { + b.StopTimer() + size := 100000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPop(b, stack, size) +} + +func BenchmarkLinkedListStackPush100(b *testing.B) { + b.StopTimer() + size := 100 + stack := New[int]() + b.StartTimer() + benchmarkPush(b, stack, size) +} + +func BenchmarkLinkedListStackPush1000(b *testing.B) { + b.StopTimer() + size := 1000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPush(b, stack, size) +} + +func BenchmarkLinkedListStackPush10000(b *testing.B) { + b.StopTimer() + size := 10000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPush(b, stack, size) +} + +func BenchmarkLinkedListStackPush100000(b *testing.B) { + b.StopTimer() + size := 100000 + stack := New[int]() + for n := 0; n < size; n++ { + stack.Push(n) + } + b.StartTimer() + benchmarkPush(b, stack, size) +} diff --git a/stacks/linkedliststack/serialization.go b/stacks/linkedliststack/serialization.go new file mode 100644 index 00000000..3a8ca044 --- /dev/null +++ b/stacks/linkedliststack/serialization.go @@ -0,0 +1,33 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linkedliststack + +import ( + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Stack[int])(nil) +var _ containers.JSONDeserializer = (*Stack[int])(nil) + +// ToJSON outputs the JSON representation of the stack. +func (stack *Stack[T]) ToJSON() ([]byte, error) { + return stack.list.ToJSON() +} + +// FromJSON populates the stack from the input JSON representation. +func (stack *Stack[T]) FromJSON(data []byte) error { + return stack.list.FromJSON(data) +} + +// UnmarshalJSON @implements json.Unmarshaler +func (stack *Stack[T]) UnmarshalJSON(bytes []byte) error { + return stack.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (stack *Stack[T]) MarshalJSON() ([]byte, error) { + return stack.ToJSON() +} diff --git a/stacks/stacks.go b/stacks/stacks.go index fe9abbbf..4acd0116 100644 --- a/stacks/stacks.go +++ b/stacks/stacks.go @@ -1,41 +1,26 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package stacks provides an abstract Stack interface. +// +// In computer science, a stack is an abstract data type that serves as a collection of elements, with two principal operations: push, which adds an element to the collection, and pop, which removes the most recently added element that was not yet removed. The order in which elements come off a stack gives rise to its alternative name, LIFO (for last in, first out). Additionally, a peek operation may give access to the top without modifying the stack. +// +// Reference: https://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29 package stacks -import "github.com/emirpasic/gods/containers" +import "github.com/emirpasic/gods/v2/containers" -type Interface interface { - Push(value interface{}) - Pop() (value interface{}, ok bool) - Peek() (value interface{}, ok bool) +// Stack interface that all stacks implement +type Stack[T any] interface { + Push(value T) + Pop() (value T, ok bool) + Peek() (value T, ok bool) - containers.Interface + containers.Container[T] // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string } diff --git a/testutils/testutils.go b/testutils/testutils.go new file mode 100644 index 00000000..b97d6385 --- /dev/null +++ b/testutils/testutils.go @@ -0,0 +1,18 @@ +package testutils + +import "testing" + +func SameElements[T comparable](t *testing.T, actual, expected []T) { + if len(actual) != len(expected) { + t.Errorf("Got %d expected %d", len(actual), len(expected)) + } +outer: + for _, e := range expected { + for _, a := range actual { + if e == a { + continue outer + } + } + t.Errorf("Did not find expected element %v in %v", e, actual) + } +} diff --git a/trees/avltree/avltree.go b/trees/avltree/avltree.go new file mode 100644 index 00000000..81b9f3f8 --- /dev/null +++ b/trees/avltree/avltree.go @@ -0,0 +1,471 @@ +// Copyright (c) 2017, Benjamin Scher Purcell. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package avltree implements an AVL balanced binary tree. +// +// Structure is not thread safe. +// +// References: https://en.wikipedia.org/wiki/AVL_tree +package avltree + +import ( + "cmp" + "fmt" + + "github.com/emirpasic/gods/v2/trees" + "github.com/emirpasic/gods/v2/utils" +) + +// Assert Tree implementation +var _ trees.Tree[int] = (*Tree[string, int])(nil) + +// Tree holds elements of the AVL tree. +type Tree[K comparable, V any] struct { + Root *Node[K, V] // Root node + Comparator utils.Comparator[K] // Key comparator + size int // Total number of keys in the tree +} + +// Node is a single element within the tree +type Node[K comparable, V any] struct { + Key K + Value V + Parent *Node[K, V] // Parent node + Children [2]*Node[K, V] // Children nodes + b int8 +} + +// New instantiates an AVL tree with the built-in comparator for K +func New[K cmp.Ordered, V any]() *Tree[K, V] { + return &Tree[K, V]{Comparator: cmp.Compare[K]} +} + +// NewWith instantiates an AVL tree with the custom comparator. +func NewWith[K comparable, V any](comparator utils.Comparator[K]) *Tree[K, V] { + return &Tree[K, V]{Comparator: comparator} +} + +// Put inserts node into the tree. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Put(key K, value V) { + tree.put(key, value, nil, &tree.Root) +} + +// Get searches the node in the tree by key and returns its value or nil if key is not found in tree. +// Second return parameter is true if key was found, otherwise false. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Get(key K) (value V, found bool) { + n := tree.GetNode(key) + if n != nil { + return n.Value, true + } + return value, false +} + +// GetNode searches the node in the tree by key and returns its node or nil if key is not found in tree. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) GetNode(key K) *Node[K, V] { + n := tree.Root + for n != nil { + cmp := tree.Comparator(key, n.Key) + switch { + case cmp == 0: + return n + case cmp < 0: + n = n.Children[0] + case cmp > 0: + n = n.Children[1] + } + } + return n +} + +// Remove remove the node from the tree by key. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Remove(key K) { + tree.remove(key, &tree.Root) +} + +// Empty returns true if tree does not contain any nodes. +func (tree *Tree[K, V]) Empty() bool { + return tree.size == 0 +} + +// Size returns the number of elements stored in the tree. +func (tree *Tree[K, V]) Size() int { + return tree.size +} + +// Size returns the number of elements stored in the subtree. +// Computed dynamically on each call, i.e. the subtree is traversed to count the number of the nodes. +func (n *Node[K, V]) Size() int { + if n == nil { + return 0 + } + size := 1 + if n.Children[0] != nil { + size += n.Children[0].Size() + } + if n.Children[1] != nil { + size += n.Children[1].Size() + } + return size +} + +// Keys returns all keys in-order +func (tree *Tree[K, V]) Keys() []K { + keys := make([]K, tree.size) + it := tree.Iterator() + for i := 0; it.Next(); i++ { + keys[i] = it.Key() + } + return keys +} + +// Values returns all values in-order based on the key. +func (tree *Tree[K, V]) Values() []V { + values := make([]V, tree.size) + it := tree.Iterator() + for i := 0; it.Next(); i++ { + values[i] = it.Value() + } + return values +} + +// Left returns the minimum element of the AVL tree +// or nil if the tree is empty. +func (tree *Tree[K, V]) Left() *Node[K, V] { + return tree.bottom(0) +} + +// Right returns the maximum element of the AVL tree +// or nil if the tree is empty. +func (tree *Tree[K, V]) Right() *Node[K, V] { + return tree.bottom(1) +} + +// Floor Finds floor node of the input key, return the floor node or nil if no floor is found. +// Second return parameter is true if floor was found, otherwise false. +// +// Floor node is defined as the largest node that is smaller than or equal to the given node. +// A floor node may not be found, either because the tree is empty, or because +// all nodes in the tree is larger than the given node. +// +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Floor(key K) (floor *Node[K, V], found bool) { + found = false + n := tree.Root + for n != nil { + c := tree.Comparator(key, n.Key) + switch { + case c == 0: + return n, true + case c < 0: + n = n.Children[0] + case c > 0: + floor, found = n, true + n = n.Children[1] + } + } + if found { + return + } + return nil, false +} + +// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling is found. +// Second return parameter is true if ceiling was found, otherwise false. +// +// Ceiling node is defined as the smallest node that is larger than or equal to the given node. +// A ceiling node may not be found, either because the tree is empty, or because +// all nodes in the tree is smaller than the given node. +// +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Ceiling(key K) (floor *Node[K, V], found bool) { + found = false + n := tree.Root + for n != nil { + c := tree.Comparator(key, n.Key) + switch { + case c == 0: + return n, true + case c < 0: + floor, found = n, true + n = n.Children[0] + case c > 0: + n = n.Children[1] + } + } + if found { + return + } + return nil, false +} + +// Clear removes all nodes from the tree. +func (tree *Tree[K, V]) Clear() { + tree.Root = nil + tree.size = 0 +} + +// String returns a string representation of container +func (tree *Tree[K, V]) String() string { + str := "AVLTree\n" + if !tree.Empty() { + output(tree.Root, "", true, &str) + } + return str +} + +func (n *Node[K, V]) String() string { + return fmt.Sprintf("%v", n.Key) +} + +func (tree *Tree[K, V]) put(key K, value V, p *Node[K, V], qp **Node[K, V]) bool { + q := *qp + if q == nil { + tree.size++ + *qp = &Node[K, V]{Key: key, Value: value, Parent: p} + return true + } + + c := tree.Comparator(key, q.Key) + if c == 0 { + q.Key = key + q.Value = value + return false + } + + if c < 0 { + c = -1 + } else { + c = 1 + } + a := (c + 1) / 2 + var fix bool + fix = tree.put(key, value, q, &q.Children[a]) + if fix { + return putFix(int8(c), qp) + } + return false +} + +func (tree *Tree[K, V]) remove(key K, qp **Node[K, V]) bool { + q := *qp + if q == nil { + return false + } + + c := tree.Comparator(key, q.Key) + if c == 0 { + tree.size-- + if q.Children[1] == nil { + if q.Children[0] != nil { + q.Children[0].Parent = q.Parent + } + *qp = q.Children[0] + return true + } + fix := removeMin(&q.Children[1], &q.Key, &q.Value) + if fix { + return removeFix(-1, qp) + } + return false + } + + if c < 0 { + c = -1 + } else { + c = 1 + } + a := (c + 1) / 2 + fix := tree.remove(key, &q.Children[a]) + if fix { + return removeFix(int8(-c), qp) + } + return false +} + +func removeMin[K comparable, V any](qp **Node[K, V], minKey *K, minVal *V) bool { + q := *qp + if q.Children[0] == nil { + *minKey = q.Key + *minVal = q.Value + if q.Children[1] != nil { + q.Children[1].Parent = q.Parent + } + *qp = q.Children[1] + return true + } + fix := removeMin(&q.Children[0], minKey, minVal) + if fix { + return removeFix(1, qp) + } + return false +} + +func putFix[K comparable, V any](c int8, t **Node[K, V]) bool { + s := *t + if s.b == 0 { + s.b = c + return true + } + + if s.b == -c { + s.b = 0 + return false + } + + if s.Children[(c+1)/2].b == c { + s = singlerot(c, s) + } else { + s = doublerot(c, s) + } + *t = s + return false +} + +func removeFix[K comparable, V any](c int8, t **Node[K, V]) bool { + s := *t + if s.b == 0 { + s.b = c + return false + } + + if s.b == -c { + s.b = 0 + return true + } + + a := (c + 1) / 2 + if s.Children[a].b == 0 { + s = rotate(c, s) + s.b = -c + *t = s + return false + } + + if s.Children[a].b == c { + s = singlerot(c, s) + } else { + s = doublerot(c, s) + } + *t = s + return true +} + +func singlerot[K comparable, V any](c int8, s *Node[K, V]) *Node[K, V] { + s.b = 0 + s = rotate(c, s) + s.b = 0 + return s +} + +func doublerot[K comparable, V any](c int8, s *Node[K, V]) *Node[K, V] { + a := (c + 1) / 2 + r := s.Children[a] + s.Children[a] = rotate(-c, s.Children[a]) + p := rotate(c, s) + + switch { + default: + s.b = 0 + r.b = 0 + case p.b == c: + s.b = -c + r.b = 0 + case p.b == -c: + s.b = 0 + r.b = c + } + + p.b = 0 + return p +} + +func rotate[K comparable, V any](c int8, s *Node[K, V]) *Node[K, V] { + a := (c + 1) / 2 + r := s.Children[a] + s.Children[a] = r.Children[a^1] + if s.Children[a] != nil { + s.Children[a].Parent = s + } + r.Children[a^1] = s + r.Parent = s.Parent + s.Parent = r + return r +} + +func (tree *Tree[K, V]) bottom(d int) *Node[K, V] { + n := tree.Root + if n == nil { + return nil + } + + for c := n.Children[d]; c != nil; c = n.Children[d] { + n = c + } + return n +} + +// Prev returns the previous element in an inorder +// walk of the AVL tree. +func (n *Node[K, V]) Prev() *Node[K, V] { + return n.walk1(0) +} + +// Next returns the next element in an inorder +// walk of the AVL tree. +func (n *Node[K, V]) Next() *Node[K, V] { + return n.walk1(1) +} + +func (n *Node[K, V]) walk1(a int) *Node[K, V] { + if n == nil { + return nil + } + + if n.Children[a] != nil { + n = n.Children[a] + for n.Children[a^1] != nil { + n = n.Children[a^1] + } + return n + } + + p := n.Parent + for p != nil && p.Children[a] == n { + n = p + p = p.Parent + } + return p +} + +func output[K comparable, V any](node *Node[K, V], prefix string, isTail bool, str *string) { + if node.Children[1] != nil { + newPrefix := prefix + if isTail { + newPrefix += "β”‚ " + } else { + newPrefix += " " + } + output(node.Children[1], newPrefix, false, str) + } + *str += prefix + if isTail { + *str += "└── " + } else { + *str += "β”Œβ”€β”€ " + } + *str += node.String() + "\n" + if node.Children[0] != nil { + newPrefix := prefix + if isTail { + newPrefix += " " + } else { + newPrefix += "β”‚ " + } + output(node.Children[0], newPrefix, true, str) + } +} diff --git a/trees/avltree/avltree_test.go b/trees/avltree/avltree_test.go new file mode 100644 index 00000000..346fc479 --- /dev/null +++ b/trees/avltree/avltree_test.go @@ -0,0 +1,871 @@ +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package avltree + +import ( + "encoding/json" + "slices" + "strings" + "testing" +) + +func TestAVLTreeGet(t *testing.T) { + tree := New[int, string]() + + if actualValue := tree.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + if actualValue := tree.GetNode(2).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + // + // AVLTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 2 + // └── 1 + + if actualValue := tree.Size(); actualValue != 6 { + t.Errorf("Got %v expected %v", actualValue, 6) + } + + if actualValue := tree.GetNode(2).Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + + if actualValue := tree.GetNode(4).Size(); actualValue != 6 { + t.Errorf("Got %v expected %v", actualValue, 6) + } + + if actualValue := tree.GetNode(7).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestAVLTreePut(t *testing.T) { + tree := New[int, string]() + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + + if actualValue := tree.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + if actualValue, expectedValue := tree.Keys(), []int{1, 2, 3, 4, 5, 6, 7}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{"a", "b", "c", "d", "e", "f", "g"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tests1 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {8, "", false}, + } + + for _, test := range tests1 { + // retrievals + actualValue, actualFound := tree.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } +} + +func TestAVLTreeRemove(t *testing.T) { + tree := New[int, string]() + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + + tree.Remove(5) + tree.Remove(6) + tree.Remove(7) + tree.Remove(8) + tree.Remove(5) + + if actualValue, expectedValue := tree.Keys(), []int{1, 2, 3, 4}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{"a", "b", "c", "d"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := tree.Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + + tests2 := [][]interface{}{ + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "", false}, + {6, "", false}, + {7, "", false}, + {8, "", false}, + } + + for _, test := range tests2 { + actualValue, actualFound := tree.Get(test[0].(int)) + if actualValue != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } + + tree.Remove(1) + tree.Remove(4) + tree.Remove(2) + tree.Remove(3) + tree.Remove(2) + tree.Remove(2) + + if actualValue, expectedValue := tree.Keys(), []int{}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if empty, size := tree.Empty(), tree.Size(); empty != true || size != -0 { + t.Errorf("Got %v expected %v", empty, true) + } + +} + +func TestAVLTreeLeftAndRight(t *testing.T) { + tree := New[int, string]() + + if actualValue := tree.Left(); actualValue != nil { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue := tree.Right(); actualValue != nil { + t.Errorf("Got %v expected %v", actualValue, nil) + } + + tree.Put(1, "a") + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") // overwrite + tree.Put(2, "b") + + if actualValue, expectedValue := tree.Left().Key, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Left().Value, "x"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + if actualValue, expectedValue := tree.Right().Key, 7; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Right().Value, "g"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeCeilingAndFloor(t *testing.T) { + tree := New[int, string]() + + if node, found := tree.Floor(0); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + if node, found := tree.Ceiling(0); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + + if node, found := tree.Floor(4); node.Key != 4 || !found { + t.Errorf("Got %v expected %v", node.Key, 4) + } + if node, found := tree.Floor(0); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + + if node, found := tree.Ceiling(4); node.Key != 4 || !found { + t.Errorf("Got %v expected %v", node.Key, 4) + } + if node, found := tree.Ceiling(8); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } +} + +func TestAVLTreeIteratorNextOnEmpty(t *testing.T) { + tree := New[int, string]() + it := tree.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestAVLTreeIteratorPrevOnEmpty(t *testing.T) { + tree := New[int, string]() + it := tree.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestAVLTreeIterator1Next(t *testing.T) { + tree := New[int, string]() + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + // β”‚ β”Œβ”€β”€ 7 + // └── 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 2 + // └── 1 + it := tree.Iterator() + + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIterator1Prev(t *testing.T) { + tree := New[int, string]() + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + // β”‚ β”Œβ”€β”€ 7 + // └── 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 2 + // └── 1 + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIterator2Next(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIterator2Prev(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIterator3Next(t *testing.T) { + tree := New[int, string]() + tree.Put(1, "a") + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIterator3Prev(t *testing.T) { + tree := New[int, string]() + tree.Put(1, "a") + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIterator4Next(t *testing.T) { + tree := New[int, int]() + tree.Put(13, 5) + tree.Put(8, 3) + tree.Put(17, 7) + tree.Put(1, 1) + tree.Put(11, 4) + tree.Put(15, 6) + tree.Put(25, 9) + tree.Put(6, 2) + tree.Put(22, 8) + tree.Put(27, 10) + // β”‚ β”Œβ”€β”€ 27 + // β”‚ β”Œβ”€β”€ 25 + // β”‚ β”‚ └── 22 + // β”‚ β”Œβ”€β”€ 17 + // β”‚ β”‚ └── 15 + // └── 13 + // β”‚ β”Œβ”€β”€ 11 + // └── 8 + // β”‚ β”Œβ”€β”€ 6 + // └── 1 + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + value := it.Value() + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIterator4Prev(t *testing.T) { + tree := New[int, int]() + tree.Put(13, 5) + tree.Put(8, 3) + tree.Put(17, 7) + tree.Put(1, 1) + tree.Put(11, 4) + tree.Put(15, 6) + tree.Put(25, 9) + tree.Put(6, 2) + tree.Put(22, 8) + tree.Put(27, 10) + // β”‚ β”Œβ”€β”€ 27 + // β”‚ β”Œβ”€β”€ 25 + // β”‚ β”‚ └── 22 + // β”‚ β”Œβ”€β”€ 17 + // β”‚ β”‚ └── 15 + // └── 13 + // β”‚ β”Œβ”€β”€ 11 + // └── 8 + // β”‚ β”Œβ”€β”€ 6 + // └── 1 + it := tree.Iterator() + count := tree.Size() + for it.Next() { + } + for it.Prev() { + value := it.Value() + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + count-- + } + if actualValue, expectedValue := count, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeIteratorBegin(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + + if it.Key() != 0 { + t.Errorf("Got %v expected %v", it.Key(), 0) + } + + it.Begin() + + if it.Key() != 0 { + t.Errorf("Got %v expected %v", it.Key(), 0) + } + + for it.Next() { + } + + it.Begin() + + if it.Key() != 0 { + t.Errorf("Got %v expected %v", it.Key(), 0) + } + + it.Next() + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestAVLTreeIteratorEnd(t *testing.T) { + tree := New[int, string]() + it := tree.Iterator() + + if it.Key() != 0 { + t.Errorf("Got %v expected %v", it.Key(), 0) + } + + it.End() + if it.Key() != 0 { + t.Errorf("Got %v expected %v", it.Key(), 0) + } + + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it.End() + if it.Key() != 0 { + t.Errorf("Got %v expected %v", it.Key(), 0) + } + + it.Prev() + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestAVLTreeIteratorFirst(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestAVLTreeIteratorLast(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestAVLTreeIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + tree := New[int, string]() + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (not found) + { + tree := New[int, string]() + tree.Put(0, "xx") + tree.Put(1, "yy") + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (found) + { + tree := New[int, string]() + tree.Put(2, "cc") + tree.Put(0, "aa") + tree.Put(1, "bb") + it := tree.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestAVLTreeIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + tree := New[int, string]() + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (not found) + { + tree := New[int, string]() + tree.Put(0, "xx") + tree.Put(1, "yy") + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (found) + { + tree := New[int, string]() + tree.Put(2, "cc") + tree.Put(0, "aa") + tree.Put(1, "bb") + it := tree.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestAVLTreeSerialization(t *testing.T) { + tree := New[string, string]() + tree.Put("c", "3") + tree.Put("b", "2") + tree.Put("a", "1") + + var err error + assert := func() { + if actualValue, expectedValue := tree.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Keys(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{"1", "2", "3"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := tree.ToJSON() + assert() + + err = tree.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", tree}) + if err != nil { + t.Errorf("Got error %v", err) + } + + intTree := New[string, int]() + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), intTree) + if err != nil { + t.Errorf("Got error %v", err) + } + if actualValue, expectedValue := intTree.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := intTree.Keys(), []string{"a", "b"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := intTree.Values(), []int{1, 2}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestAVLTreeString(t *testing.T) { + c := New[int, int]() + c.Put(1, 1) + c.Put(2, 1) + c.Put(3, 1) + c.Put(4, 1) + c.Put(5, 1) + c.Put(6, 1) + c.Put(7, 1) + c.Put(8, 1) + + if !strings.HasPrefix(c.String(), "AVLTree") { + t.Errorf("String should start with container name") + } +} + +func benchmarkGet(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Get(n) + } + } +} + +func benchmarkPut(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + } +} + +func benchmarkRemove(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Remove(n) + } + } +} + +func BenchmarkAVLTreeGet100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkAVLTreeGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkAVLTreeGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkAVLTreeGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkAVLTreePut100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}]() + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkAVLTreePut1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkAVLTreePut10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkAVLTreePut100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkAVLTreeRemove100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkAVLTreeRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkAVLTreeRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkAVLTreeRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} diff --git a/trees/avltree/iterator.go b/trees/avltree/iterator.go new file mode 100644 index 00000000..8541ce0a --- /dev/null +++ b/trees/avltree/iterator.go @@ -0,0 +1,150 @@ +// Copyright (c) 2017, Benjamin Scher Purcell. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package avltree + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithKey[string, int] = (*Iterator[string, int])(nil) + +// Iterator holding the iterator's state +type Iterator[K comparable, V any] struct { + tree *Tree[K, V] + node *Node[K, V] + position position +} + +type position byte + +const ( + begin, between, end position = 0, 1, 2 +) + +// Iterator returns a stateful iterator whose elements are key/value pairs. +func (tree *Tree[K, V]) Iterator() *Iterator[K, V] { + return &Iterator[K, V]{tree: tree, node: nil, position: begin} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Next() bool { + switch iterator.position { + case begin: + iterator.position = between + iterator.node = iterator.tree.Left() + case between: + iterator.node = iterator.node.Next() + } + + if iterator.node == nil { + iterator.position = end + return false + } + return true +} + +// Prev moves the iterator to the next element and returns true if there was a previous element in the container. +// If Prev() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Prev() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Prev() bool { + switch iterator.position { + case end: + iterator.position = between + iterator.node = iterator.tree.Right() + case between: + iterator.node = iterator.node.Prev() + } + + if iterator.node == nil { + iterator.position = begin + return false + } + return true +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Value() (v V) { + if iterator.node == nil { + return v + } + return iterator.node.Value +} + +// Key returns the current element's key. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Key() (k K) { + if iterator.node == nil { + return k + } + return iterator.node.Key +} + +// Node returns the current element's node. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Node() *Node[K, V] { + return iterator.node +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[K, V]) Begin() { + iterator.node = nil + iterator.position = begin +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[K, V]) End() { + iterator.node = nil + iterator.position = end +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator +func (iterator *Iterator[K, V]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) NextTo(f func(key K, value V) bool) bool { + for iterator.Next() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) PrevTo(f func(key K, value V) bool) bool { + for iterator.Prev() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} diff --git a/trees/avltree/serialization.go b/trees/avltree/serialization.go new file mode 100644 index 00000000..4b3da5d3 --- /dev/null +++ b/trees/avltree/serialization.go @@ -0,0 +1,51 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package avltree + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Tree[string, int])(nil) +var _ containers.JSONDeserializer = (*Tree[string, int])(nil) + +// ToJSON outputs the JSON representation of the tree. +func (tree *Tree[K, V]) ToJSON() ([]byte, error) { + elements := make(map[K]V) + it := tree.Iterator() + for it.Next() { + elements[it.Key()] = it.Value() + } + return json.Marshal(&elements) +} + +// FromJSON populates the tree from the input JSON representation. +func (tree *Tree[K, V]) FromJSON(data []byte) error { + elements := make(map[K]V) + err := json.Unmarshal(data, &elements) + if err != nil { + return err + } + + tree.Clear() + for key, value := range elements { + tree.Put(key, value) + } + + return nil +} + +// UnmarshalJSON @implements json.Unmarshaler +func (tree *Tree[K, V]) UnmarshalJSON(bytes []byte) error { + return tree.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (tree *Tree[K, V]) MarshalJSON() ([]byte, error) { + return tree.ToJSON() +} diff --git a/trees/binaryheap/binaryheap.go b/trees/binaryheap/binaryheap.go index 346d787a..9f1605cd 100644 --- a/trees/binaryheap/binaryheap.go +++ b/trees/binaryheap/binaryheap.go @@ -1,77 +1,65 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of binary heap backed by ArrayList. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package binaryheap implements a binary heap backed by array list. +// // Comparator defines this heap as either min or max heap. +// // Structure is not thread safe. +// // References: http://en.wikipedia.org/wiki/Binary_heap - package binaryheap import ( + "cmp" "fmt" - "github.com/emirpasic/gods/lists/arraylist" - "github.com/emirpasic/gods/trees" - "github.com/emirpasic/gods/utils" "strings" -) -func assertInterfaceImplementation() { - var _ trees.Interface = (*Heap)(nil) -} + "github.com/emirpasic/gods/v2/lists/arraylist" + "github.com/emirpasic/gods/v2/trees" + "github.com/emirpasic/gods/v2/utils" +) -type Heap struct { - list *arraylist.List - comparator utils.Comparator -} +// Assert Tree implementation +var _ trees.Tree[int] = (*Heap[int])(nil) -// Instantiates a new empty heap tree with the custom comparator. -func NewWith(comparator utils.Comparator) *Heap { - return &Heap{list: arraylist.New(), comparator: comparator} +// Heap holds elements in an array-list +type Heap[T comparable] struct { + list *arraylist.List[T] + Comparator utils.Comparator[T] } -// Instantiates a new empty heap with the IntComparator, i.e. elements are of type int. -func NewWithIntComparator() *Heap { - return &Heap{list: arraylist.New(), comparator: utils.IntComparator} +// New instantiates a new empty heap tree with the built-in comparator for T +func New[T cmp.Ordered]() *Heap[T] { + return &Heap[T]{list: arraylist.New[T](), Comparator: cmp.Compare[T]} } -// Instantiates a new empty heap with the StringComparator, i.e. elements are of type string. -func NewWithStringComparator() *Heap { - return &Heap{list: arraylist.New(), comparator: utils.StringComparator} +// NewWith instantiates a new empty heap tree with the custom comparator. +func NewWith[T comparable](comparator utils.Comparator[T]) *Heap[T] { + return &Heap[T]{list: arraylist.New[T](), Comparator: comparator} } -// Pushes a value onto the heap and bubbles it up accordingly. -func (heap *Heap) Push(value interface{}) { - heap.list.Add(value) - heap.bubbleUp() +// Push adds a value onto the heap and bubbles it up accordingly. +func (heap *Heap[T]) Push(values ...T) { + if len(values) == 1 { + heap.list.Add(values[0]) + heap.bubbleUp() + } else { + // Reference: https://en.wikipedia.org/wiki/Binary_heap#Building_a_heap + for _, value := range values { + heap.list.Add(value) + } + size := heap.list.Size()/2 + 1 + for i := size; i >= 0; i-- { + heap.bubbleDownIndex(i) + } + } } -// Pops (removes) top element on heap and returns it, or nil if heap is empty. +// Pop removes top element on heap and returns it, or nil if heap is empty. // Second return parameter is true, unless the heap was empty and there was nothing to pop. -func (heap *Heap) Pop() (value interface{}, ok bool) { +func (heap *Heap[T]) Pop() (value T, ok bool) { value, ok = heap.list.Get(0) if !ok { return @@ -83,58 +71,68 @@ func (heap *Heap) Pop() (value interface{}, ok bool) { return } -// Returns top element on the heap without removing it, or nil if heap is empty. +// Peek returns top element on the heap without removing it, or nil if heap is empty. // Second return parameter is true, unless the heap was empty and there was nothing to peek. -func (heap *Heap) Peek() (value interface{}, ok bool) { +func (heap *Heap[T]) Peek() (value T, ok bool) { return heap.list.Get(0) } -// Returns true if heap does not contain any elements. -func (heap *Heap) Empty() bool { +// Empty returns true if heap does not contain any elements. +func (heap *Heap[T]) Empty() bool { return heap.list.Empty() } -// Returns number of elements within the heap. -func (heap *Heap) Size() int { +// Size returns number of elements within the heap. +func (heap *Heap[T]) Size() int { return heap.list.Size() } -// Removes all elements from the heap. -func (heap *Heap) Clear() { +// Clear removes all elements from the heap. +func (heap *Heap[T]) Clear() { heap.list.Clear() } -// Returns all elements in the heap. -func (heap *Heap) Values() []interface{} { - return heap.list.Values() +// Values returns all elements in the heap. +func (heap *Heap[T]) Values() []T { + values := make([]T, heap.list.Size(), heap.list.Size()) + for it := heap.Iterator(); it.Next(); { + values[it.Index()] = it.Value() + } + return values } -func (heap *Heap) String() string { +// String returns a string representation of container +func (heap *Heap[T]) String() string { str := "BinaryHeap\n" values := []string{} - for _, value := range heap.list.Values() { - values = append(values, fmt.Sprintf("%v", value)) + for it := heap.Iterator(); it.Next(); { + values = append(values, fmt.Sprintf("%v", it.Value())) } str += strings.Join(values, ", ") return str } -// Performs the "bubble down" operation. This is to place the element that is at the -// root of the heap in its correct place so that the heap maintains the min/max-heap order property. -func (heap *Heap) bubbleDown() { - index := 0 +// Performs the "bubble down" operation. This is to place the element that is at the root +// of the heap in its correct place so that the heap maintains the min/max-heap order property. +func (heap *Heap[T]) bubbleDown() { + heap.bubbleDownIndex(0) +} + +// Performs the "bubble down" operation. This is to place the element that is at the index +// of the heap in its correct place so that the heap maintains the min/max-heap order property. +func (heap *Heap[T]) bubbleDownIndex(index int) { size := heap.list.Size() for leftIndex := index<<1 + 1; leftIndex < size; leftIndex = index<<1 + 1 { rightIndex := index<<1 + 2 smallerIndex := leftIndex leftValue, _ := heap.list.Get(leftIndex) rightValue, _ := heap.list.Get(rightIndex) - if rightIndex < size && heap.comparator(leftValue, rightValue) > 0 { + if rightIndex < size && heap.Comparator(leftValue, rightValue) > 0 { smallerIndex = rightIndex } indexValue, _ := heap.list.Get(index) smallerValue, _ := heap.list.Get(smallerIndex) - if heap.comparator(indexValue, smallerValue) > 0 { + if heap.Comparator(indexValue, smallerValue) > 0 { heap.list.Swap(index, smallerIndex) } else { break @@ -146,15 +144,20 @@ func (heap *Heap) bubbleDown() { // Performs the "bubble up" operation. This is to place a newly inserted // element (i.e. last element in the list) in its correct place so that // the heap maintains the min/max-heap order property. -func (heap *Heap) bubbleUp() { +func (heap *Heap[T]) bubbleUp() { index := heap.list.Size() - 1 for parentIndex := (index - 1) >> 1; index > 0; parentIndex = (index - 1) >> 1 { indexValue, _ := heap.list.Get(index) parentValue, _ := heap.list.Get(parentIndex) - if heap.comparator(parentValue, indexValue) <= 0 { + if heap.Comparator(parentValue, indexValue) <= 0 { break } heap.list.Swap(index, parentIndex) index = parentIndex } } + +// Check that the index is within bounds of the list +func (heap *Heap[T]) withinRange(index int) bool { + return index >= 0 && index < heap.list.Size() +} diff --git a/trees/binaryheap/binaryheap_test.go b/trees/binaryheap/binaryheap_test.go index 473526fe..5ee8df4e 100644 --- a/trees/binaryheap/binaryheap_test.go +++ b/trees/binaryheap/binaryheap_test.go @@ -1,93 +1,89 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package binaryheap import ( + "encoding/json" "math/rand" + "slices" + "strings" "testing" ) -func TestBinaryHeap(t *testing.T) { - - heap := NewWithIntComparator() +func TestBinaryHeapPush(t *testing.T) { + heap := New[int]() if actualValue := heap.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - // insertions heap.Push(3) - // [3] heap.Push(2) - // [2,3] heap.Push(1) - // [1,3,2](2 swapped with 1, hence last) - if actualValue := heap.Values(); actualValue[0].(int) != 1 || actualValue[1].(int) != 3 || actualValue[2].(int) != 2 { - t.Errorf("Got %v expected %v", actualValue, "[1,2,3]") + if actualValue, expectedValue := heap.Values(), []int{1, 2, 3}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } - if actualValue := heap.Empty(); actualValue != false { t.Errorf("Got %v expected %v", actualValue, false) } - if actualValue := heap.Size(); actualValue != 3 { t.Errorf("Got %v expected %v", actualValue, 3) } - if actualValue, ok := heap.Peek(); actualValue != 1 || !ok { t.Errorf("Got %v expected %v", actualValue, 1) } +} + +func TestBinaryHeapPushBulk(t *testing.T) { + heap := New[int]() + heap.Push(15, 20, 3, 1, 2) + + if actualValue, expectedValue := heap.Values(), []int{1, 2, 3, 15, 20}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, ok := heap.Pop(); actualValue != 1 || !ok { + t.Errorf("Got %v expected %v", actualValue, 1) + } +} + +func TestBinaryHeapPop(t *testing.T) { + heap := New[int]() + + if actualValue := heap.Empty(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + + heap.Push(3) + heap.Push(2) + heap.Push(1) heap.Pop() if actualValue, ok := heap.Peek(); actualValue != 2 || !ok { t.Errorf("Got %v expected %v", actualValue, 2) } - if actualValue, ok := heap.Pop(); actualValue != 2 || !ok { t.Errorf("Got %v expected %v", actualValue, 2) } - if actualValue, ok := heap.Pop(); actualValue != 3 || !ok { t.Errorf("Got %v expected %v", actualValue, 3) } - - if actualValue, ok := heap.Pop(); actualValue != nil || ok { + if actualValue, ok := heap.Pop(); actualValue != 0 || ok { t.Errorf("Got %v expected %v", actualValue, nil) } - if actualValue := heap.Empty(); actualValue != true { t.Errorf("Got %v expected %v", actualValue, true) } - if actualValue := heap.Values(); len(actualValue) != 0 { t.Errorf("Got %v expected %v", actualValue, "[]") } +} + +func TestBinaryHeapRandom(t *testing.T) { + heap := New[int]() rand.Seed(3) for i := 0; i < 10000; i++ { @@ -98,23 +94,431 @@ func TestBinaryHeap(t *testing.T) { prev, _ := heap.Pop() for !heap.Empty() { curr, _ := heap.Pop() - if prev.(int) > curr.(int) { + if prev > curr { t.Errorf("Heap property invalidated. prev: %v current: %v", prev, curr) } prev = curr } +} + +func TestBinaryHeapIteratorOnEmpty(t *testing.T) { + heap := New[int]() + it := heap.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty heap") + } +} +func TestBinaryHeapIteratorNext(t *testing.T) { + heap := New[int]() + heap.Push(3) + heap.Push(2) + heap.Push(1) + + it := heap.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } } -func BenchmarkBinaryHeap(b *testing.B) { +func TestBinaryHeapIteratorPrev(t *testing.T) { + heap := New[int]() + heap.Push(3) + heap.Push(2) + heap.Push(1) + + it := heap.Iterator() + for it.Next() { + } + count := 0 + for it.Prev() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, 3-count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBinaryHeapIteratorBegin(t *testing.T) { + heap := New[int]() + it := heap.Iterator() + it.Begin() + heap.Push(2) + heap.Push(3) + heap.Push(1) + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != 1 { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, 1) + } +} + +func TestBinaryHeapIteratorEnd(t *testing.T) { + heap := New[int]() + it := heap.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + heap.Push(3) + heap.Push(2) + heap.Push(1) + it.End() + if index := it.Index(); index != heap.Size() { + t.Errorf("Got %v expected %v", index, heap.Size()) + } + + it.Prev() + if index, value := it.Index(), it.Value(); index != heap.Size()-1 || value != 3 { + t.Errorf("Got %v,%v expected %v,%v", index, value, heap.Size()-1, 3) + } +} + +func TestBinaryHeapIteratorFirst(t *testing.T) { + heap := New[int]() + it := heap.Iterator() + if actualValue, expectedValue := it.First(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + heap.Push(3) + heap.Push(2) + heap.Push(1) + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != 1 { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, 1) + } +} + +func TestBinaryHeapIteratorLast(t *testing.T) { + tree := New[int]() + it := tree.Iterator() + if actualValue, expectedValue := it.Last(), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + tree.Push(2) + tree.Push(3) + tree.Push(1) + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != 3 { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, 3) + } +} + +func TestBinaryHeapIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + tree := New[string]() + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // NextTo (not found) + { + tree := New[string]() + tree.Push("xx") + tree.Push("yy") + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // NextTo (found) + { + tree := New[string]() + tree.Push("aa") + tree.Push("bb") + tree.Push("cc") + it := tree.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestBinaryHeapIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + tree := New[string]() + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // PrevTo (not found) + { + tree := New[string]() + tree.Push("xx") + tree.Push("yy") + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + } + + // PrevTo (found) + { + tree := New[string]() + tree.Push("aa") + tree.Push("bb") + tree.Push("cc") + it := tree.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty list") + } + if index, value := it.Index(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Index(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestBinaryHeapSerialization(t *testing.T) { + heap := New[string]() + + heap.Push("c") + heap.Push("b") + heap.Push("a") + + var err error + assert := func() { + if actualValue, expectedValue := heap.Values(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := heap.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + if actualValue, ok := heap.Peek(); actualValue != "a" || !ok { + t.Errorf("Got %v expected %v", actualValue, "a") + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := heap.ToJSON() + assert() + + err = heap.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", heap}) + if err != nil { + t.Errorf("Got error %v", err) + } + + intHeap := New[int]() + err = json.Unmarshal([]byte(`[1,2,3]`), &intHeap) + if err != nil { + t.Errorf("Got error %v", err) + } + if actualValue, expectedValue := intHeap.Values(), []int{1, 2, 3}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeString(t *testing.T) { + c := New[int]() + c.Push(1) + if !strings.HasPrefix(c.String(), "BinaryHeap") { + t.Errorf("String should start with container name") + } +} + +func benchmarkPush(b *testing.B, heap *Heap[int], size int) { for i := 0; i < b.N; i++ { - heap := NewWithIntComparator() - for n := 0; n < 1000; n++ { - heap.Push(i) + for n := 0; n < size; n++ { + heap.Push(n) } - for !heap.Empty() { + } +} + +func benchmarkPop(b *testing.B, heap *Heap[int], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { heap.Pop() } } +} + +func BenchmarkBinaryHeapPop100(b *testing.B) { + b.StopTimer() + size := 100 + heap := New[int]() + for n := 0; n < size; n++ { + heap.Push(n) + } + b.StartTimer() + benchmarkPop(b, heap, size) +} + +func BenchmarkBinaryHeapPop1000(b *testing.B) { + b.StopTimer() + size := 1000 + heap := New[int]() + for n := 0; n < size; n++ { + heap.Push(n) + } + b.StartTimer() + benchmarkPop(b, heap, size) +} + +func BenchmarkBinaryHeapPop10000(b *testing.B) { + b.StopTimer() + size := 10000 + heap := New[int]() + for n := 0; n < size; n++ { + heap.Push(n) + } + b.StartTimer() + benchmarkPop(b, heap, size) +} + +func BenchmarkBinaryHeapPop100000(b *testing.B) { + b.StopTimer() + size := 100000 + heap := New[int]() + for n := 0; n < size; n++ { + heap.Push(n) + } + b.StartTimer() + benchmarkPop(b, heap, size) +} + +func BenchmarkBinaryHeapPush100(b *testing.B) { + b.StopTimer() + size := 100 + heap := New[int]() + b.StartTimer() + benchmarkPush(b, heap, size) +} + +func BenchmarkBinaryHeapPush1000(b *testing.B) { + b.StopTimer() + size := 1000 + heap := New[int]() + for n := 0; n < size; n++ { + heap.Push(n) + } + b.StartTimer() + benchmarkPush(b, heap, size) +} +func BenchmarkBinaryHeapPush10000(b *testing.B) { + b.StopTimer() + size := 10000 + heap := New[int]() + for n := 0; n < size; n++ { + heap.Push(n) + } + b.StartTimer() + benchmarkPush(b, heap, size) +} + +func BenchmarkBinaryHeapPush100000(b *testing.B) { + b.StopTimer() + size := 100000 + heap := New[int]() + for n := 0; n < size; n++ { + heap.Push(n) + } + b.StartTimer() + benchmarkPush(b, heap, size) } diff --git a/trees/binaryheap/iterator.go b/trees/binaryheap/iterator.go new file mode 100644 index 00000000..73ff1815 --- /dev/null +++ b/trees/binaryheap/iterator.go @@ -0,0 +1,143 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package binaryheap + +import ( + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithIndex[int] = (*Iterator[int])(nil) + +// Iterator returns a stateful iterator whose values can be fetched by an index. +type Iterator[T comparable] struct { + heap *Heap[T] + index int +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (heap *Heap[T]) Iterator() *Iterator[T] { + return &Iterator[T]{heap: heap, index: -1} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Next() bool { + if iterator.index < iterator.heap.Size() { + iterator.index++ + } + return iterator.heap.withinRange(iterator.index) +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Prev() bool { + if iterator.index >= 0 { + iterator.index-- + } + return iterator.heap.withinRange(iterator.index) +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Value() T { + start, end := evaluateRange(iterator.index) + if end > iterator.heap.Size() { + end = iterator.heap.Size() + } + tmpHeap := NewWith(iterator.heap.Comparator) + for n := start; n < end; n++ { + value, _ := iterator.heap.list.Get(n) + tmpHeap.Push(value) + } + for n := 0; n < iterator.index-start; n++ { + tmpHeap.Pop() + } + value, _ := tmpHeap.Pop() + return value +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator[T]) Index() int { + return iterator.index +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[T]) Begin() { + iterator.index = -1 +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[T]) End() { + iterator.index = iterator.heap.Size() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) NextTo(f func(index int, value T) bool) bool { + for iterator.Next() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[T]) PrevTo(f func(index int, value T) bool) bool { + for iterator.Prev() { + index, value := iterator.Index(), iterator.Value() + if f(index, value) { + return true + } + } + return false +} + +// numOfBits counts the number of bits of an int +func numOfBits(n int) uint { + var count uint + for n != 0 { + count++ + n >>= 1 + } + return count +} + +// evaluateRange evaluates the index range [start,end) of same level nodes in the heap as the index +func evaluateRange(index int) (start int, end int) { + bits := numOfBits(index+1) - 1 + start = 1< tree.maxEntries() +} + +func (tree *Tree[K, V]) maxChildren() int { + return tree.m +} + +func (tree *Tree[K, V]) minChildren() int { + return (tree.m + 1) / 2 // ceil(m/2) +} + +func (tree *Tree[K, V]) maxEntries() int { + return tree.maxChildren() - 1 +} + +func (tree *Tree[K, V]) minEntries() int { + return tree.minChildren() - 1 +} + +func (tree *Tree[K, V]) middle() int { + return (tree.m - 1) / 2 // "-1" to favor right nodes to have more keys when splitting +} + +// search searches only within the single node among its entries +func (tree *Tree[K, V]) search(node *Node[K, V], key K) (index int, found bool) { + low, high := 0, len(node.Entries)-1 + var mid int + for low <= high { + mid = (high + low) / 2 + compare := tree.Comparator(key, node.Entries[mid].Key) + switch { + case compare > 0: + low = mid + 1 + case compare < 0: + high = mid - 1 + case compare == 0: + return mid, true + } + } + return low, false +} + +// searchRecursively searches recursively down the tree starting at the startNode +func (tree *Tree[K, V]) searchRecursively(startNode *Node[K, V], key K) (node *Node[K, V], index int, found bool) { + if tree.Empty() { + return nil, -1, false + } + node = startNode + for { + index, found = tree.search(node, key) + if found { + return node, index, true + } + if tree.isLeaf(node) { + return nil, -1, false + } + node = node.Children[index] + } +} + +func (tree *Tree[K, V]) insert(node *Node[K, V], entry *Entry[K, V]) (inserted bool) { + if tree.isLeaf(node) { + return tree.insertIntoLeaf(node, entry) + } + return tree.insertIntoInternal(node, entry) +} + +func (tree *Tree[K, V]) insertIntoLeaf(node *Node[K, V], entry *Entry[K, V]) (inserted bool) { + insertPosition, found := tree.search(node, entry.Key) + if found { + node.Entries[insertPosition] = entry + return false + } + // Insert entry's key in the middle of the node + node.Entries = append(node.Entries, nil) + copy(node.Entries[insertPosition+1:], node.Entries[insertPosition:]) + node.Entries[insertPosition] = entry + tree.split(node) + return true +} + +func (tree *Tree[K, V]) insertIntoInternal(node *Node[K, V], entry *Entry[K, V]) (inserted bool) { + insertPosition, found := tree.search(node, entry.Key) + if found { + node.Entries[insertPosition] = entry + return false + } + return tree.insert(node.Children[insertPosition], entry) +} + +func (tree *Tree[K, V]) split(node *Node[K, V]) { + if !tree.shouldSplit(node) { + return + } + + if node == tree.Root { + tree.splitRoot() + return + } + + tree.splitNonRoot(node) +} + +func (tree *Tree[K, V]) splitNonRoot(node *Node[K, V]) { + middle := tree.middle() + parent := node.Parent + + left := &Node[K, V]{Entries: append([]*Entry[K, V](nil), node.Entries[:middle]...), Parent: parent} + right := &Node[K, V]{Entries: append([]*Entry[K, V](nil), node.Entries[middle+1:]...), Parent: parent} + + // Move children from the node to be split into left and right nodes + if !tree.isLeaf(node) { + left.Children = append([]*Node[K, V](nil), node.Children[:middle+1]...) + right.Children = append([]*Node[K, V](nil), node.Children[middle+1:]...) + setParent(left.Children, left) + setParent(right.Children, right) + } + + insertPosition, _ := tree.search(parent, node.Entries[middle].Key) + + // Insert middle key into parent + parent.Entries = append(parent.Entries, nil) + copy(parent.Entries[insertPosition+1:], parent.Entries[insertPosition:]) + parent.Entries[insertPosition] = node.Entries[middle] + + // Set child left of inserted key in parent to the created left node + parent.Children[insertPosition] = left + + // Set child right of inserted key in parent to the created right node + parent.Children = append(parent.Children, nil) + copy(parent.Children[insertPosition+2:], parent.Children[insertPosition+1:]) + parent.Children[insertPosition+1] = right + + tree.split(parent) +} + +func (tree *Tree[K, V]) splitRoot() { + middle := tree.middle() + + left := &Node[K, V]{Entries: append([]*Entry[K, V](nil), tree.Root.Entries[:middle]...)} + right := &Node[K, V]{Entries: append([]*Entry[K, V](nil), tree.Root.Entries[middle+1:]...)} + + // Move children from the node to be split into left and right nodes + if !tree.isLeaf(tree.Root) { + left.Children = append([]*Node[K, V](nil), tree.Root.Children[:middle+1]...) + right.Children = append([]*Node[K, V](nil), tree.Root.Children[middle+1:]...) + setParent(left.Children, left) + setParent(right.Children, right) + } + + // Root is a node with one entry and two children (left and right) + newRoot := &Node[K, V]{ + Entries: []*Entry[K, V]{tree.Root.Entries[middle]}, + Children: []*Node[K, V]{left, right}, + } + + left.Parent = newRoot + right.Parent = newRoot + tree.Root = newRoot +} + +func setParent[K comparable, V any](nodes []*Node[K, V], parent *Node[K, V]) { + for _, node := range nodes { + node.Parent = parent + } +} + +func (tree *Tree[K, V]) left(node *Node[K, V]) *Node[K, V] { + if tree.Empty() { + return nil + } + current := node + for { + if tree.isLeaf(current) { + return current + } + current = current.Children[0] + } +} + +func (tree *Tree[K, V]) right(node *Node[K, V]) *Node[K, V] { + if tree.Empty() { + return nil + } + current := node + for { + if tree.isLeaf(current) { + return current + } + current = current.Children[len(current.Children)-1] + } +} + +// leftSibling returns the node's left sibling and child index (in parent) if it exists, otherwise (nil,-1) +// key is any of keys in node (could even be deleted). +func (tree *Tree[K, V]) leftSibling(node *Node[K, V], key K) (*Node[K, V], int) { + if node.Parent != nil { + index, _ := tree.search(node.Parent, key) + index-- + if index >= 0 && index < len(node.Parent.Children) { + return node.Parent.Children[index], index + } + } + return nil, -1 +} + +// rightSibling returns the node's right sibling and child index (in parent) if it exists, otherwise (nil,-1) +// key is any of keys in node (could even be deleted). +func (tree *Tree[K, V]) rightSibling(node *Node[K, V], key K) (*Node[K, V], int) { + if node.Parent != nil { + index, _ := tree.search(node.Parent, key) + index++ + if index < len(node.Parent.Children) { + return node.Parent.Children[index], index + } + } + return nil, -1 +} + +// delete deletes an entry in node at entries' index +// ref.: https://en.wikipedia.org/wiki/B-tree#Deletion +func (tree *Tree[K, V]) delete(node *Node[K, V], index int) { + // deleting from a leaf node + if tree.isLeaf(node) { + deletedKey := node.Entries[index].Key + tree.deleteEntry(node, index) + tree.rebalance(node, deletedKey) + if len(tree.Root.Entries) == 0 { + tree.Root = nil + } + return + } + + // deleting from an internal node + leftLargestNode := tree.right(node.Children[index]) // largest node in the left sub-tree (assumed to exist) + leftLargestEntryIndex := len(leftLargestNode.Entries) - 1 + node.Entries[index] = leftLargestNode.Entries[leftLargestEntryIndex] + deletedKey := leftLargestNode.Entries[leftLargestEntryIndex].Key + tree.deleteEntry(leftLargestNode, leftLargestEntryIndex) + tree.rebalance(leftLargestNode, deletedKey) +} + +// rebalance rebalances the tree after deletion if necessary and returns true, otherwise false. +// Note that we first delete the entry and then call rebalance, thus the passed deleted key as reference. +func (tree *Tree[K, V]) rebalance(node *Node[K, V], deletedKey K) { + // check if rebalancing is needed + if node == nil || len(node.Entries) >= tree.minEntries() { + return + } + + // try to borrow from left sibling + leftSibling, leftSiblingIndex := tree.leftSibling(node, deletedKey) + if leftSibling != nil && len(leftSibling.Entries) > tree.minEntries() { + // rotate right + node.Entries = append([]*Entry[K, V]{node.Parent.Entries[leftSiblingIndex]}, node.Entries...) // prepend parent's separator entry to node's entries + node.Parent.Entries[leftSiblingIndex] = leftSibling.Entries[len(leftSibling.Entries)-1] + tree.deleteEntry(leftSibling, len(leftSibling.Entries)-1) + if !tree.isLeaf(leftSibling) { + leftSiblingRightMostChild := leftSibling.Children[len(leftSibling.Children)-1] + leftSiblingRightMostChild.Parent = node + node.Children = append([]*Node[K, V]{leftSiblingRightMostChild}, node.Children...) + tree.deleteChild(leftSibling, len(leftSibling.Children)-1) + } + return + } + + // try to borrow from right sibling + rightSibling, rightSiblingIndex := tree.rightSibling(node, deletedKey) + if rightSibling != nil && len(rightSibling.Entries) > tree.minEntries() { + // rotate left + node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1]) // append parent's separator entry to node's entries + node.Parent.Entries[rightSiblingIndex-1] = rightSibling.Entries[0] + tree.deleteEntry(rightSibling, 0) + if !tree.isLeaf(rightSibling) { + rightSiblingLeftMostChild := rightSibling.Children[0] + rightSiblingLeftMostChild.Parent = node + node.Children = append(node.Children, rightSiblingLeftMostChild) + tree.deleteChild(rightSibling, 0) + } + return + } + + // merge with siblings + if rightSibling != nil { + // merge with right sibling + node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1]) + node.Entries = append(node.Entries, rightSibling.Entries...) + deletedKey = node.Parent.Entries[rightSiblingIndex-1].Key + tree.deleteEntry(node.Parent, rightSiblingIndex-1) + tree.appendChildren(node.Parent.Children[rightSiblingIndex], node) + tree.deleteChild(node.Parent, rightSiblingIndex) + } else if leftSibling != nil { + // merge with left sibling + entries := append([]*Entry[K, V](nil), leftSibling.Entries...) + entries = append(entries, node.Parent.Entries[leftSiblingIndex]) + node.Entries = append(entries, node.Entries...) + deletedKey = node.Parent.Entries[leftSiblingIndex].Key + tree.deleteEntry(node.Parent, leftSiblingIndex) + tree.prependChildren(node.Parent.Children[leftSiblingIndex], node) + tree.deleteChild(node.Parent, leftSiblingIndex) + } + + // make the merged node the root if its parent was the root and the root is empty + if node.Parent == tree.Root && len(tree.Root.Entries) == 0 { + tree.Root = node + node.Parent = nil + return + } + + // parent might underflow, so try to rebalance if necessary + tree.rebalance(node.Parent, deletedKey) +} + +func (tree *Tree[K, V]) prependChildren(fromNode *Node[K, V], toNode *Node[K, V]) { + children := append([]*Node[K, V](nil), fromNode.Children...) + toNode.Children = append(children, toNode.Children...) + setParent(fromNode.Children, toNode) +} + +func (tree *Tree[K, V]) appendChildren(fromNode *Node[K, V], toNode *Node[K, V]) { + toNode.Children = append(toNode.Children, fromNode.Children...) + setParent(fromNode.Children, toNode) +} + +func (tree *Tree[K, V]) deleteEntry(node *Node[K, V], index int) { + copy(node.Entries[index:], node.Entries[index+1:]) + node.Entries[len(node.Entries)-1] = nil + node.Entries = node.Entries[:len(node.Entries)-1] +} + +func (tree *Tree[K, V]) deleteChild(node *Node[K, V], index int) { + if index >= len(node.Children) { + return + } + copy(node.Children[index:], node.Children[index+1:]) + node.Children[len(node.Children)-1] = nil + node.Children = node.Children[:len(node.Children)-1] +} diff --git a/trees/btree/btree_test.go b/trees/btree/btree_test.go new file mode 100644 index 00000000..c6af7c0e --- /dev/null +++ b/trees/btree/btree_test.go @@ -0,0 +1,1385 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btree + +import ( + "encoding/json" + "slices" + "strings" + "testing" +) + +func TestBTreeGet1(t *testing.T) { + tree := New[int, string](3) + tree.Put(1, "a") + tree.Put(2, "b") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + + tests := [][]interface{}{ + {0, "", false}, + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {8, "", false}, + } + + for _, test := range tests { + if value, found := tree.Get(test[0].(int)); value != test[1] || found != test[2] { + t.Errorf("Got %v,%v expected %v,%v", value, found, test[1], test[2]) + } + } +} + +func TestBTreeGet2(t *testing.T) { + tree := New[int, string](3) + tree.Put(7, "g") + tree.Put(9, "i") + tree.Put(10, "j") + tree.Put(6, "f") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(5, "e") + tree.Put(8, "h") + tree.Put(2, "b") + tree.Put(1, "a") + + tests := [][]interface{}{ + {0, "", false}, + {1, "a", true}, + {2, "b", true}, + {3, "c", true}, + {4, "d", true}, + {5, "e", true}, + {6, "f", true}, + {7, "g", true}, + {8, "h", true}, + {9, "i", true}, + {10, "j", true}, + {11, "", false}, + } + + for _, test := range tests { + if value, found := tree.Get(test[0].(int)); value != test[1] || found != test[2] { + t.Errorf("Got %v,%v expected %v,%v", value, found, test[1], test[2]) + } + } +} + +func TestBTreeGet3(t *testing.T) { + tree := New[int, string](3) + + if actualValue := tree.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + if actualValue := tree.GetNode(2).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + tree.Put(7, "g") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f, 7->g (in order) + + // BTree + // 1 + // 2 + // 3 + // 4 + // 5 + // 6 + // 7 + + if actualValue := tree.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + + if actualValue := tree.GetNode(2).Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + + if actualValue := tree.GetNode(4).Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + + if actualValue := tree.GetNode(8).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestBTreePut1(t *testing.T) { + // https://upload.wikimedia.org/wikipedia/commons/3/33/B_tree_insertion_example.png + tree := New[int, int](3) + assertValidTree(t, tree, 0) + + tree.Put(1, 0) + assertValidTree(t, tree, 1) + assertValidTreeNode(t, tree.Root, 1, 0, []int{1}, false) + + tree.Put(2, 1) + assertValidTree(t, tree, 2) + assertValidTreeNode(t, tree.Root, 2, 0, []int{1, 2}, false) + + tree.Put(3, 2) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 1, 2, []int{2}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{3}, true) + + tree.Put(4, 2) + assertValidTree(t, tree, 4) + assertValidTreeNode(t, tree.Root, 1, 2, []int{2}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 2, 0, []int{3, 4}, true) + + tree.Put(5, 2) + assertValidTree(t, tree, 5) + assertValidTreeNode(t, tree.Root, 2, 3, []int{2, 4}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[2], 1, 0, []int{5}, true) + + tree.Put(6, 2) + assertValidTree(t, tree, 6) + assertValidTreeNode(t, tree.Root, 2, 3, []int{2, 4}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[2], 2, 0, []int{5, 6}, true) + + tree.Put(7, 2) + assertValidTree(t, tree, 7) + assertValidTreeNode(t, tree.Root, 1, 2, []int{4}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{6}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{7}, true) +} + +func TestBTreePut2(t *testing.T) { + tree := New[int, int](4) + assertValidTree(t, tree, 0) + + tree.Put(0, 0) + assertValidTree(t, tree, 1) + assertValidTreeNode(t, tree.Root, 1, 0, []int{0}, false) + + tree.Put(2, 2) + assertValidTree(t, tree, 2) + assertValidTreeNode(t, tree.Root, 2, 0, []int{0, 2}, false) + + tree.Put(1, 1) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 3, 0, []int{0, 1, 2}, false) + + tree.Put(1, 1) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 3, 0, []int{0, 1, 2}, false) + + tree.Put(3, 3) + assertValidTree(t, tree, 4) + assertValidTreeNode(t, tree.Root, 1, 2, []int{1}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[1], 2, 0, []int{2, 3}, true) + + tree.Put(4, 4) + assertValidTree(t, tree, 5) + assertValidTreeNode(t, tree.Root, 1, 2, []int{1}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[1], 3, 0, []int{2, 3, 4}, true) + + tree.Put(5, 5) + assertValidTree(t, tree, 6) + assertValidTreeNode(t, tree.Root, 2, 3, []int{1, 3}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[2], 2, 0, []int{4, 5}, true) +} + +func TestBTreePut3(t *testing.T) { + // http://www.geeksforgeeks.org/b-tree-set-1-insert-2/ + tree := New[int, int](6) + assertValidTree(t, tree, 0) + + tree.Put(10, 0) + assertValidTree(t, tree, 1) + assertValidTreeNode(t, tree.Root, 1, 0, []int{10}, false) + + tree.Put(20, 1) + assertValidTree(t, tree, 2) + assertValidTreeNode(t, tree.Root, 2, 0, []int{10, 20}, false) + + tree.Put(30, 2) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 3, 0, []int{10, 20, 30}, false) + + tree.Put(40, 3) + assertValidTree(t, tree, 4) + assertValidTreeNode(t, tree.Root, 4, 0, []int{10, 20, 30, 40}, false) + + tree.Put(50, 4) + assertValidTree(t, tree, 5) + assertValidTreeNode(t, tree.Root, 5, 0, []int{10, 20, 30, 40, 50}, false) + + tree.Put(60, 5) + assertValidTree(t, tree, 6) + assertValidTreeNode(t, tree.Root, 1, 2, []int{30}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{10, 20}, true) + assertValidTreeNode(t, tree.Root.Children[1], 3, 0, []int{40, 50, 60}, true) + + tree.Put(70, 6) + assertValidTree(t, tree, 7) + assertValidTreeNode(t, tree.Root, 1, 2, []int{30}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{10, 20}, true) + assertValidTreeNode(t, tree.Root.Children[1], 4, 0, []int{40, 50, 60, 70}, true) + + tree.Put(80, 7) + assertValidTree(t, tree, 8) + assertValidTreeNode(t, tree.Root, 1, 2, []int{30}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{10, 20}, true) + assertValidTreeNode(t, tree.Root.Children[1], 5, 0, []int{40, 50, 60, 70, 80}, true) + + tree.Put(90, 8) + assertValidTree(t, tree, 9) + assertValidTreeNode(t, tree.Root, 2, 3, []int{30, 60}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{10, 20}, true) + assertValidTreeNode(t, tree.Root.Children[1], 2, 0, []int{40, 50}, true) + assertValidTreeNode(t, tree.Root.Children[2], 3, 0, []int{70, 80, 90}, true) +} + +func TestBTreePut4(t *testing.T) { + tree := New[int, *struct{}](3) + assertValidTree(t, tree, 0) + + tree.Put(6, nil) + assertValidTree(t, tree, 1) + assertValidTreeNode(t, tree.Root, 1, 0, []int{6}, false) + + tree.Put(5, nil) + assertValidTree(t, tree, 2) + assertValidTreeNode(t, tree.Root, 2, 0, []int{5, 6}, false) + + tree.Put(4, nil) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 1, 2, []int{5}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{6}, true) + + tree.Put(3, nil) + assertValidTree(t, tree, 4) + assertValidTreeNode(t, tree.Root, 1, 2, []int{5}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{3, 4}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{6}, true) + + tree.Put(2, nil) + assertValidTree(t, tree, 5) + assertValidTreeNode(t, tree.Root, 2, 3, []int{3, 5}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[2], 1, 0, []int{6}, true) + + tree.Put(1, nil) + assertValidTree(t, tree, 6) + assertValidTreeNode(t, tree.Root, 2, 3, []int{3, 5}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{1, 2}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[2], 1, 0, []int{6}, true) + + tree.Put(0, nil) + assertValidTree(t, tree, 7) + assertValidTreeNode(t, tree.Root, 1, 2, []int{3}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{6}, true) + + tree.Put(-1, nil) + assertValidTree(t, tree, 8) + assertValidTreeNode(t, tree.Root, 1, 2, []int{3}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 2, 0, []int{-1, 0}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{6}, true) + + tree.Put(-2, nil) + assertValidTree(t, tree, 9) + assertValidTreeNode(t, tree.Root, 1, 2, []int{3}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 3, []int{-1, 1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 1, 0, []int{-2}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[2], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{6}, true) + + tree.Put(-3, nil) + assertValidTree(t, tree, 10) + assertValidTreeNode(t, tree.Root, 1, 2, []int{3}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 3, []int{-1, 1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 2, 0, []int{-3, -2}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[2], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{6}, true) + + tree.Put(-4, nil) + assertValidTree(t, tree, 11) + assertValidTreeNode(t, tree.Root, 2, 3, []int{-1, 3}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{-3}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[2], 1, 2, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 1, 0, []int{-4}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{-2}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[2].Children[0], 1, 0, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[2].Children[1], 1, 0, []int{6}, true) +} + +func TestBTreeRemove1(t *testing.T) { + // empty + tree := New[int, int](3) + tree.Remove(1) + assertValidTree(t, tree, 0) +} + +func TestBTreeRemove2(t *testing.T) { + // leaf node (no underflow) + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + + tree.Remove(1) + assertValidTree(t, tree, 1) + assertValidTreeNode(t, tree.Root, 1, 0, []int{2}, false) + + tree.Remove(2) + assertValidTree(t, tree, 0) +} + +func TestBTreeRemove3(t *testing.T) { + // merge with right (underflow) + { + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + tree.Put(3, nil) + + tree.Remove(1) + assertValidTree(t, tree, 2) + assertValidTreeNode(t, tree.Root, 2, 0, []int{2, 3}, false) + } + // merge with left (underflow) + { + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + tree.Put(3, nil) + + tree.Remove(3) + assertValidTree(t, tree, 2) + assertValidTreeNode(t, tree.Root, 2, 0, []int{1, 2}, false) + } +} + +func TestBTreeRemove4(t *testing.T) { + // rotate left (underflow) + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + tree.Put(3, nil) + tree.Put(4, nil) + + assertValidTree(t, tree, 4) + assertValidTreeNode(t, tree.Root, 1, 2, []int{2}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 2, 0, []int{3, 4}, true) + + tree.Remove(1) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 1, 2, []int{3}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{4}, true) +} + +func TestBTreeRemove5(t *testing.T) { + // rotate right (underflow) + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + tree.Put(3, nil) + tree.Put(0, nil) + + assertValidTree(t, tree, 4) + assertValidTreeNode(t, tree.Root, 1, 2, []int{2}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{0, 1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{3}, true) + + tree.Remove(3) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 1, 2, []int{1}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{0}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{2}, true) +} + +func TestBTreeRemove6(t *testing.T) { + // root height reduction after a series of underflows on right side + // use simulator: https://www.cs.usfca.edu/~galles/visualization/BTree.html + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + tree.Put(3, nil) + tree.Put(4, nil) + tree.Put(5, nil) + tree.Put(6, nil) + tree.Put(7, nil) + + assertValidTree(t, tree, 7) + assertValidTreeNode(t, tree.Root, 1, 2, []int{4}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{6}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{7}, true) + + tree.Remove(7) + assertValidTree(t, tree, 6) + assertValidTreeNode(t, tree.Root, 2, 3, []int{2, 4}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[2], 2, 0, []int{5, 6}, true) +} + +func TestBTreeRemove7(t *testing.T) { + // root height reduction after a series of underflows on left side + // use simulator: https://www.cs.usfca.edu/~galles/visualization/BTree.html + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + tree.Put(3, nil) + tree.Put(4, nil) + tree.Put(5, nil) + tree.Put(6, nil) + tree.Put(7, nil) + + assertValidTree(t, tree, 7) + assertValidTreeNode(t, tree.Root, 1, 2, []int{4}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{6}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{7}, true) + + tree.Remove(1) // series of underflows + assertValidTree(t, tree, 6) + assertValidTreeNode(t, tree.Root, 2, 3, []int{4, 6}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{2, 3}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[2], 1, 0, []int{7}, true) + + // clear all remaining + tree.Remove(2) + assertValidTree(t, tree, 5) + assertValidTreeNode(t, tree.Root, 2, 3, []int{4, 6}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[2], 1, 0, []int{7}, true) + + tree.Remove(3) + assertValidTree(t, tree, 4) + assertValidTreeNode(t, tree.Root, 1, 2, []int{6}, false) + assertValidTreeNode(t, tree.Root.Children[0], 2, 0, []int{4, 5}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{7}, true) + + tree.Remove(4) + assertValidTree(t, tree, 3) + assertValidTreeNode(t, tree.Root, 1, 2, []int{6}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 0, []int{7}, true) + + tree.Remove(5) + assertValidTree(t, tree, 2) + assertValidTreeNode(t, tree.Root, 2, 0, []int{6, 7}, false) + + tree.Remove(6) + assertValidTree(t, tree, 1) + assertValidTreeNode(t, tree.Root, 1, 0, []int{7}, false) + + tree.Remove(7) + assertValidTree(t, tree, 0) +} + +func TestBTreeRemove8(t *testing.T) { + // use simulator: https://www.cs.usfca.edu/~galles/visualization/BTree.html + tree := New[int, *struct{}](3) + tree.Put(1, nil) + tree.Put(2, nil) + tree.Put(3, nil) + tree.Put(4, nil) + tree.Put(5, nil) + tree.Put(6, nil) + tree.Put(7, nil) + tree.Put(8, nil) + tree.Put(9, nil) + + assertValidTree(t, tree, 9) + assertValidTreeNode(t, tree.Root, 1, 2, []int{4}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{2}, true) + assertValidTreeNode(t, tree.Root.Children[1], 2, 3, []int{6, 8}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 1, 0, []int{1}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{3}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{7}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[2], 1, 0, []int{9}, true) + + tree.Remove(1) + assertValidTree(t, tree, 8) + assertValidTreeNode(t, tree.Root, 1, 2, []int{6}, false) + assertValidTreeNode(t, tree.Root.Children[0], 1, 2, []int{4}, true) + assertValidTreeNode(t, tree.Root.Children[1], 1, 2, []int{8}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[0], 2, 0, []int{2, 3}, true) + assertValidTreeNode(t, tree.Root.Children[0].Children[1], 1, 0, []int{5}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[0], 1, 0, []int{7}, true) + assertValidTreeNode(t, tree.Root.Children[1].Children[1], 1, 0, []int{9}, true) +} + +func TestBTreeRemove9(t *testing.T) { + const max = 1000 + orders := []int{3, 4, 5, 6, 7, 8, 9, 10, 20, 100, 500, 1000, 5000, 10000} + for _, order := range orders { + + tree := New[int, int](order) + + { + for i := 1; i <= max; i++ { + tree.Put(i, i) + } + assertValidTree(t, tree, max) + + for i := 1; i <= max; i++ { + if _, found := tree.Get(i); !found { + t.Errorf("Not found %v", i) + } + } + + for i := 1; i <= max; i++ { + tree.Remove(i) + } + assertValidTree(t, tree, 0) + } + + { + for i := max; i > 0; i-- { + tree.Put(i, i) + } + assertValidTree(t, tree, max) + + for i := max; i > 0; i-- { + if _, found := tree.Get(i); !found { + t.Errorf("Not found %v", i) + } + } + + for i := max; i > 0; i-- { + tree.Remove(i) + } + assertValidTree(t, tree, 0) + } + } +} + +func TestBTreeHeight(t *testing.T) { + tree := New[int, int](3) + if actualValue, expectedValue := tree.Height(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Put(1, 0) + if actualValue, expectedValue := tree.Height(), 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Put(2, 1) + if actualValue, expectedValue := tree.Height(), 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Put(3, 2) + if actualValue, expectedValue := tree.Height(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Put(4, 2) + if actualValue, expectedValue := tree.Height(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Put(5, 2) + if actualValue, expectedValue := tree.Height(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Put(6, 2) + if actualValue, expectedValue := tree.Height(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Put(7, 2) + if actualValue, expectedValue := tree.Height(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + tree.Remove(1) + tree.Remove(2) + tree.Remove(3) + tree.Remove(4) + tree.Remove(5) + tree.Remove(6) + tree.Remove(7) + if actualValue, expectedValue := tree.Height(), 0; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeLeftAndRight(t *testing.T) { + tree := New[int, string](3) + + if actualValue := tree.Left(); actualValue != nil { + t.Errorf("Got %v expected %v", actualValue, nil) + } + if actualValue := tree.Right(); actualValue != nil { + t.Errorf("Got %v expected %v", actualValue, nil) + } + + tree.Put(1, "a") + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") // overwrite + tree.Put(2, "b") + + if actualValue, expectedValue := tree.LeftKey(), 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.LeftValue(), "x"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + if actualValue, expectedValue := tree.RightKey(), 7; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.RightValue(), "g"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIteratorValuesAndKeys(t *testing.T) { + tree := New[int, string](4) + tree.Put(4, "d") + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(7, "g") + tree.Put(2, "b") + tree.Put(1, "x") // override + if actualValue, expectedValue := tree.Keys(), []int{1, 2, 3, 4, 5, 6, 7}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{"x", "b", "c", "d", "e", "f", "g"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := tree.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } +} + +func TestBTreeIteratorNextOnEmpty(t *testing.T) { + tree := New[int, string](3) + it := tree.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestBTreeIteratorPrevOnEmpty(t *testing.T) { + tree := New[int, string](3) + it := tree.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestBTreeIterator1Next(t *testing.T) { + tree := New[int, string](3) + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIterator1Prev(t *testing.T) { + tree := New[int, string](3) + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIterator2Next(t *testing.T) { + tree := New[int, string](3) + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIterator2Prev(t *testing.T) { + tree := New[int, string](3) + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIterator3Next(t *testing.T) { + tree := New[int, string](3) + tree.Put(1, "a") + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIterator3Prev(t *testing.T) { + tree := New[int, string](3) + tree.Put(1, "a") + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIterator4Next(t *testing.T) { + tree := New[int, int](3) + tree.Put(13, 5) + tree.Put(8, 3) + tree.Put(17, 7) + tree.Put(1, 1) + tree.Put(11, 4) + tree.Put(15, 6) + tree.Put(25, 9) + tree.Put(6, 2) + tree.Put(22, 8) + tree.Put(27, 10) + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + value := it.Value() + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIterator4Prev(t *testing.T) { + tree := New[int, int](3) + tree.Put(13, 5) + tree.Put(8, 3) + tree.Put(17, 7) + tree.Put(1, 1) + tree.Put(11, 4) + tree.Put(15, 6) + tree.Put(25, 9) + tree.Put(6, 2) + tree.Put(22, 8) + tree.Put(27, 10) + it := tree.Iterator() + count := tree.Size() + for it.Next() { + } + for it.Prev() { + value := it.Value() + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + count-- + } + if actualValue, expectedValue := count, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeIteratorBegin(t *testing.T) { + tree := New[int, string](3) + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Begin() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + for it.Next() { + } + + it.Begin() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Next() + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestBTreeIteratorEnd(t *testing.T) { + tree := New[int, string](3) + it := tree.Iterator() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.End() + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it.End() + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Prev() + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestBTreeIteratorFirst(t *testing.T) { + tree := New[int, string](3) + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestBTreeIteratorLast(t *testing.T) { + tree := New[int, string](3) + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestBTreeSearch(t *testing.T) { + { + tree := New[int, int](3) + tree.Root = &Node[int, int]{Entries: []*Entry[int, int]{}, Children: make([]*Node[int, int], 0)} + tests := [][]interface{}{ + {0, 0, false}, + } + for _, test := range tests { + index, found := tree.search(tree.Root, test[0].(int)) + if actualValue, expectedValue := index, test[1]; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := found, test[2]; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + } + { + tree := New[int, int](3) + tree.Root = &Node[int, int]{Entries: []*Entry[int, int]{{2, 0}, {4, 1}, {6, 2}}, Children: []*Node[int, int]{}} + tests := [][]interface{}{ + {0, 0, false}, + {1, 0, false}, + {2, 0, true}, + {3, 1, false}, + {4, 1, true}, + {5, 2, false}, + {6, 2, true}, + {7, 3, false}, + } + for _, test := range tests { + index, found := tree.search(tree.Root, test[0].(int)) + if actualValue, expectedValue := index, test[1]; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := found, test[2]; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + } +} + +func assertValidTree[K comparable, V any](t *testing.T, tree *Tree[K, V], expectedSize int) { + if actualValue, expectedValue := tree.size, expectedSize; actualValue != expectedValue { + t.Errorf("Got %v expected %v for tree size", actualValue, expectedValue) + } +} + +func assertValidTreeNode[K comparable, V any](t *testing.T, node *Node[K, V], expectedEntries int, expectedChildren int, keys []K, hasParent bool) { + if actualValue, expectedValue := node.Parent != nil, hasParent; actualValue != expectedValue { + t.Errorf("Got %v expected %v for hasParent", actualValue, expectedValue) + } + if actualValue, expectedValue := len(node.Entries), expectedEntries; actualValue != expectedValue { + t.Errorf("Got %v expected %v for entries size", actualValue, expectedValue) + } + if actualValue, expectedValue := len(node.Children), expectedChildren; actualValue != expectedValue { + t.Errorf("Got %v expected %v for children size", actualValue, expectedValue) + } + for i, key := range keys { + if actualValue, expectedValue := node.Entries[i].Key, key; actualValue != expectedValue { + t.Errorf("Got %v expected %v for key", actualValue, expectedValue) + } + } +} + +func TestBTreeIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + tree := New[int, string](3) + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (not found) + { + tree := New[int, string](3) + tree.Put(0, "xx") + tree.Put(1, "yy") + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (found) + { + tree := New[int, string](3) + tree.Put(2, "cc") + tree.Put(0, "aa") + tree.Put(1, "bb") + it := tree.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestBTreeIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + tree := New[int, string](3) + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (not found) + { + tree := New[int, string](3) + tree.Put(0, "xx") + tree.Put(1, "yy") + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (found) + { + tree := New[int, string](3) + tree.Put(2, "cc") + tree.Put(0, "aa") + tree.Put(1, "bb") + it := tree.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestBTreeSerialization(t *testing.T) { + tree := New[string, string](3) + tree.Put("c", "3") + tree.Put("b", "2") + tree.Put("a", "1") + + var err error + assert := func() { + if actualValue, expectedValue := tree.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Keys(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{"1", "2", "3"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := tree.ToJSON() + assert() + + err = tree.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", tree}) + if err != nil { + t.Errorf("Got error %v", err) + } + + intTree := New[string, int](3) + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), intTree) + if err != nil { + t.Errorf("Got error %v", err) + } + if actualValue, expectedValue := intTree.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := intTree.Keys(), []string{"a", "b"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := intTree.Values(), []int{1, 2}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestBTreeString(t *testing.T) { + c := New[string, int](3) + c.Put("a", 1) + if !strings.HasPrefix(c.String(), "BTree") { + t.Errorf("String should start with container name") + } +} + +func benchmarkGet(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Get(n) + } + } +} + +func benchmarkPut(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + } +} + +func benchmarkRemove(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Remove(n) + } + } +} + +func BenchmarkBTreeGet100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkBTreeGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkBTreeGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkBTreeGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkBTreePut100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}](128) + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkBTreePut1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkBTreePut10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkBTreePut100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkBTreeRemove100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkBTreeRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkBTreeRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkBTreeRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}](128) + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} diff --git a/trees/btree/iterator.go b/trees/btree/iterator.go new file mode 100644 index 00000000..23c9387d --- /dev/null +++ b/trees/btree/iterator.go @@ -0,0 +1,226 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btree + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithKey[string, int] = (*Iterator[string, int])(nil) + +// Iterator holding the iterator's state +type Iterator[K comparable, V any] struct { + tree *Tree[K, V] + node *Node[K, V] + entry *Entry[K, V] + position position +} + +type position byte + +const ( + begin, between, end position = 0, 1, 2 +) + +// Iterator returns a stateful iterator whose elements are key/value pairs. +func (tree *Tree[K, V]) Iterator() *Iterator[K, V] { + return &Iterator[K, V]{tree: tree, node: nil, position: begin} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Next() bool { + // If already at end, go to end + if iterator.position == end { + goto end + } + // If at beginning, get the left-most entry in the tree + if iterator.position == begin { + left := iterator.tree.Left() + if left == nil { + goto end + } + iterator.node = left + iterator.entry = left.Entries[0] + goto between + } + { + // Find current entry position in current node + e, _ := iterator.tree.search(iterator.node, iterator.entry.Key) + // Try to go down to the child right of the current entry + if e+1 < len(iterator.node.Children) { + iterator.node = iterator.node.Children[e+1] + // Try to go down to the child left of the current node + for len(iterator.node.Children) > 0 { + iterator.node = iterator.node.Children[0] + } + // Return the left-most entry + iterator.entry = iterator.node.Entries[0] + goto between + } + // Above assures that we have reached a leaf node, so return the next entry in current node (if any) + if e+1 < len(iterator.node.Entries) { + iterator.entry = iterator.node.Entries[e+1] + goto between + } + } + // Reached leaf node and there are no entries to the right of the current entry, so go up to the parent + for iterator.node.Parent != nil { + iterator.node = iterator.node.Parent + // Find next entry position in current node (note: search returns the first equal or bigger than entry) + e, _ := iterator.tree.search(iterator.node, iterator.entry.Key) + // Check that there is a next entry position in current node + if e < len(iterator.node.Entries) { + iterator.entry = iterator.node.Entries[e] + goto between + } + } + +end: + iterator.End() + return false + +between: + iterator.position = between + return true +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Prev() bool { + // If already at beginning, go to begin + if iterator.position == begin { + goto begin + } + // If at end, get the right-most entry in the tree + if iterator.position == end { + right := iterator.tree.Right() + if right == nil { + goto begin + } + iterator.node = right + iterator.entry = right.Entries[len(right.Entries)-1] + goto between + } + { + // Find current entry position in current node + e, _ := iterator.tree.search(iterator.node, iterator.entry.Key) + // Try to go down to the child left of the current entry + if e < len(iterator.node.Children) { + iterator.node = iterator.node.Children[e] + // Try to go down to the child right of the current node + for len(iterator.node.Children) > 0 { + iterator.node = iterator.node.Children[len(iterator.node.Children)-1] + } + // Return the right-most entry + iterator.entry = iterator.node.Entries[len(iterator.node.Entries)-1] + goto between + } + // Above assures that we have reached a leaf node, so return the previous entry in current node (if any) + if e-1 >= 0 { + iterator.entry = iterator.node.Entries[e-1] + goto between + } + } + // Reached leaf node and there are no entries to the left of the current entry, so go up to the parent + for iterator.node.Parent != nil { + iterator.node = iterator.node.Parent + // Find previous entry position in current node (note: search returns the first equal or bigger than entry) + e, _ := iterator.tree.search(iterator.node, iterator.entry.Key) + // Check that there is a previous entry position in current node + if e-1 >= 0 { + iterator.entry = iterator.node.Entries[e-1] + goto between + } + } + +begin: + iterator.Begin() + return false + +between: + iterator.position = between + return true +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Value() V { + return iterator.entry.Value +} + +// Key returns the current element's key. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Key() K { + return iterator.entry.Key +} + +// Node returns the current element's node. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Node() *Node[K, V] { + return iterator.node +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[K, V]) Begin() { + iterator.node = nil + iterator.position = begin + iterator.entry = nil +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[K, V]) End() { + iterator.node = nil + iterator.position = end + iterator.entry = nil +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator +func (iterator *Iterator[K, V]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) NextTo(f func(key K, value V) bool) bool { + for iterator.Next() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) PrevTo(f func(key K, value V) bool) bool { + for iterator.Prev() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} diff --git a/trees/btree/serialization.go b/trees/btree/serialization.go new file mode 100644 index 00000000..4b9a3139 --- /dev/null +++ b/trees/btree/serialization.go @@ -0,0 +1,51 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btree + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Tree[string, int])(nil) +var _ containers.JSONDeserializer = (*Tree[string, int])(nil) + +// ToJSON outputs the JSON representation of the tree. +func (tree *Tree[K, V]) ToJSON() ([]byte, error) { + elements := make(map[K]V) + it := tree.Iterator() + for it.Next() { + elements[it.Key()] = it.Value() + } + return json.Marshal(&elements) +} + +// FromJSON populates the tree from the input JSON representation. +func (tree *Tree[K, V]) FromJSON(data []byte) error { + elements := make(map[K]V) + err := json.Unmarshal(data, &elements) + if err != nil { + return err + } + + tree.Clear() + for key, value := range elements { + tree.Put(key, value) + } + + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (tree *Tree[K, V]) UnmarshalJSON(bytes []byte) error { + return tree.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (tree *Tree[K, V]) MarshalJSON() ([]byte, error) { + return tree.ToJSON() +} diff --git a/trees/redblacktree/iterator.go b/trees/redblacktree/iterator.go new file mode 100644 index 00000000..9aa5605e --- /dev/null +++ b/trees/redblacktree/iterator.go @@ -0,0 +1,190 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package redblacktree + +import "github.com/emirpasic/gods/v2/containers" + +// Assert Iterator implementation +var _ containers.ReverseIteratorWithKey[string, int] = (*Iterator[string, int])(nil) + +// Iterator holding the iterator's state +type Iterator[K comparable, V any] struct { + tree *Tree[K, V] + node *Node[K, V] + position position +} + +type position byte + +const ( + begin, between, end position = 0, 1, 2 +) + +// Iterator returns a stateful iterator whose elements are key/value pairs. +func (tree *Tree[K, V]) Iterator() *Iterator[K, V] { + return &Iterator[K, V]{tree: tree, node: nil, position: begin} +} + +// IteratorAt returns a stateful iterator whose elements are key/value pairs that is initialised at a particular node. +func (tree *Tree[K, V]) IteratorAt(node *Node[K, V]) *Iterator[K, V] { + return &Iterator[K, V]{tree: tree, node: node, position: between} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Next() bool { + if iterator.position == end { + goto end + } + if iterator.position == begin { + left := iterator.tree.Left() + if left == nil { + goto end + } + iterator.node = left + goto between + } + if iterator.node.Right != nil { + iterator.node = iterator.node.Right + for iterator.node.Left != nil { + iterator.node = iterator.node.Left + } + goto between + } + for iterator.node.Parent != nil { + node := iterator.node + iterator.node = iterator.node.Parent + if node == iterator.node.Left { + goto between + } + } + +end: + iterator.node = nil + iterator.position = end + return false + +between: + iterator.position = between + return true +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Prev() bool { + if iterator.position == begin { + goto begin + } + if iterator.position == end { + right := iterator.tree.Right() + if right == nil { + goto begin + } + iterator.node = right + goto between + } + if iterator.node.Left != nil { + iterator.node = iterator.node.Left + for iterator.node.Right != nil { + iterator.node = iterator.node.Right + } + goto between + } + for iterator.node.Parent != nil { + node := iterator.node + iterator.node = iterator.node.Parent + if node == iterator.node.Right { + goto between + } + } + +begin: + iterator.node = nil + iterator.position = begin + return false + +between: + iterator.position = between + return true +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Value() V { + return iterator.node.Value +} + +// Key returns the current element's key. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Key() K { + return iterator.node.Key +} + +// Node returns the current element's node. +// Does not modify the state of the iterator. +func (iterator *Iterator[K, V]) Node() *Node[K, V] { + return iterator.node +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator[K, V]) Begin() { + iterator.node = nil + iterator.position = begin +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator[K, V]) End() { + iterator.node = nil + iterator.position = end +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator +func (iterator *Iterator[K, V]) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) NextTo(f func(key K, value V) bool) bool { + for iterator.Next() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator[K, V]) PrevTo(f func(key K, value V) bool) bool { + for iterator.Prev() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} diff --git a/trees/redblacktree/redblacktree.go b/trees/redblacktree/redblacktree.go index 42a85534..ec53532d 100644 --- a/trees/redblacktree/redblacktree.go +++ b/trees/redblacktree/redblacktree.go @@ -1,46 +1,26 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Implementation of Red-black tree. +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package redblacktree implements a red-black tree. +// // Used by TreeSet and TreeMap. +// // Structure is not thread safe. +// // References: http://en.wikipedia.org/wiki/Red%E2%80%93black_tree - package redblacktree import ( + "cmp" "fmt" - "github.com/emirpasic/gods/stacks/linkedliststack" - "github.com/emirpasic/gods/trees" - "github.com/emirpasic/gods/utils" + + "github.com/emirpasic/gods/v2/trees" + "github.com/emirpasic/gods/v2/utils" ) -func assertInterfaceImplementation() { - var _ trees.Interface = (*Tree)(nil) -} +// Assert Tree implementation +var _ trees.Tree[int] = (*Tree[string, int])(nil) type color bool @@ -48,199 +28,279 @@ const ( black, red color = true, false ) -type Tree struct { - root *node +// Tree holds elements of the red-black tree +type Tree[K comparable, V any] struct { + Root *Node[K, V] size int - comparator utils.Comparator + Comparator utils.Comparator[K] } -type node struct { - key interface{} - value interface{} +// Node is a single element within the tree +type Node[K comparable, V any] struct { + Key K + Value V color color - left *node - right *node - parent *node + Left *Node[K, V] + Right *Node[K, V] + Parent *Node[K, V] } -// Instantiates a red-black tree with the custom comparator. -func NewWith(comparator utils.Comparator) *Tree { - return &Tree{comparator: comparator} +// New instantiates a red-black tree with the built-in comparator for K +func New[K cmp.Ordered, V any]() *Tree[K, V] { + return &Tree[K, V]{Comparator: cmp.Compare[K]} } -// Instantiates a red-black tree with the IntComparator, i.e. keys are of type int. -func NewWithIntComparator() *Tree { - return &Tree{comparator: utils.IntComparator} +// NewWith instantiates a red-black tree with the custom comparator. +func NewWith[K comparable, V any](comparator utils.Comparator[K]) *Tree[K, V] { + return &Tree[K, V]{Comparator: comparator} } -// Instantiates a red-black tree with the StringComparator, i.e. keys are of type string. -func NewWithStringComparator() *Tree { - return &Tree{comparator: utils.StringComparator} -} - -// Inserts node into the tree. +// Put inserts node into the tree. // Key should adhere to the comparator's type assertion, otherwise method panics. -func (tree *Tree) Put(key interface{}, value interface{}) { - insertedNode := &node{key: key, value: value, color: red} - if tree.root == nil { - tree.root = insertedNode +func (tree *Tree[K, V]) Put(key K, value V) { + var insertedNode *Node[K, V] + if tree.Root == nil { + // Assert key is of comparator's type for initial tree + tree.Comparator(key, key) + tree.Root = &Node[K, V]{Key: key, Value: value, color: red} + insertedNode = tree.Root } else { - node := tree.root + node := tree.Root loop := true for loop { - compare := tree.comparator(key, node.key) + compare := tree.Comparator(key, node.Key) switch { case compare == 0: - node.value = value + node.Key = key + node.Value = value return case compare < 0: - if node.left == nil { - node.left = insertedNode + if node.Left == nil { + node.Left = &Node[K, V]{Key: key, Value: value, color: red} + insertedNode = node.Left loop = false } else { - node = node.left + node = node.Left } case compare > 0: - if node.right == nil { - node.right = insertedNode + if node.Right == nil { + node.Right = &Node[K, V]{Key: key, Value: value, color: red} + insertedNode = node.Right loop = false } else { - node = node.right + node = node.Right } } } - insertedNode.parent = node + insertedNode.Parent = node } tree.insertCase1(insertedNode) - tree.size += 1 + tree.size++ } -// Searches the node in the tree by key and returns its value or nil if key is not found in tree. +// Get searches the node in the tree by key and returns its value or nil if key is not found in tree. // Second return parameter is true if key was found, otherwise false. // Key should adhere to the comparator's type assertion, otherwise method panics. -func (tree *Tree) Get(key interface{}) (value interface{}, found bool) { +func (tree *Tree[K, V]) Get(key K) (value V, found bool) { node := tree.lookup(key) if node != nil { - return node.value, true + return node.Value, true } - return nil, false + return value, false } -// Remove the node from the tree by key. +// GetNode searches the node in the tree by key and returns its node or nil if key is not found in tree. // Key should adhere to the comparator's type assertion, otherwise method panics. -func (tree *Tree) Remove(key interface{}) { - var child *node +func (tree *Tree[K, V]) GetNode(key K) *Node[K, V] { + return tree.lookup(key) +} + +// Remove remove the node from the tree by key. +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Remove(key K) { + var child *Node[K, V] node := tree.lookup(key) if node == nil { return } - if node.left != nil && node.right != nil { - pred := node.left.maximumNode() - node.key = pred.key - node.value = pred.value + if node.Left != nil && node.Right != nil { + pred := node.Left.maximumNode() + node.Key = pred.Key + node.Value = pred.Value node = pred } - if node.left == nil || node.right == nil { - if node.right == nil { - child = node.left + if node.Left == nil || node.Right == nil { + if node.Right == nil { + child = node.Left } else { - child = node.right + child = node.Right } if node.color == black { node.color = nodeColor(child) tree.deleteCase1(node) } tree.replaceNode(node, child) - if node.parent == nil && child != nil { + if node.Parent == nil && child != nil { child.color = black } } - tree.size -= 1 + tree.size-- } -// Returns true if tree does not contain any nodes -func (tree *Tree) Empty() bool { +// Empty returns true if tree does not contain any nodes +func (tree *Tree[K, V]) Empty() bool { return tree.size == 0 } -// Returns number of nodes in the tree. -func (tree *Tree) Size() int { +// Size returns number of nodes in the tree. +func (tree *Tree[K, V]) Size() int { return tree.size } -// Returns all keys in-order -func (tree *Tree) Keys() []interface{} { - keys := make([]interface{}, tree.size) - for i, node := range tree.inOrder() { - keys[i] = node.key +// Size returns the number of elements stored in the subtree. +// Computed dynamically on each call, i.e. the subtree is traversed to count the number of the nodes. +func (node *Node[K, V]) Size() int { + if node == nil { + return 0 + } + size := 1 + if node.Left != nil { + size += node.Left.Size() + } + if node.Right != nil { + size += node.Right.Size() + } + return size +} + +// Keys returns all keys in-order +func (tree *Tree[K, V]) Keys() []K { + keys := make([]K, tree.size) + it := tree.Iterator() + for i := 0; it.Next(); i++ { + keys[i] = it.Key() } return keys } -// Returns all values in-order based on the key. -func (tree *Tree) Values() []interface{} { - values := make([]interface{}, tree.size) - for i, node := range tree.inOrder() { - values[i] = node.value +// Values returns all values in-order based on the key. +func (tree *Tree[K, V]) Values() []V { + values := make([]V, tree.size) + it := tree.Iterator() + for i := 0; it.Next(); i++ { + values[i] = it.Value() } return values } -// Removes all nodes from the tree. -func (tree *Tree) Clear() { - tree.root = nil +// Left returns the left-most (min) node or nil if tree is empty. +func (tree *Tree[K, V]) Left() *Node[K, V] { + var parent *Node[K, V] + current := tree.Root + for current != nil { + parent = current + current = current.Left + } + return parent +} + +// Right returns the right-most (max) node or nil if tree is empty. +func (tree *Tree[K, V]) Right() *Node[K, V] { + var parent *Node[K, V] + current := tree.Root + for current != nil { + parent = current + current = current.Right + } + return parent +} + +// Floor Finds floor node of the input key, return the floor node or nil if no floor is found. +// Second return parameter is true if floor was found, otherwise false. +// +// Floor node is defined as the largest node that is smaller than or equal to the given node. +// A floor node may not be found, either because the tree is empty, or because +// all nodes in the tree are larger than the given node. +// +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Floor(key K) (floor *Node[K, V], found bool) { + found = false + node := tree.Root + for node != nil { + compare := tree.Comparator(key, node.Key) + switch { + case compare == 0: + return node, true + case compare < 0: + node = node.Left + case compare > 0: + floor, found = node, true + node = node.Right + } + } + if found { + return floor, true + } + return nil, false +} + +// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling is found. +// Second return parameter is true if ceiling was found, otherwise false. +// +// Ceiling node is defined as the smallest node that is larger than or equal to the given node. +// A ceiling node may not be found, either because the tree is empty, or because +// all nodes in the tree are smaller than the given node. +// +// Key should adhere to the comparator's type assertion, otherwise method panics. +func (tree *Tree[K, V]) Ceiling(key K) (ceiling *Node[K, V], found bool) { + found = false + node := tree.Root + for node != nil { + compare := tree.Comparator(key, node.Key) + switch { + case compare == 0: + return node, true + case compare < 0: + ceiling, found = node, true + node = node.Left + case compare > 0: + node = node.Right + } + } + if found { + return ceiling, true + } + return nil, false +} + +// Clear removes all nodes from the tree. +func (tree *Tree[K, V]) Clear() { + tree.Root = nil tree.size = 0 } -func (tree *Tree) String() string { +// String returns a string representation of container +func (tree *Tree[K, V]) String() string { str := "RedBlackTree\n" if !tree.Empty() { - output(tree.root, "", true, &str) + output(tree.Root, "", true, &str) } return str } -func (node *node) String() string { - return fmt.Sprintf("%v", node.key) -} - -// Returns all nodes in order -func (tree *Tree) inOrder() []*node { - nodes := make([]*node, tree.size) - if tree.size > 0 { - current := tree.root - stack := linkedliststack.New() - done := false - count := 0 - for !done { - if current != nil { - stack.Push(current) - current = current.left - } else { - if !stack.Empty() { - currentPop, _ := stack.Pop() - current = currentPop.(*node) - nodes[count] = current - count += 1 - current = current.right - } else { - done = true - } - } - } - } - return nodes +func (node *Node[K, V]) String() string { + return fmt.Sprintf("%v", node.Key) } -func output(node *node, prefix string, isTail bool, str *string) { - if node.right != nil { +func output[K comparable, V any](node *Node[K, V], prefix string, isTail bool, str *string) { + if node.Right != nil { newPrefix := prefix if isTail { newPrefix += "β”‚ " } else { newPrefix += " " } - output(node.right, newPrefix, false, str) + output(node.Right, newPrefix, false, str) } *str += prefix if isTail { @@ -249,114 +309,113 @@ func output(node *node, prefix string, isTail bool, str *string) { *str += "β”Œβ”€β”€ " } *str += node.String() + "\n" - if node.left != nil { + if node.Left != nil { newPrefix := prefix if isTail { newPrefix += " " } else { newPrefix += "β”‚ " } - output(node.left, newPrefix, true, str) + output(node.Left, newPrefix, true, str) } } -func (tree *Tree) lookup(key interface{}) *node { - node := tree.root +func (tree *Tree[K, V]) lookup(key K) *Node[K, V] { + node := tree.Root for node != nil { - compare := tree.comparator(key, node.key) + compare := tree.Comparator(key, node.Key) switch { case compare == 0: return node case compare < 0: - node = node.left + node = node.Left case compare > 0: - node = node.right + node = node.Right } } return nil } -func (node *node) grandparent() *node { - if node != nil && node.parent != nil { - return node.parent.parent +func (node *Node[K, V]) grandparent() *Node[K, V] { + if node != nil && node.Parent != nil { + return node.Parent.Parent } return nil } -func (node *node) uncle() *node { - if node == nil || node.parent == nil || node.parent.parent == nil { +func (node *Node[K, V]) uncle() *Node[K, V] { + if node == nil || node.Parent == nil || node.Parent.Parent == nil { return nil } - return node.parent.sibling() + return node.Parent.sibling() } -func (node *node) sibling() *node { - if node == nil || node.parent == nil { +func (node *Node[K, V]) sibling() *Node[K, V] { + if node == nil || node.Parent == nil { return nil } - if node == node.parent.left { - return node.parent.right - } else { - return node.parent.left + if node == node.Parent.Left { + return node.Parent.Right } + return node.Parent.Left } -func (tree *Tree) rotateLeft(node *node) { - right := node.right +func (tree *Tree[K, V]) rotateLeft(node *Node[K, V]) { + right := node.Right tree.replaceNode(node, right) - node.right = right.left - if right.left != nil { - right.left.parent = node + node.Right = right.Left + if right.Left != nil { + right.Left.Parent = node } - right.left = node - node.parent = right + right.Left = node + node.Parent = right } -func (tree *Tree) rotateRight(node *node) { - left := node.left +func (tree *Tree[K, V]) rotateRight(node *Node[K, V]) { + left := node.Left tree.replaceNode(node, left) - node.left = left.right - if left.right != nil { - left.right.parent = node + node.Left = left.Right + if left.Right != nil { + left.Right.Parent = node } - left.right = node - node.parent = left + left.Right = node + node.Parent = left } -func (tree *Tree) replaceNode(old *node, new *node) { - if old.parent == nil { - tree.root = new +func (tree *Tree[K, V]) replaceNode(old *Node[K, V], new *Node[K, V]) { + if old.Parent == nil { + tree.Root = new } else { - if old == old.parent.left { - old.parent.left = new + if old == old.Parent.Left { + old.Parent.Left = new } else { - old.parent.right = new + old.Parent.Right = new } } if new != nil { - new.parent = old.parent + new.Parent = old.Parent } } -func (tree *Tree) insertCase1(node *node) { - if node.parent == nil { +func (tree *Tree[K, V]) insertCase1(node *Node[K, V]) { + if node.Parent == nil { node.color = black } else { tree.insertCase2(node) } } -func (tree *Tree) insertCase2(node *node) { - if nodeColor(node.parent) == black { +func (tree *Tree[K, V]) insertCase2(node *Node[K, V]) { + if nodeColor(node.Parent) == black { return } tree.insertCase3(node) } -func (tree *Tree) insertCase3(node *node) { +func (tree *Tree[K, V]) insertCase3(node *Node[K, V]) { uncle := node.uncle() if nodeColor(uncle) == red { - node.parent.color = black + node.Parent.color = black uncle.color = black node.grandparent().color = red tree.insertCase1(node.grandparent()) @@ -365,121 +424,120 @@ func (tree *Tree) insertCase3(node *node) { } } -func (tree *Tree) insertCase4(node *node) { +func (tree *Tree[K, V]) insertCase4(node *Node[K, V]) { grandparent := node.grandparent() - if node == node.parent.right && node.parent == grandparent.left { - tree.rotateLeft(node.parent) - node = node.left - } else if node == node.parent.left && node.parent == grandparent.right { - tree.rotateRight(node.parent) - node = node.right + if node == node.Parent.Right && node.Parent == grandparent.Left { + tree.rotateLeft(node.Parent) + node = node.Left + } else if node == node.Parent.Left && node.Parent == grandparent.Right { + tree.rotateRight(node.Parent) + node = node.Right } tree.insertCase5(node) } -func (tree *Tree) insertCase5(node *node) { - node.parent.color = black +func (tree *Tree[K, V]) insertCase5(node *Node[K, V]) { + node.Parent.color = black grandparent := node.grandparent() grandparent.color = red - if node == node.parent.left && node.parent == grandparent.left { + if node == node.Parent.Left && node.Parent == grandparent.Left { tree.rotateRight(grandparent) - } else if node == node.parent.right && node.parent == grandparent.right { + } else if node == node.Parent.Right && node.Parent == grandparent.Right { tree.rotateLeft(grandparent) } } -func (node *node) maximumNode() *node { +func (node *Node[K, V]) maximumNode() *Node[K, V] { if node == nil { return nil } - for node.right != nil { - node = node.right + for node.Right != nil { + node = node.Right } return node } -func (tree *Tree) deleteCase1(node *node) { - if node.parent == nil { +func (tree *Tree[K, V]) deleteCase1(node *Node[K, V]) { + if node.Parent == nil { return - } else { - tree.deleteCase2(node) } + tree.deleteCase2(node) } -func (tree *Tree) deleteCase2(node *node) { +func (tree *Tree[K, V]) deleteCase2(node *Node[K, V]) { sibling := node.sibling() if nodeColor(sibling) == red { - node.parent.color = red + node.Parent.color = red sibling.color = black - if node == node.parent.left { - tree.rotateLeft(node.parent) + if node == node.Parent.Left { + tree.rotateLeft(node.Parent) } else { - tree.rotateRight(node.parent) + tree.rotateRight(node.Parent) } } tree.deleteCase3(node) } -func (tree *Tree) deleteCase3(node *node) { +func (tree *Tree[K, V]) deleteCase3(node *Node[K, V]) { sibling := node.sibling() - if nodeColor(node.parent) == black && + if nodeColor(node.Parent) == black && nodeColor(sibling) == black && - nodeColor(sibling.left) == black && - nodeColor(sibling.right) == black { + nodeColor(sibling.Left) == black && + nodeColor(sibling.Right) == black { sibling.color = red - tree.deleteCase1(node.parent) + tree.deleteCase1(node.Parent) } else { tree.deleteCase4(node) } } -func (tree *Tree) deleteCase4(node *node) { +func (tree *Tree[K, V]) deleteCase4(node *Node[K, V]) { sibling := node.sibling() - if nodeColor(node.parent) == red && + if nodeColor(node.Parent) == red && nodeColor(sibling) == black && - nodeColor(sibling.left) == black && - nodeColor(sibling.right) == black { + nodeColor(sibling.Left) == black && + nodeColor(sibling.Right) == black { sibling.color = red - node.parent.color = black + node.Parent.color = black } else { tree.deleteCase5(node) } } -func (tree *Tree) deleteCase5(node *node) { +func (tree *Tree[K, V]) deleteCase5(node *Node[K, V]) { sibling := node.sibling() - if node == node.parent.left && + if node == node.Parent.Left && nodeColor(sibling) == black && - nodeColor(sibling.left) == red && - nodeColor(sibling.right) == black { + nodeColor(sibling.Left) == red && + nodeColor(sibling.Right) == black { sibling.color = red - sibling.left.color = black + sibling.Left.color = black tree.rotateRight(sibling) - } else if node == node.parent.right && + } else if node == node.Parent.Right && nodeColor(sibling) == black && - nodeColor(sibling.right) == red && - nodeColor(sibling.left) == black { + nodeColor(sibling.Right) == red && + nodeColor(sibling.Left) == black { sibling.color = red - sibling.right.color = black + sibling.Right.color = black tree.rotateLeft(sibling) } tree.deleteCase6(node) } -func (tree *Tree) deleteCase6(node *node) { +func (tree *Tree[K, V]) deleteCase6(node *Node[K, V]) { sibling := node.sibling() - sibling.color = nodeColor(node.parent) - node.parent.color = black - if node == node.parent.left && nodeColor(sibling.right) == red { - sibling.right.color = black - tree.rotateLeft(node.parent) - } else if nodeColor(sibling.left) == red { - sibling.left.color = black - tree.rotateRight(node.parent) + sibling.color = nodeColor(node.Parent) + node.Parent.color = black + if node == node.Parent.Left && nodeColor(sibling.Right) == red { + sibling.Right.color = black + tree.rotateLeft(node.Parent) + } else if nodeColor(sibling.Left) == red { + sibling.Left.color = black + tree.rotateRight(node.Parent) } } -func nodeColor(node *node) color { +func nodeColor[K comparable, V any](node *Node[K, V]) color { if node == nil { return black } diff --git a/trees/redblacktree/redblacktree_test.go b/trees/redblacktree/redblacktree_test.go index 58fe60fe..b4e76b1e 100644 --- a/trees/redblacktree/redblacktree_test.go +++ b/trees/redblacktree/redblacktree_test.go @@ -1,41 +1,65 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package redblacktree import ( + "encoding/json" "fmt" + "slices" + "strings" "testing" ) -func TestRedBlackTree(t *testing.T) { +func TestRedBlackTreeGet(t *testing.T) { + tree := New[int, string]() - tree := NewWithIntComparator() + if actualValue := tree.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + if actualValue := tree.GetNode(2).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + tree.Put(1, "x") // 1->x + tree.Put(2, "b") // 1->x, 2->b (in order) + tree.Put(1, "a") // 1->a, 2->b (in order, replacement) + tree.Put(3, "c") // 1->a, 2->b, 3->c (in order) + tree.Put(4, "d") // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put(5, "e") // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put(6, "f") // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + + fmt.Println(tree) + // + // RedBlackTree + // β”‚ β”Œβ”€β”€ 6 + // β”‚ β”Œβ”€β”€ 5 + // β”‚ β”Œβ”€β”€ 4 + // β”‚ β”‚ └── 3 + // └── 2 + // └── 1 + + if actualValue := tree.Size(); actualValue != 6 { + t.Errorf("Got %v expected %v", actualValue, 6) + } + + if actualValue := tree.GetNode(4).Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + + if actualValue := tree.GetNode(2).Size(); actualValue != 6 { + t.Errorf("Got %v expected %v", actualValue, 6) + } + + if actualValue := tree.GetNode(8).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} - // insertions +func TestRedBlackTreePut(t *testing.T) { + tree := New[int, string]() tree.Put(5, "e") tree.Put(6, "f") tree.Put(7, "g") @@ -45,22 +69,16 @@ func TestRedBlackTree(t *testing.T) { tree.Put(2, "b") tree.Put(1, "a") //overwrite - // Test Size() if actualValue := tree.Size(); actualValue != 7 { t.Errorf("Got %v expected %v", actualValue, 7) } - - // test Keys() - if actualValue, expactedValue := fmt.Sprintf("%d%d%d%d%d%d%d", tree.Keys()...), "1234567"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) + if actualValue, expectedValue := tree.Keys(), []int{1, 2, 3, 4, 5, 6, 7}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } - - // test Values() - if actualValue, expactedValue := fmt.Sprintf("%s%s%s%s%s%s%s", tree.Values()...), "abcdefg"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) + if actualValue, expectedValue := tree.Values(), []string{"a", "b", "c", "d", "e", "f", "g"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } - // key,expectedValue,expectedFound tests1 := [][]interface{}{ {1, "a", true}, {2, "b", true}, @@ -69,35 +87,41 @@ func TestRedBlackTree(t *testing.T) { {5, "e", true}, {6, "f", true}, {7, "g", true}, - {8, nil, false}, + {8, "", false}, } for _, test := range tests1 { // retrievals - actualValue, actualFound := tree.Get(test[0]) + actualValue, actualFound := tree.Get(test[0].(int)) if actualValue != test[1] || actualFound != test[2] { t.Errorf("Got %v expected %v", actualValue, test[1]) } } +} + +func TestRedBlackTreeRemove(t *testing.T) { + tree := New[int, string]() + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite - // removals tree.Remove(5) tree.Remove(6) tree.Remove(7) tree.Remove(8) tree.Remove(5) - // Test Keys() - if actualValue, expactedValue := fmt.Sprintf("%d%d%d%d", tree.Keys()...), "1234"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) + if actualValue, expectedValue := tree.Keys(), []int{1, 2, 3, 4}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } - - // test Values() - if actualValue, expactedValue := fmt.Sprintf("%s%s%s%s", tree.Values()...), "abcd"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) + if actualValue, expectedValue := tree.Values(), []string{"a", "b", "c", "d"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } - - // Test Size() if actualValue := tree.Size(); actualValue != 4 { t.Errorf("Got %v expected %v", actualValue, 7) } @@ -107,21 +131,19 @@ func TestRedBlackTree(t *testing.T) { {2, "b", true}, {3, "c", true}, {4, "d", true}, - {5, nil, false}, - {6, nil, false}, - {7, nil, false}, - {8, nil, false}, + {5, "", false}, + {6, "", false}, + {7, "", false}, + {8, "", false}, } for _, test := range tests2 { - // retrievals - actualValue, actualFound := tree.Get(test[0]) + actualValue, actualFound := tree.Get(test[0].(int)) if actualValue != test[1] || actualFound != test[2] { t.Errorf("Got %v expected %v", actualValue, test[1]) } } - // removals tree.Remove(1) tree.Remove(4) tree.Remove(2) @@ -129,45 +151,716 @@ func TestRedBlackTree(t *testing.T) { tree.Remove(2) tree.Remove(2) - // Test Keys() - if actualValue, expactedValue := fmt.Sprintf("%s", tree.Keys()), "[]"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) + if actualValue, expectedValue := tree.Keys(), []int{}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if empty, size := tree.Empty(), tree.Size(); empty != true || size != -0 { + t.Errorf("Got %v expected %v", empty, true) + } + +} + +func TestRedBlackTreeLeftAndRight(t *testing.T) { + tree := New[int, string]() + + if actualValue := tree.Left(); actualValue != nil { + t.Errorf("Got %v expected %v", actualValue, nil) } + if actualValue := tree.Right(); actualValue != nil { + t.Errorf("Got %v expected %v", actualValue, nil) + } + + tree.Put(1, "a") + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") // overwrite + tree.Put(2, "b") - // test Values() - if actualValue, expactedValue := fmt.Sprintf("%s", tree.Values()), "[]"; actualValue != expactedValue { - t.Errorf("Got %v expected %v", actualValue, expactedValue) + if actualValue, expectedValue := tree.Left().Key, 1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Left().Value, "x"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) } - // Test Size() - if actualValue := tree.Size(); actualValue != 0 { - t.Errorf("Got %v expected %v", actualValue, 0) + if actualValue, expectedValue := tree.Right().Key, 7; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Right().Value, "g"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeCeilingAndFloor(t *testing.T) { + tree := New[int, string]() + + if node, found := tree.Floor(0); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + if node, found := tree.Ceiling(0); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + + if node, found := tree.Floor(4); node.Key != 4 || !found { + t.Errorf("Got %v expected %v", node.Key, 4) + } + if node, found := tree.Floor(0); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + + if node, found := tree.Ceiling(4); node.Key != 4 || !found { + t.Errorf("Got %v expected %v", node.Key, 4) + } + if node, found := tree.Ceiling(8); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } +} + +func TestRedBlackTreeIteratorNextOnEmpty(t *testing.T) { + tree := New[int, string]() + it := tree.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestRedBlackTreeIteratorPrevOnEmpty(t *testing.T) { + tree := New[int, string]() + it := tree.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestRedBlackTreeIterator1Next(t *testing.T) { + tree := New[int, string]() + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + // β”‚ β”Œβ”€β”€ 7 + // └── 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 2 + // └── 1 + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeIterator1Prev(t *testing.T) { + tree := New[int, string]() + tree.Put(5, "e") + tree.Put(6, "f") + tree.Put(7, "g") + tree.Put(3, "c") + tree.Put(4, "d") + tree.Put(1, "x") + tree.Put(2, "b") + tree.Put(1, "a") //overwrite + // β”‚ β”Œβ”€β”€ 7 + // └── 6 + // β”‚ β”Œβ”€β”€ 5 + // └── 4 + // β”‚ β”Œβ”€β”€ 3 + // └── 2 + // └── 1 + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) } +} - // Test Empty() - if actualValue := tree.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestRedBlackTreeIterator2Next(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} +func TestRedBlackTreeIterator2Prev(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") tree.Put(1, "a") tree.Put(2, "b") - tree.Clear() + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} - // Test Empty() - if actualValue := tree.Empty(); actualValue != true { - t.Errorf("Got %v expected %v", actualValue, true) +func TestRedBlackTreeIterator3Next(t *testing.T) { + tree := New[int, string]() + tree.Put(1, "a") + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) } +} +func TestRedBlackTreeIterator3Prev(t *testing.T) { + tree := New[int, string]() + tree.Put(1, "a") + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeIterator4Next(t *testing.T) { + tree := New[int, int]() + tree.Put(13, 5) + tree.Put(8, 3) + tree.Put(17, 7) + tree.Put(1, 1) + tree.Put(11, 4) + tree.Put(15, 6) + tree.Put(25, 9) + tree.Put(6, 2) + tree.Put(22, 8) + tree.Put(27, 10) + // β”‚ β”Œβ”€β”€ 27 + // β”‚ β”Œβ”€β”€ 25 + // β”‚ β”‚ └── 22 + // β”‚ β”Œβ”€β”€ 17 + // β”‚ β”‚ └── 15 + // └── 13 + // β”‚ β”Œβ”€β”€ 11 + // └── 8 + // β”‚ β”Œβ”€β”€ 6 + // └── 1 + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + value := it.Value() + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } } -func BenchmarkRedBlackTree(b *testing.B) { +func TestRedBlackTreeIterator4Prev(t *testing.T) { + tree := New[int, int]() + tree.Put(13, 5) + tree.Put(8, 3) + tree.Put(17, 7) + tree.Put(1, 1) + tree.Put(11, 4) + tree.Put(15, 6) + tree.Put(25, 9) + tree.Put(6, 2) + tree.Put(22, 8) + tree.Put(27, 10) + // β”‚ β”Œβ”€β”€ 27 + // β”‚ β”Œβ”€β”€ 25 + // β”‚ β”‚ └── 22 + // β”‚ β”Œβ”€β”€ 17 + // β”‚ β”‚ └── 15 + // └── 13 + // β”‚ β”Œβ”€β”€ 11 + // └── 8 + // β”‚ β”Œβ”€β”€ 6 + // └── 1 + it := tree.Iterator() + count := tree.Size() + for it.Next() { + } + for it.Prev() { + value := it.Value() + if actualValue, expectedValue := value, count; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + count-- + } + if actualValue, expectedValue := count, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeIteratorBegin(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Begin() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + for it.Next() { + } + + it.Begin() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Next() + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestRedBlackTreeIteratorEnd(t *testing.T) { + tree := New[int, string]() + it := tree.Iterator() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.End() + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it.End() + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Prev() + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestRedBlackTreeIteratorFirst(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 1 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestRedBlackTreeIteratorLast(t *testing.T) { + tree := New[int, string]() + tree.Put(3, "c") + tree.Put(1, "a") + tree.Put(2, "b") + it := tree.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); key != 3 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestRedBlackTreeIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // NextTo (empty) + { + tree := New[int, string]() + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (not found) + { + tree := New[int, string]() + tree.Put(0, "xx") + tree.Put(1, "yy") + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (found) + { + tree := New[int, string]() + tree.Put(2, "cc") + tree.Put(0, "aa") + tree.Put(1, "bb") + it := tree.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 2 || value != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestRedBlackTreeIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index int, value string) bool { + return strings.HasSuffix(value, "b") + } + + // PrevTo (empty) + { + tree := New[int, string]() + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (not found) + { + tree := New[int, string]() + tree.Put(0, "xx") + tree.Put(1, "yy") + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (found) + { + tree := New[int, string]() + tree.Put(2, "cc") + tree.Put(0, "aa") + tree.Put(1, "bb") + it := tree.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); index != 1 || value != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); index != 0 || value != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func TestRedBlackTreeSerialization(t *testing.T) { + tree := New[string, string]() + tree.Put("c", "3") + tree.Put("b", "2") + tree.Put("a", "1") + + var err error + assert := func() { + if actualValue, expectedValue := tree.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Keys(), []string{"a", "b", "c"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := tree.Values(), []string{"1", "2", "3"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + bytes, err := tree.ToJSON() + assert() + + err = tree.FromJSON(bytes) + assert() + + bytes, err = json.Marshal([]interface{}{"a", "b", "c", tree}) + if err != nil { + t.Errorf("Got error %v", err) + } + + intTree := New[string, int]() + err = json.Unmarshal([]byte(`{"a":1,"b":2}`), intTree) + if err != nil { + t.Errorf("Got error %v", err) + } + if actualValue, expectedValue := intTree.Size(), 2; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := intTree.Keys(), []string{"a", "b"}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := intTree.Values(), []int{1, 2}; !slices.Equal(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeString(t *testing.T) { + c := New[string, int]() + c.Put("a", 1) + if !strings.HasPrefix(c.String(), "RedBlackTree") { + t.Errorf("String should start with container name") + } +} + +func benchmarkGet(b *testing.B, tree *Tree[int, struct{}], size int) { for i := 0; i < b.N; i++ { - tree := NewWithIntComparator() - for n := 0; n < 1000; n++ { - tree.Put(n, n) + for n := 0; n < size; n++ { + tree.Get(n) } - for n := 0; n < 1000; n++ { + } +} + +func benchmarkPut(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + } +} + +func benchmarkRemove(b *testing.B, tree *Tree[int, struct{}], size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { tree.Remove(n) } } } + +func BenchmarkRedBlackTreeGet100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreeGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreeGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreeGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreePut100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}]() + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreePut1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreePut10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreePut100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New[int, struct{}]() + for n := 0; n < size; n++ { + tree.Put(n, struct{}{}) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} diff --git a/trees/redblacktree/serialization.go b/trees/redblacktree/serialization.go new file mode 100644 index 00000000..9311c897 --- /dev/null +++ b/trees/redblacktree/serialization.go @@ -0,0 +1,48 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package redblacktree + +import ( + "encoding/json" + + "github.com/emirpasic/gods/v2/containers" +) + +// Assert Serialization implementation +var _ containers.JSONSerializer = (*Tree[string, int])(nil) +var _ containers.JSONDeserializer = (*Tree[string, int])(nil) + +// ToJSON outputs the JSON representation of the tree. +func (tree *Tree[K, V]) ToJSON() ([]byte, error) { + elements := make(map[K]V) + it := tree.Iterator() + for it.Next() { + elements[it.Key()] = it.Value() + } + return json.Marshal(&elements) +} + +// FromJSON populates the tree from the input JSON representation. +func (tree *Tree[K, V]) FromJSON(data []byte) error { + elements := make(map[K]V) + err := json.Unmarshal(data, &elements) + if err == nil { + tree.Clear() + for key, value := range elements { + tree.Put(key, value) + } + } + return err +} + +// UnmarshalJSON @implements json.Unmarshaler +func (tree *Tree[K, V]) UnmarshalJSON(bytes []byte) error { + return tree.FromJSON(bytes) +} + +// MarshalJSON @implements json.Marshaler +func (tree *Tree[K, V]) MarshalJSON() ([]byte, error) { + return tree.ToJSON() +} diff --git a/trees/trees.go b/trees/trees.go index 3714bee0..ad544007 100644 --- a/trees/trees.go +++ b/trees/trees.go @@ -1,37 +1,22 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// Package trees provides an abstract Tree interface. +// +// In computer science, a tree is a widely used abstract data type (ADT) or data structure implementing this ADT that simulates a hierarchical tree structure, with a root value and subtrees of children with a parent node, represented as a set of linked nodes. +// +// Reference: https://en.wikipedia.org/wiki/Tree_%28data_structure%29 package trees -import "github.com/emirpasic/gods/containers" +import "github.com/emirpasic/gods/v2/containers" -type Interface interface { - containers.Interface +// Tree interface that all trees implement +type Tree[V any] interface { + containers.Container[V] // Empty() bool // Size() int // Clear() // Values() []interface{} + // String() string } diff --git a/utils/comparator.go b/utils/comparator.go index 0960dabf..37ef49d7 100644 --- a/utils/comparator.go +++ b/utils/comparator.go @@ -1,72 +1,21 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package utils -// Comparator will make type assertion (see IntComparator for example), -// which will panic if a or b are not of the asserted type. -// -// Should return: -// -1, if a < b -// 0, if a == b -// 1, if a > b -type Comparator func(a, b interface{}) int +import "time" -func IntComparator(a, b interface{}) int { - aInt := a.(int) - bInt := b.(int) +type Comparator[T any] func(x, y T) int + +// TimeComparator provides a basic comparison on time.Time +func TimeComparator(a, b time.Time) int { switch { - case aInt > bInt: + case a.After(b): return 1 - case aInt < bInt: + case a.Before(b): return -1 default: return 0 } } - -func StringComparator(a, b interface{}) int { - s1 := a.(string) - s2 := b.(string) - min := len(s2) - if len(s1) < len(s2) { - min = len(s1) - } - diff := 0 - for i := 0; i < min && diff == 0; i++ { - diff = int(s1[i]) - int(s2[i]) - } - if diff == 0 { - diff = len(s1) - len(s2) - } - if diff < 0 { - return -1 - } - if diff > 0 { - return 1 - } - return 0 -} diff --git a/utils/comparator_test.go b/utils/comparator_test.go index 9efda990..b985f94c 100644 --- a/utils/comparator_test.go +++ b/utils/comparator_test.go @@ -1,110 +1,27 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package utils import ( "testing" + "time" ) -func TestIntComparator(t *testing.T) { - - // i1,i2,expected - tests := [][]interface{}{ - {1, 1, 0}, - {1, 2, -1}, - {2, 1, 1}, - {11, 22, -1}, - {0, 0, 0}, - {1, 0, 1}, - {0, 1, -1}, - } - - for _, test := range tests { - actual := IntComparator(test[0], test[1]) - expected := test[2] - if actual != expected { - t.Errorf("Got %v expected %v", actual, expected) - } - } -} - -func TestStringComparator(t *testing.T) { +func TestTimeComparator(t *testing.T) { - // s1,s2,expected - tests := [][]interface{}{ - {"a", "a", 0}, - {"a", "b", -1}, - {"b", "a", 1}, - {"aa", "aab", -1}, - {"", "", 0}, - {"a", "", 1}, - {"", "a", -1}, - {"", "aaaaaaa", -1}, - } + now := time.Now() - for _, test := range tests { - actual := StringComparator(test[0], test[1]) - expected := test[2] - if actual != expected { - t.Errorf("Got %v expected %v", actual, expected) - } - } -} - -func TestCustomComparator(t *testing.T) { - - type Custom struct { - id int - name string - } - - byID := func(a, b interface{}) int { - c1 := a.(Custom) - c2 := b.(Custom) - switch { - case c1.id > c2.id: - return 1 - case c1.id < c2.id: - return -1 - default: - return 0 - } - } - - // o1,o2,expected + // i1,i2,expected tests := [][]interface{}{ - {Custom{1, "a"}, Custom{1, "a"}, 0}, - {Custom{1, "a"}, Custom{2, "b"}, -1}, - {Custom{2, "b"}, Custom{1, "a"}, 1}, - {Custom{1, "a"}, Custom{1, "b"}, 0}, + {now, now, 0}, + {now.Add(24 * 7 * 2 * time.Hour), now, 1}, + {now, now.Add(24 * 7 * 2 * time.Hour), -1}, } for _, test := range tests { - actual := byID(test[0], test[1]) + actual := TimeComparator(test[0].(time.Time), test[1].(time.Time)) expected := test[2] if actual != expected { t.Errorf("Got %v expected %v", actual, expected) diff --git a/utils/sort.go b/utils/sort.go deleted file mode 100644 index 176a62fe..00000000 --- a/utils/sort.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Util methods for sorting a slice of values with respect to the comparator - -package utils - -import "github.com/emirpasic/gods/utils/timsort" - -// Sorts values (in-place) using timsort -func Sort(values []interface{}, comparator Comparator) { - less := func(a, b interface{}) bool { return comparator(a, b) < 0 } - timsort.Sort(values, less) -} diff --git a/utils/sort_test.go b/utils/sort_test.go deleted file mode 100644 index d1e523bf..00000000 --- a/utils/sort_test.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package utils - -import ( - "testing" -) - -func TestSortInts(t *testing.T) { - ints := []interface{}{} - ints = append(ints, 4) - ints = append(ints, 1) - ints = append(ints, 2) - ints = append(ints, 3) - - Sort(ints, IntComparator) - - for i := 1; i < len(ints); i++ { - if ints[i-1].(int) > ints[i].(int) { - t.Errorf("Not sorted!") - } - } - -} - -func TestSortStrings(t *testing.T) { - - strings := []interface{}{} - strings = append(strings, "d") - strings = append(strings, "a") - strings = append(strings, "b") - strings = append(strings, "c") - - Sort(strings, StringComparator) - - for i := 1; i < len(strings); i++ { - if strings[i-1].(string) > strings[i].(string) { - t.Errorf("Not sorted!") - } - } -} - -func TestSortStructs(t *testing.T) { - type User struct { - id int - name string - } - - byID := func(a, b interface{}) int { - c1 := a.(User) - c2 := b.(User) - switch { - case c1.id > c2.id: - return 1 - case c1.id < c2.id: - return -1 - default: - return 0 - } - } - - // o1,o2,expected - users := []interface{}{ - User{4, "d"}, - User{1, "a"}, - User{3, "c"}, - User{2, "b"}, - } - - Sort(users, byID) - - for i := 1; i < len(users); i++ { - if users[i-1].(User).id > users[i].(User).id { - t.Errorf("Not sorted!") - } - } - -} diff --git a/utils/timsort/LICENSE b/utils/timsort/LICENSE deleted file mode 100644 index 208ba422..00000000 --- a/utils/timsort/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2010-2011 Mike Kroutikov - -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. \ No newline at end of file diff --git a/utils/timsort/timsort.go b/utils/timsort/timsort.go deleted file mode 100644 index 861c3795..00000000 --- a/utils/timsort/timsort.go +++ /dev/null @@ -1,1148 +0,0 @@ -// Obtained from: https://github.com/psilva261/timsort - -/* -Copyright (c) 2010-2011 Mike Kroutikov - -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 -*/ - -// Fast stable sort, uses external comparator. -// -// A stable, adaptive, iterative mergesort that requires far fewer than -// n lg(n) comparisons when running on partially sorted arrays, while -// offering performance comparable to a traditional mergesort when run -// on random arrays. Like all proper mergesorts, this sort is stable and -// runs O(n log n) time (worst case). In the worst case, this sort requires -// temporary storage space for n/2 object references; in the best case, -// it requires only a small constant amount of space. -// -// This implementation was derived from Java's TimSort object by Josh Bloch, -// which, in turn, was based on the original code by Tim Peters: -// -// http://svn.python.org/projects/python/trunk/Objects/listsort.txt -// -// Mike K. - -package timsort - -import ( - "errors" - "fmt" -) - -const ( - /** - * This is the minimum sized sequence that will be merged. Shorter - * sequences will be lengthened by calling binarySort. If the entire - * array is less than this length, no merges will be performed. - * - * This constant should be a power of two. It was 64 in Tim Peter's C - * implementation, but 32 was empirically determined to work better in - * this implementation. In the unlikely event that you set this constant - * to be a number that's not a power of two, you'll need to change the - * {@link #minRunLength} computation. - * - * If you decrease this constant, you must change the stackLen - * computation in the TimSort constructor, or you risk an - * ArrayOutOfBounds exception. See listsort.txt for a discussion - * of the minimum stack length required as a function of the length - * of the array being sorted and the minimum merge sequence length. - */ - _MIN_MERGE = 32 - // mk: tried higher MIN_MERGE and got slower sorting (348->375) - // c_MIN_MERGE = 64 - - /** - * When we get into galloping mode, we stay there until both runs win less - * often than c_MIN_GALLOP consecutive times. - */ - _MIN_GALLOP = 7 - - /** - * Maximum initial size of tmp array, which is used for merging. The array - * can grow to accommodate demand. - * - * Unlike Tim's original C version, we do not allocate this much storage - * when sorting smaller arrays. This change was required for performance. - */ - _INITIAL_TMP_STORAGE_LENGTH = 256 -) - -// Delegate type that sorting uses as a comparator -type LessThan func(a, b interface{}) bool - -type timSortHandler struct { - - /** - * The array being sorted. - */ - a []interface{} - - /** - * The comparator for this sort. - */ - lt LessThan - - /** - * This controls when we get *into* galloping mode. It is initialized - * to c_MIN_GALLOP. The mergeLo and mergeHi methods nudge it higher for - * random data, and lower for highly structured data. - */ - minGallop int - - /** - * Temp storage for merges. - */ - tmp []interface{} // Actual runtime type will be Object[], regardless of T - - /** - * A stack of pending runs yet to be merged. Run i starts at - * address base[i] and extends for len[i] elements. It's always - * true (so long as the indices are in bounds) that: - * - * runBase[i] + runLen[i] == runBase[i + 1] - * - * so we could cut the storage for this, but it's a minor amount, - * and keeping all the info explicit simplifies the code. - */ - stackSize int // Number of pending runs on stack - runBase []int - runLen []int -} - -/** - * Creates a TimSort instance to maintain the state of an ongoing sort. - * - * @param a the array to be sorted - * @param c the comparator to determine the order of the sort - */ -func newTimSort(a []interface{}, lt LessThan) (self *timSortHandler) { - self = new(timSortHandler) - - self.a = a - self.lt = lt - self.minGallop = _MIN_GALLOP - self.stackSize = 0 - - // Allocate temp storage (which may be increased later if necessary) - len := len(a) - - tmpSize := _INITIAL_TMP_STORAGE_LENGTH - if len < 2*tmpSize { - tmpSize = len / 2 - } - - self.tmp = make([]interface{}, tmpSize) - - /* - * Allocate runs-to-be-merged stack (which cannot be expanded). The - * stack length requirements are described in listsort.txt. The C - * version always uses the same stack length (85), but this was - * measured to be too expensive when sorting "mid-sized" arrays (e.g., - * 100 elements) in Java. Therefore, we use smaller (but sufficiently - * large) stack lengths for smaller arrays. The "magic numbers" in the - * computation below must be changed if c_MIN_MERGE is decreased. See - * the c_MIN_MERGE declaration above for more information. - */ - // mk: confirmed that for small sorts this optimization gives measurable (albeit small) - // performance enhancement - stackLen := 40 - if len < 120 { - stackLen = 5 - } else if len < 1542 { - stackLen = 10 - } else if len < 119151 { - stackLen = 19 - } - - self.runBase = make([]int, stackLen) - self.runLen = make([]int, stackLen) - - return self -} - -// Sorts an array using the provided comparator -func Sort(a []interface{}, lt LessThan) (err error) { - lo := 0 - hi := len(a) - nRemaining := hi - - if nRemaining < 2 { - return // Arrays of size 0 and 1 are always sorted - } - - // If array is small, do a "mini-TimSort" with no merges - if nRemaining < _MIN_MERGE { - initRunLen, err := countRunAndMakeAscending(a, lo, hi, lt) - if err != nil { - return err - } - - return binarySort(a, lo, hi, lo+initRunLen, lt) - } - - /** - * March over the array once, left to right, finding natural runs, - * extending short natural runs to minRun elements, and merging runs - * to maintain stack invariant. - */ - - ts := newTimSort(a, lt) - minRun, err := minRunLength(nRemaining) - if err != nil { - return - } - for { - // Identify next run - runLen, err := countRunAndMakeAscending(a, lo, hi, lt) - if err != nil { - return err - } - - // If run is short, extend to min(minRun, nRemaining) - if runLen < minRun { - force := minRun - if nRemaining <= minRun { - force = nRemaining - } - if err = binarySort(a, lo, lo+force, lo+runLen, lt); err != nil { - return err - } - runLen = force - } - - // Push run onto pending-run stack, and maybe merge - ts.pushRun(lo, runLen) - if err = ts.mergeCollapse(); err != nil { - return err - } - - // Advance to find next run - lo += runLen - nRemaining -= runLen - if nRemaining == 0 { - break - } - } - - // Merge all remaining runs to complete sort - if lo != hi { - return errors.New("lo==hi!") - } - - if err = ts.mergeForceCollapse(); err != nil { - return - } - if ts.stackSize != 1 { - return errors.New("ts.stackSize != 1") - } - return -} - -/** - * Sorts the specified portion of the specified array using a binary - * insertion sort. This is the best method for sorting small numbers - * of elements. It requires O(n log n) compares, but O(n^2) data - * movement (worst case). - * - * If the initial part of the specified range is already sorted, - * this method can take advantage of it: the method assumes that the - * elements from index {@code lo}, inclusive, to {@code start}, - * exclusive are already sorted. - * - * @param a the array in which a range is to be sorted - * @param lo the index of the first element in the range to be sorted - * @param hi the index after the last element in the range to be sorted - * @param start the index of the first element in the range that is - * not already known to be sorted (@code lo <= start <= hi} - * @param c comparator to used for the sort - */ -func binarySort(a []interface{}, lo, hi, start int, lt LessThan) (err error) { - if lo > start || start > hi { - return errors.New("lo <= start && start <= hi") - } - - if start == lo { - start++ - } - - for ; start < hi; start++ { - pivot := a[start] - - // Set left (and right) to the index where a[start] (pivot) belongs - left := lo - right := start - - if left > right { - return errors.New("left <= right") - } - - /* - * Invariants: - * pivot >= all in [lo, left). - * pivot < all in [right, start). - */ - for left < right { - mid := (left + right) / 2 - if lt(pivot, a[mid]) { - right = mid - } else { - left = mid + 1 - } - } - - if left != right { - return errors.New("left == right") - } - - /* - * The invariants still hold: pivot >= all in [lo, left) and - * pivot < all in [left, start), so pivot belongs at left. Note - * that if there are elements equal to pivot, left points to the - * first slot after them -- that's why this sort is stable. - * Slide elements over to make room to make room for pivot. - */ - n := start - left // The number of elements to move - // just an optimization for copy in default case - if n <= 2 { - if n == 2 { - a[left+2] = a[left+1] - } - if n > 0 { - a[left+1] = a[left] - } - } else { - copy(a[left+1:], a[left:left+n]) - } - a[left] = pivot - } - return -} - -/** - * Returns the length of the run beginning at the specified position in - * the specified array and reverses the run if it is descending (ensuring - * that the run will always be ascending when the method returns). - * - * A run is the longest ascending sequence with: - * - * a[lo] <= a[lo + 1] <= a[lo + 2] <= ... - * - * or the longest descending sequence with: - * - * a[lo] > a[lo + 1] > a[lo + 2] > ... - * - * For its intended use in a stable mergesort, the strictness of the - * definition of "descending" is needed so that the call can safely - * reverse a descending sequence without violating stability. - * - * @param a the array in which a run is to be counted and possibly reversed - * @param lo index of the first element in the run - * @param hi index after the last element that may be contained in the run. - It is required that @code{lo < hi}. - * @param c the comparator to used for the sort - * @return the length of the run beginning at the specified position in - * the specified array -*/ -func countRunAndMakeAscending(a []interface{}, lo, hi int, lt LessThan) (int, error) { - - if lo >= hi { - return 0, errors.New("lo < hi") - } - - runHi := lo + 1 - if runHi == hi { - return 1, nil - } - - // Find end of run, and reverse range if descending - if lt(a[runHi], a[lo]) { // Descending - runHi++ - - for runHi < hi && lt(a[runHi], a[runHi-1]) { - runHi++ - } - reverseRange(a, lo, runHi) - } else { // Ascending - for runHi < hi && !lt(a[runHi], a[runHi-1]) { - runHi++ - } - } - - return runHi - lo, nil -} - -/** - * Reverse the specified range of the specified array. - * - * @param a the array in which a range is to be reversed - * @param lo the index of the first element in the range to be reversed - * @param hi the index after the last element in the range to be reversed - */ -func reverseRange(a []interface{}, lo, hi int) { - hi-- - for lo < hi { - a[lo], a[hi] = a[hi], a[lo] - lo++ - hi-- - } -} - -/** - * Returns the minimum acceptable run length for an array of the specified - * length. Natural runs shorter than this will be extended with - * {@link #binarySort}. - * - * Roughly speaking, the computation is: - * - * If n < c_MIN_MERGE, return n (it's too small to bother with fancy stuff). - * Else if n is an exact power of 2, return c_MIN_MERGE/2. - * Else return an int k, c_MIN_MERGE/2 <= k <= c_MIN_MERGE, such that n/k - * is close to, but strictly less than, an exact power of 2. - * - * For the rationale, see listsort.txt. - * - * @param n the length of the array to be sorted - * @return the length of the minimum run to be merged - */ -func minRunLength(n int) (int, error) { - if n < 0 { - return 0, errors.New("n >= 0") - } - r := 0 // Becomes 1 if any 1 bits are shifted off - for n >= _MIN_MERGE { - r |= (n & 1) - n >>= 1 - } - return n + r, nil -} - -/** - * Pushes the specified run onto the pending-run stack. - * - * @param runBase index of the first element in the run - * @param runLen the number of elements in the run - */ -func (self *timSortHandler) pushRun(runBase, runLen int) { - self.runBase[self.stackSize] = runBase - self.runLen[self.stackSize] = runLen - self.stackSize++ -} - -/** - * Examines the stack of runs waiting to be merged and merges adjacent runs - * until the stack invariants are reestablished: - * - * 1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1] - * 2. runLen[i - 2] > runLen[i - 1] - * - * This method is called each time a new run is pushed onto the stack, - * so the invariants are guaranteed to hold for i < stackSize upon - * entry to the method. - */ -func (self *timSortHandler) mergeCollapse() (err error) { - for self.stackSize > 1 { - n := self.stackSize - 2 - if n > 0 && self.runLen[n-1] <= self.runLen[n]+self.runLen[n+1] { - if self.runLen[n-1] < self.runLen[n+1] { - n-- - } - if err = self.mergeAt(n); err != nil { - return - } - } else if self.runLen[n] <= self.runLen[n+1] { - if err = self.mergeAt(n); err != nil { - return - } - } else { - break // Invariant is established - } - } - return -} - -/** - * Merges all runs on the stack until only one remains. This method is - * called once, to complete the sort. - */ -func (self *timSortHandler) mergeForceCollapse() (err error) { - for self.stackSize > 1 { - n := self.stackSize - 2 - if n > 0 && self.runLen[n-1] < self.runLen[n+1] { - n-- - } - if err = self.mergeAt(n); err != nil { - return - } - } - return -} - -/** - * Merges the two runs at stack indices i and i+1. Run i must be - * the penultimate or antepenultimate run on the stack. In other words, - * i must be equal to stackSize-2 or stackSize-3. - * - * @param i stack index of the first of the two runs to merge - */ -func (self *timSortHandler) mergeAt(i int) (err error) { - if self.stackSize < 2 { - return errors.New("stackSize >= 2") - } - - if i < 0 { - return errors.New(" i >= 0") - } - - if i != self.stackSize-2 && i != self.stackSize-3 { - return errors.New("if i == stackSize - 2 || i == stackSize - 3") - } - - base1 := self.runBase[i] - len1 := self.runLen[i] - base2 := self.runBase[i+1] - len2 := self.runLen[i+1] - - if len1 <= 0 || len2 <= 0 { - return errors.New("len1 > 0 && len2 > 0") - } - - if base1+len1 != base2 { - return errors.New("base1 + len1 == base2") - } - - /* - * Record the length of the combined runs; if i is the 3rd-last - * run now, also slide over the last run (which isn't involved - * in this merge). The current run (i+1) goes away in any case. - */ - self.runLen[i] = len1 + len2 - if i == self.stackSize-3 { - self.runBase[i+1] = self.runBase[i+2] - self.runLen[i+1] = self.runLen[i+2] - } - self.stackSize-- - - /* - * Find where the first element of run2 goes in run1. Prior elements - * in run1 can be ignored (because they're already in place). - */ - k, err := gallopRight(self.a[base2], self.a, base1, len1, 0, self.lt) - if err != nil { - return err - } - if k < 0 { - return errors.New(" k >= 0;") - } - base1 += k - len1 -= k - if len1 == 0 { - return - } - - /* - * Find where the last element of run1 goes in run2. Subsequent elements - * in run2 can be ignored (because they're already in place). - */ - len2, err = gallopLeft(self.a[base1+len1-1], self.a, base2, len2, len2-1, self.lt) - if err != nil { - return - } - if len2 < 0 { - return errors.New(" len2 >= 0;") - } - if len2 == 0 { - return - } - - // Merge remaining runs, using tmp array with min(len1, len2) elements - if len1 <= len2 { - err = self.mergeLo(base1, len1, base2, len2) - if err != nil { - return errors.New(fmt.Sprintf("mergeLo: %v", err)) - } - } else { - err = self.mergeHi(base1, len1, base2, len2) - if err != nil { - return errors.New(fmt.Sprintf("mergeHi: %v", err)) - } - } - return -} - -/** - * Locates the position at which to insert the specified key into the - * specified sorted range; if the range contains an element equal to key, - * returns the index of the leftmost equal element. - * - * @param key the key whose insertion point to search for - * @param a the array in which to search - * @param base the index of the first element in the range - * @param len the length of the range; must be > 0 - * @param hint the index at which to begin the search, 0 <= hint < n. - * The closer hint is to the result, the faster this method will run. - * @param c the comparator used to order the range, and to search - * @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], - * pretending that a[b - 1] is minus infinity and a[b + n] is infinity. - * In other words, key belongs at index b + k; or in other words, - * the first k elements of a should precede key, and the last n - k - * should follow it. - */ -func gallopLeft(key interface{}, a []interface{}, base, len, hint int, c LessThan) (int, error) { - if len <= 0 || hint < 0 || hint >= len { - return 0, errors.New(" len > 0 && hint >= 0 && hint < len;") - } - lastOfs := 0 - ofs := 1 - - if c(a[base+hint], key) { - // Gallop right until a[base+hint+lastOfs] < key <= a[base+hint+ofs] - maxOfs := len - hint - for ofs < maxOfs && c(a[base+hint+ofs], key) { - lastOfs = ofs - ofs = (ofs << 1) + 1 - if ofs <= 0 { // int overflow - ofs = maxOfs - } - } - if ofs > maxOfs { - ofs = maxOfs - } - - // Make offsets relative to base - lastOfs += hint - ofs += hint - } else { // key <= a[base + hint] - // Gallop left until a[base+hint-ofs] < key <= a[base+hint-lastOfs] - maxOfs := hint + 1 - for ofs < maxOfs && !c(a[base+hint-ofs], key) { - lastOfs = ofs - ofs = (ofs << 1) + 1 - if ofs <= 0 { // int overflow - ofs = maxOfs - } - } - if ofs > maxOfs { - ofs = maxOfs - } - - // Make offsets relative to base - tmp := lastOfs - lastOfs = hint - ofs - ofs = hint - tmp - } - - if -1 > lastOfs || lastOfs >= ofs || ofs > len { - return 0, errors.New(" -1 <= lastOfs && lastOfs < ofs && ofs <= len;") - } - - /* - * Now a[base+lastOfs] < key <= a[base+ofs], so key belongs somewhere - * to the right of lastOfs but no farther right than ofs. Do a binary - * search, with invariant a[base + lastOfs - 1] < key <= a[base + ofs]. - */ - lastOfs++ - for lastOfs < ofs { - m := lastOfs + (ofs-lastOfs)/2 - - if c(a[base+m], key) { - lastOfs = m + 1 // a[base + m] < key - } else { - ofs = m // key <= a[base + m] - } - } - - if lastOfs != ofs { - return 0, errors.New(" lastOfs == ofs") // so a[base + ofs - 1] < key <= a[base + ofs] - } - return ofs, nil -} - -/** - * Like gallopLeft, except that if the range contains an element equal to - * key, gallopRight returns the index after the rightmost equal element. - * - * @param key the key whose insertion point to search for - * @param a the array in which to search - * @param base the index of the first element in the range - * @param len the length of the range; must be > 0 - * @param hint the index at which to begin the search, 0 <= hint < n. - * The closer hint is to the result, the faster this method will run. - * @param c the comparator used to order the range, and to search - * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] - */ -func gallopRight(key interface{}, a []interface{}, base, len, hint int, c LessThan) (int, error) { - if len <= 0 || hint < 0 || hint >= len { - return 0, errors.New(" len > 0 && hint >= 0 && hint < len;") - } - - ofs := 1 - lastOfs := 0 - if c(key, a[base+hint]) { - // Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs] - maxOfs := hint + 1 - for ofs < maxOfs && c(key, a[base+hint-ofs]) { - lastOfs = ofs - ofs = (ofs << 1) + 1 - if ofs <= 0 { // int overflow - ofs = maxOfs - } - } - if ofs > maxOfs { - ofs = maxOfs - } - - // Make offsets relative to b - tmp := lastOfs - lastOfs = hint - ofs - ofs = hint - tmp - } else { // a[b + hint] <= key - // Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs] - maxOfs := len - hint - for ofs < maxOfs && !c(key, a[base+hint+ofs]) { - lastOfs = ofs - ofs = (ofs << 1) + 1 - if ofs <= 0 { // int overflow - ofs = maxOfs - } - } - if ofs > maxOfs { - ofs = maxOfs - } - - // Make offsets relative to b - lastOfs += hint - ofs += hint - } - if -1 > lastOfs || lastOfs >= ofs || ofs > len { - return 0, errors.New("-1 <= lastOfs && lastOfs < ofs && ofs <= len") - } - - /* - * Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to - * the right of lastOfs but no farther right than ofs. Do a binary - * search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs]. - */ - lastOfs++ - for lastOfs < ofs { - m := lastOfs + (ofs-lastOfs)/2 - - if c(key, a[base+m]) { - ofs = m // key < a[b + m] - } else { - lastOfs = m + 1 // a[b + m] <= key - } - } - if lastOfs != ofs { - return 0, errors.New(" lastOfs == ofs") // so a[b + ofs - 1] <= key < a[b + ofs] - } - return ofs, nil -} - -/** - * Merges two adjacent runs in place, in a stable fashion. The first - * element of the first run must be greater than the first element of the - * second run (a[base1] > a[base2]), and the last element of the first run - * (a[base1 + len1-1]) must be greater than all elements of the second run. - * - * For performance, this method should be called only when len1 <= len2; - * its twin, mergeHi should be called if len1 >= len2. (Either method - * may be called if len1 == len2.) - * - * @param base1 index of first element in first run to be merged - * @param len1 length of first run to be merged (must be > 0) - * @param base2 index of first element in second run to be merged - * (must be aBase + aLen) - * @param len2 length of second run to be merged (must be > 0) - */ -func (self *timSortHandler) mergeLo(base1, len1, base2, len2 int) (err error) { - if len1 <= 0 || len2 <= 0 || base1+len1 != base2 { - return errors.New(" len1 > 0 && len2 > 0 && base1 + len1 == base2") - } - - // Copy first run into temp array - a := self.a // For performance - tmp := self.ensureCapacity(len1) - - copy(tmp, a[base1:base1+len1]) - - cursor1 := 0 // Indexes into tmp array - cursor2 := base2 // Indexes int a - dest := base1 // Indexes int a - - // Move first element of second run and deal with degenerate cases - a[dest] = a[cursor2] - dest++ - cursor2++ - len2-- - if len2 == 0 { - copy(a[dest:dest+len1], tmp) - return - } - if len1 == 1 { - copy(a[dest:dest+len2], a[cursor2:cursor2+len2]) - a[dest+len2] = tmp[cursor1] // Last elt of run 1 to end of merge - return - } - - lt := self.lt // Use local variable for performance - minGallop := self.minGallop // " " " " " - -outer: - for { - count1 := 0 // Number of times in a row that first run won - count2 := 0 // Number of times in a row that second run won - - /* - * Do the straightforward thing until (if ever) one run starts - * winning consistently. - */ - for { - if len1 <= 1 || len2 <= 0 { - return errors.New(" len1 > 1 && len2 > 0") - } - - if lt(a[cursor2], tmp[cursor1]) { - a[dest] = a[cursor2] - dest++ - cursor2++ - count2++ - count1 = 0 - len2-- - if len2 == 0 { - break outer - } - } else { - a[dest] = tmp[cursor1] - dest++ - cursor1++ - count1++ - count2 = 0 - len1-- - if len1 == 1 { - break outer - } - } - if (count1 | count2) >= minGallop { - break - } - } - - /* - * One run is winning so consistently that galloping may be a - * huge win. So try that, and continue galloping until (if ever) - * neither run appears to be winning consistently anymore. - */ - for { - if len1 <= 1 || len2 <= 0 { - return errors.New("len1 > 1 && len2 > 0") - } - count1, err = gallopRight(a[cursor2], tmp, cursor1, len1, 0, lt) - if err != nil { - return - } - if count1 != 0 { - copy(a[dest:dest+count1], tmp[cursor1:cursor1+count1]) - dest += count1 - cursor1 += count1 - len1 -= count1 - if len1 <= 1 { // len1 == 1 || len1 == 0 - break outer - } - } - a[dest] = a[cursor2] - dest++ - cursor2++ - len2-- - if len2 == 0 { - break outer - } - - count2, err = gallopLeft(tmp[cursor1], a, cursor2, len2, 0, lt) - if err != nil { - return - } - if count2 != 0 { - copy(a[dest:dest+count2], a[cursor2:cursor2+count2]) - dest += count2 - cursor2 += count2 - len2 -= count2 - if len2 == 0 { - break outer - } - } - a[dest] = tmp[cursor1] - dest++ - cursor1++ - len1-- - if len1 == 1 { - break outer - } - minGallop-- - if count1 < _MIN_GALLOP && count2 < _MIN_GALLOP { - break - } - } - if minGallop < 0 { - minGallop = 0 - } - minGallop += 2 // Penalize for leaving gallop mode - } // End of "outer" loop - - if minGallop < 1 { - minGallop = 1 - } - self.minGallop = minGallop // Write back to field - - if len1 == 1 { - - if len2 <= 0 { - return errors.New(" len2 > 0;") - } - copy(a[dest:dest+len2], a[cursor2:cursor2+len2]) - a[dest+len2] = tmp[cursor1] // Last elt of run 1 to end of merge - } else if len1 == 0 { - return errors.New("Comparison method violates its general contract!") - } else { - if len2 != 0 { - return errors.New("len2 == 0;") - } - if len1 <= 1 { - return errors.New(" len1 > 1;") - } - - copy(a[dest:dest+len1], tmp[cursor1:cursor1+len1]) - } - return -} - -/** - * Like mergeLo, except that this method should be called only if - * len1 >= len2; mergeLo should be called if len1 <= len2. (Either method - * may be called if len1 == len2.) - * - * @param base1 index of first element in first run to be merged - * @param len1 length of first run to be merged (must be > 0) - * @param base2 index of first element in second run to be merged - * (must be aBase + aLen) - * @param len2 length of second run to be merged (must be > 0) - */ -func (self *timSortHandler) mergeHi(base1, len1, base2, len2 int) (err error) { - if len1 <= 0 || len2 <= 0 || base1+len1 != base2 { - return errors.New("len1 > 0 && len2 > 0 && base1 + len1 == base2;") - } - - // Copy second run into temp array - a := self.a // For performance - tmp := self.ensureCapacity(len2) - - copy(tmp, a[base2:base2+len2]) - - cursor1 := base1 + len1 - 1 // Indexes into a - cursor2 := len2 - 1 // Indexes into tmp array - dest := base2 + len2 - 1 // Indexes into a - - // Move last element of first run and deal with degenerate cases - a[dest] = a[cursor1] - dest-- - cursor1-- - len1-- - if len1 == 0 { - dest -= len2 - 1 - copy(a[dest:dest+len2], tmp) - return - } - if len2 == 1 { - dest -= len1 - 1 - cursor1 -= len1 - 1 - copy(a[dest:dest+len1], a[cursor1:cursor1+len1]) - a[dest-1] = tmp[cursor2] - return - } - - lt := self.lt // Use local variable for performance - minGallop := self.minGallop // " " " " " - -outer: - for { - count1 := 0 // Number of times in a row that first run won - count2 := 0 // Number of times in a row that second run won - - /* - * Do the straightforward thing until (if ever) one run - * appears to win consistently. - */ - for { - if len1 <= 0 || len2 <= 1 { - return errors.New(" len1 > 0 && len2 > 1;") - } - if lt(tmp[cursor2], a[cursor1]) { - a[dest] = a[cursor1] - dest-- - cursor1-- - count1++ - count2 = 0 - len1-- - if len1 == 0 { - break outer - } - } else { - a[dest] = tmp[cursor2] - dest-- - cursor2-- - count2++ - count1 = 0 - len2-- - if len2 == 1 { - break outer - } - } - if (count1 | count2) >= minGallop { - break - } - } - - /* - * One run is winning so consistently that galloping may be a - * huge win. So try that, and continue galloping until (if ever) - * neither run appears to be winning consistently anymore. - */ - for { - if len1 <= 0 || len2 <= 1 { - return errors.New(" len1 > 0 && len2 > 1;") - } - if gr, err := gallopRight(tmp[cursor2], a, base1, len1, len1-1, lt); err == nil { - count1 = len1 - gr - } else { - return err - } - if count1 != 0 { - dest -= count1 - cursor1 -= count1 - len1 -= count1 - copy(a[dest+1:dest+1+count1], a[cursor1+1:cursor1+1+count1]) - if len1 == 0 { - break outer - } - } - a[dest] = tmp[cursor2] - dest-- - cursor2-- - len2-- - if len2 == 1 { - break outer - } - - if gl, err := gallopLeft(a[cursor1], tmp, 0, len2, len2-1, lt); err == nil { - count2 = len2 - gl - } else { - return err - } - if count2 != 0 { - dest -= count2 - cursor2 -= count2 - len2 -= count2 - copy(a[dest+1:dest+1+count2], tmp[cursor2+1:cursor2+1+count2]) - if len2 <= 1 { // len2 == 1 || len2 == 0 - break outer - } - } - a[dest] = a[cursor1] - dest-- - cursor1-- - len1-- - if len1 == 0 { - break outer - } - minGallop-- - - if count1 < _MIN_GALLOP && count2 < _MIN_GALLOP { - break - } - } - if minGallop < 0 { - minGallop = 0 - } - minGallop += 2 // Penalize for leaving gallop mode - } // End of "outer" loop - - if minGallop < 1 { - minGallop = 1 - } - - self.minGallop = minGallop // Write back to field - - if len2 == 1 { - if len1 <= 0 { - return errors.New(" len1 > 0;") - } - dest -= len1 - cursor1 -= len1 - - copy(a[dest+1:dest+1+len1], a[cursor1+1:cursor1+1+len1]) - a[dest] = tmp[cursor2] // Move first elt of run2 to front of merge - } else if len2 == 0 { - return errors.New("Comparison method violates its general contract!") - } else { - if len1 != 0 { - return errors.New("len1 == 0;") - } - - if len2 <= 0 { - return errors.New(" len2 > 0;") - } - - copy(a[dest-(len2-1):dest+1], tmp) - } - return -} - -/** - * Ensures that the external array tmp has at least the specified - * number of elements, increasing its size if necessary. The size - * increases exponentially to ensure amortized linear time complexity. - * - * @param minCapacity the minimum required capacity of the tmp array - * @return tmp, whether or not it grew - */ -func (self *timSortHandler) ensureCapacity(minCapacity int) []interface{} { - if len(self.tmp) < minCapacity { - // Compute smallest power of 2 > minCapacity - newSize := minCapacity - newSize |= newSize >> 1 - newSize |= newSize >> 2 - newSize |= newSize >> 4 - newSize |= newSize >> 8 - newSize |= newSize >> 16 - newSize++ - - if newSize < 0 { // Not bloody likely! - newSize = minCapacity - } else { - ns := len(self.a) / 2 - if ns < newSize { - newSize = ns - } - } - - self.tmp = make([]interface{}, newSize) - } - - return self.tmp -} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 00000000..262c6257 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,47 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package utils provides common utility functions. +// +// Provided functionalities: +// - sorting +// - comparators +package utils + +import ( + "fmt" + "strconv" +) + +// ToString converts a value to string. +func ToString(value interface{}) string { + switch value := value.(type) { + case string: + return value + case int8: + return strconv.FormatInt(int64(value), 10) + case int16: + return strconv.FormatInt(int64(value), 10) + case int32: + return strconv.FormatInt(int64(value), 10) + case int64: + return strconv.FormatInt(value, 10) + case uint8: + return strconv.FormatUint(uint64(value), 10) + case uint16: + return strconv.FormatUint(uint64(value), 10) + case uint32: + return strconv.FormatUint(uint64(value), 10) + case uint64: + return strconv.FormatUint(value, 10) + case float32: + return strconv.FormatFloat(float64(value), 'g', -1, 64) + case float64: + return strconv.FormatFloat(value, 'g', -1, 64) + case bool: + return strconv.FormatBool(value) + default: + return fmt.Sprintf("%+v", value) + } +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 00000000..afbca1b9 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package utils + +import ( + "strings" + "testing" +) + +func TestToStringInts(t *testing.T) { + var value interface{} + + value = int8(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = int16(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = int32(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = int64(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = rune(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestToStringUInts(t *testing.T) { + var value interface{} + + value = uint8(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = uint16(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = uint32(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = uint64(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = byte(1) + if actualValue, expectedValue := ToString(value), "1"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestToStringFloats(t *testing.T) { + var value interface{} + + value = float32(1.123456) + if actualValue, expectedValue := ToString(value), "1.123456"; !strings.HasPrefix(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + value = float64(1.123456) + if actualValue, expectedValue := ToString(value), "1.123456"; !strings.HasPrefix(actualValue, expectedValue) { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestToStringOther(t *testing.T) { + var value interface{} + + value = "abc" + if actualValue, expectedValue := ToString(value), "abc"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + value = true + if actualValue, expectedValue := ToString(value), "true"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + + type T struct { + id int + name string + } + + if actualValue, expectedValue := ToString(T{1, "abc"}), "{id:1 name:abc}"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +}