├── .github ├── CODEOWNERS ├── FUNDING.yml └── workflows │ └── checks.yml ├── go.mod ├── README.md ├── .gitignore ├── stack ├── stack.go └── stack_test.go ├── Makefile ├── LICENSE ├── set └── hash_set.go ├── .golangci.yml ├── queue ├── queue.go └── queue_test.go ├── go.sum ├── tree ├── tree.go └── tree_test.go ├── binary ├── tree.go └── tree_test.go └── list ├── list_test.go └── list.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in the repo. 2 | # Unless a later match takes precedence, @umputun will be requested for 3 | # review when someone opens a pull request. 4 | 5 | * @tomakado 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.tomakado.io/containers 2 | 3 | go 1.19 4 | 5 | require github.com/stretchr/testify v1.8.1 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 11 | gopkg.in/yaml.v3 v3.0.1 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # containers [![Go Reference](https://pkg.go.dev/badge/go.tomakado.io/containers.svg)](https://pkg.go.dev/go.tomakado.io/containers) [![Coverage Status](https://coveralls.io/repos/github/tomakado/containers/badge.svg?branch=master)](https://coveralls.io/github/tomakado/containers?branch=master) [![Go Report Card](https://goreportcard.com/badge/go.tomakado.io/containers)](https://goreportcard.com/report/go.tomakado.io/containers) 2 | 3 | Collection of simple generic data structures currently missing in Go's standard library. 4 | 5 | # Install 6 | 7 | ```bash 8 | go get -u go.tomakado.io/containers 9 | ``` 10 | 11 | # Data structures 12 | 13 | * Set 14 | * Unordered (hash set) 15 | * [TODO] Ordered 16 | * List 17 | * Queue 18 | * Stack 19 | * [TODO] Heap 20 | * Tree 21 | * N-ary tree 22 | * Binary tree 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/go 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=go 3 | 4 | ### Go ### 5 | # If you prefer the allow list template instead of the deny list, see community template: 6 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 7 | # 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | # Go workspace file 25 | go.work 26 | 27 | # End of https://www.toptal.com/developers/gitignore/api/go 28 | 29 | bin/ 30 | 31 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: deferpanic # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /stack/stack.go: -------------------------------------------------------------------------------- 1 | package stack 2 | 3 | type Stack[T any] struct { 4 | top int 5 | elements []T 6 | } 7 | 8 | func New[T any](capacity int) *Stack[T] { 9 | return &Stack[T]{ 10 | top: -1, 11 | elements: make([]T, 0, capacity), 12 | } 13 | } 14 | 15 | func (s *Stack[T]) IsEmpty() bool { 16 | return len(s.elements) == 0 17 | } 18 | 19 | func (s *Stack[T]) Push(e T) { 20 | s.elements = append(s.elements, e) 21 | } 22 | 23 | func (s *Stack[T]) Pop() (T, bool) { 24 | if s.IsEmpty() { 25 | var zero T 26 | return zero, false 27 | } 28 | 29 | e := s.elements[len(s.elements)-1] 30 | s.elements = s.elements[:len(s.elements)-1] 31 | 32 | return e, true 33 | } 34 | 35 | func (s *Stack[T]) Peek() (T, bool) { 36 | if s.IsEmpty() { 37 | var zero T 38 | return zero, false 39 | } 40 | 41 | return s.elements[len(s.elements)-1], true 42 | } 43 | 44 | func (s *Stack[T]) Len() int { 45 | return len(s.elements) 46 | } 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_DIR = $(shell pwd) 2 | PROJECT_BIN = $(PROJECT_DIR)/bin 3 | $(shell [ -f bin ] || mkdir -p $(PROJECT_BIN)) 4 | PATH := $(PROJECT_BIN):$(PATH) 5 | 6 | GOLANGCI_LINT = $(PROJECT_BIN)/golangci-lint 7 | GOLANGCI_LINT_VERSION = v1.50.1 8 | 9 | .PHONY: .install-linter 10 | .install-linter: 11 | ### INSTALL GOLANGCI-LINT ### 12 | [ -f $(PROJECT_BIN)/golangci-lint ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(PROJECT_BIN) $(GOLANGCI_LINT_VERSION) 13 | 14 | .PHONY: lint 15 | lint: .install-linter 16 | ### RUN GOLANGCI-LINT ### 17 | $(GOLANGCI_LINT) run ./... --config=./.golangci.yml 18 | 19 | .PHONY: lint-fast 20 | lint-fast: .install-linter 21 | $(GOLANGCI_LINT) run ./... --fast --config=./.golangci.yml 22 | 23 | # === Test === 24 | .PHONY: test 25 | test: 26 | go test -v --race --timeout=5m --cover ./... 27 | 28 | .PHONY: test-coverage 29 | test-coverage: 30 | go test -v --timeout=5m --covermode=count --coverprofile=coverage.out ./... 31 | go tool cover --func=coverage.out 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2022 Ildar Karymov (tomakado) 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs checks againts pull request or current codebase at master branc 2 | name: checks 3 | 4 | on: 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | 10 | jobs: 11 | 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v3 19 | with: 20 | go-version: 1.19 21 | 22 | - name: lint 23 | run: make lint 24 | 25 | test: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | 30 | - name: Set up Go 31 | uses: actions/setup-go@v3 32 | with: 33 | go-version: 1.19 34 | 35 | - name: test 36 | run: make test-coverage 37 | 38 | - name: install goveralls 39 | run: GO111MODULE=off go get -u github.com/mattn/goveralls 40 | 41 | - name: submit coverage 42 | run: $(go env GOPATH)/bin/goveralls -service="github" -coverprofile=coverage.out 43 | env: 44 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | -------------------------------------------------------------------------------- /set/hash_set.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | // HashSet is unordered set of unique elements. 4 | // It's built on top of map, so you can iterate over set like over map: 5 | // 6 | // hashSet := containers.NewHashSet[string]() 7 | // for element := range hashSet { 8 | // fmt.Println(element) 9 | // } 10 | type HashSet[T comparable] map[T]struct{} 11 | 12 | func New[T comparable](elements ...T) HashSet[T] { 13 | s := HashSet[T]{} 14 | 15 | for _, e := range elements { 16 | s.Add(e) 17 | } 18 | 19 | return s 20 | } 21 | 22 | // Add adds element to the set. 23 | func (s HashSet[T]) Add(element T) { 24 | s[element] = struct{}{} 25 | } 26 | 27 | // Contains checks if element is in the set. 28 | func (s HashSet[T]) Contains(element T) bool { 29 | _, ok := s[element] 30 | return ok 31 | } 32 | 33 | // Remove removes element from the set. 34 | func (s HashSet[T]) Remove(e T) { 35 | delete(s, e) 36 | } 37 | 38 | // Slice return elements of the set as slice with the same type. Order of elements is not guaranteed. 39 | func (s HashSet[T]) Slice() []T { 40 | slice := make([]T, 0, len(s)) 41 | 42 | for e := range s { 43 | slice = append(slice, e) 44 | } 45 | 46 | return slice 47 | } 48 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This file contains all available configuration options for this project. 2 | 3 | # options for analysis running 4 | run: 5 | # timeout for analysis, e.g. 30s, 5m, default is 1m 6 | timeout: 3m 7 | gofmt: 8 | # simplify code: gofmt with `-s` option, true by default 9 | simplify: true 10 | revive: 11 | rules: 12 | - name: line-length-limit 13 | arguments: 120 14 | gofumports: 15 | local-prefixes: "github.com/tomakado/containers" 16 | 17 | output: 18 | sort-results: true 19 | 20 | issues: 21 | exclude-use-default: true 22 | fix: false 23 | exclude-rules: 24 | - linters: 25 | - revive 26 | source: '^// \d+(\.\d+)*\.' 27 | 28 | - linters: 29 | - revive 30 | text: 'should not use basic type string as key in context.WithValue' 31 | 32 | - linters: 33 | - revive 34 | text: "don't use an underscore in package name" 35 | 36 | - linters: 37 | - staticcheck 38 | text: 'SA1029:' # the same as revive's rule about string as a key in context.WithValue 39 | 40 | - linters: 41 | - revive 42 | text: "comment on exported var" 43 | 44 | - linters: 45 | - revive 46 | text: var-naming 47 | 48 | - linters: 49 | - revive 50 | text: dot-imports 51 | 52 | linters: 53 | enable: 54 | - revive 55 | - gofmt 56 | - gosimple 57 | - misspell 58 | - goimports 59 | - godot 60 | - cyclop 61 | - gocognit 62 | - gocritic 63 | - prealloc 64 | - wsl 65 | - goconst 66 | 67 | -------------------------------------------------------------------------------- /queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | type Queue[T comparable] struct { 4 | head *node[T] 5 | tail *node[T] 6 | length int 7 | } 8 | 9 | func New[T comparable](items ...T) *Queue[T] { 10 | q := &Queue[T]{} 11 | 12 | for _, item := range items { 13 | q.Enqueue(item) 14 | } 15 | 16 | return q 17 | } 18 | 19 | func (q *Queue[T]) Clear() { 20 | q.head = nil 21 | q.tail = nil 22 | q.length = 0 23 | } 24 | 25 | func (q *Queue[T]) Contains(item T) bool { 26 | n := q.head 27 | 28 | for n.next != nil { 29 | if n.value == item { 30 | return true 31 | } 32 | 33 | n = n.next 34 | } 35 | 36 | return false 37 | } 38 | func (q *Queue[T]) Dequeue() (T, bool) { 39 | n := q.head 40 | if n == nil { 41 | return node[T]{}.value, false 42 | } 43 | 44 | next := q.head.next 45 | q.head = next 46 | q.length-- 47 | 48 | return n.value, true 49 | } 50 | 51 | func (q *Queue[T]) Enqueue(item T) { 52 | n := &node[T]{value: item} 53 | q.length++ 54 | 55 | if q.head == nil { 56 | q.head = n 57 | q.tail = q.head 58 | 59 | return 60 | } 61 | 62 | q.tail.next = n 63 | q.tail = q.tail.next 64 | } 65 | 66 | func (q *Queue[T]) Peek() (T, bool) { 67 | n := q.head 68 | if n == nil { 69 | return node[T]{}.value, false 70 | } 71 | 72 | return n.value, true 73 | } 74 | 75 | func (q *Queue[T]) Len() int { 76 | return q.length 77 | } 78 | 79 | func (q *Queue[T]) Slice() []T { 80 | slice := make([]T, 0, q.Len()) 81 | 82 | for q.Len() > 0 { 83 | item, _ := q.Dequeue() 84 | slice = append(slice, item) 85 | } 86 | 87 | return slice 88 | } 89 | 90 | type node[T comparable] struct { 91 | value T 92 | next *node[T] 93 | } 94 | -------------------------------------------------------------------------------- /stack/stack_test.go: -------------------------------------------------------------------------------- 1 | package stack_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "go.tomakado.io/containers/stack" 8 | ) 9 | 10 | func TestStack(t *testing.T) { 11 | s := stack.New[int](10) 12 | 13 | t.Run("IsEmpty", func(t *testing.T) { 14 | assert.True(t, s.IsEmpty(), "Expected stack to be empty") 15 | }) 16 | 17 | t.Run("Push and Peek", func(t *testing.T) { 18 | s.Push(1) 19 | s.Push(2) 20 | s.Push(3) 21 | 22 | val, ok := s.Peek() 23 | 24 | assert.Equal(t, 3, val, "Expected 3 at the top of the stack") 25 | assert.True(t, ok, "Expected Peek to return true") 26 | }) 27 | 28 | t.Run("Peek empty stack", func(t *testing.T) { 29 | var ( 30 | s = stack.New[int](10) 31 | val, ok = s.Peek() 32 | ) 33 | 34 | assert.False(t, ok, "Expected Pop to return false") 35 | assert.Equal(t, 0, val, "Expected 0 to be popped off the stack") 36 | }) 37 | 38 | t.Run("Len", func(t *testing.T) { 39 | assert.Equal(t, 3, s.Len(), "Expected stack to have length 3") 40 | }) 41 | 42 | t.Run("Pop", func(t *testing.T) { 43 | t.Run("non-empty stack", func(t *testing.T) { 44 | val, ok := s.Pop() 45 | 46 | assert.Equal(t, 3, val, "Expected 3 to be popped off the stack") 47 | assert.True(t, ok, "Expected Pop to return true") 48 | assert.Equal(t, 2, s.Len(), "Expected stack to have length 2") 49 | }) 50 | 51 | t.Run("empty stack", func(t *testing.T) { 52 | var ( 53 | s = stack.New[int](10) 54 | val, ok = s.Pop() 55 | ) 56 | 57 | assert.False(t, ok, "Expected Pop to return false") 58 | assert.Equal(t, 0, val, "Expected 0 to be popped off the stack") 59 | }) 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 9 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 11 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 12 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 13 | golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 h1:BEABXpNXLEz0WxtA+6CQIz2xkg80e+1zrhWyMcq8VzE= 14 | golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /tree/tree.go: -------------------------------------------------------------------------------- 1 | package tree 2 | 3 | import "go.tomakado.io/containers/queue" 4 | 5 | // Node is a node of N-ary tree. 6 | // Implementation is based on code by Ilija Eftimov (URL: https://ieftimov.com/posts/golang-datastructures-trees/). 7 | type Node[T comparable] struct { 8 | Value T 9 | Parent *Node[T] 10 | Children []*Node[T] 11 | } 12 | 13 | func (n *Node[T]) Append(c *Node[T]) { 14 | c.Parent = n 15 | n.Children = append(n.Children, c) 16 | } 17 | 18 | func (n *Node[T]) Remove() { 19 | for i, sibling := range n.Parent.Children { 20 | if sibling == n { 21 | n.Parent.Children = append(n.Parent.Children[:i], n.Parent.Children[i+1:]...) 22 | break 23 | } 24 | } 25 | 26 | if len(n.Children) != 0 { 27 | for _, child := range n.Children { 28 | child.Parent = nil 29 | } 30 | } 31 | 32 | n.Parent = nil 33 | } 34 | 35 | func (n *Node[T]) Depth() int { 36 | if len(n.Children) == 0 { 37 | return 1 38 | } 39 | 40 | max := 0 41 | 42 | for _, c := range n.Children { 43 | d := c.Depth() 44 | if d > max { 45 | max = d 46 | } 47 | } 48 | 49 | return max + 1 50 | } 51 | 52 | func (n *Node[T]) BFS(value T) (*Node[T], bool) { 53 | q := queue.New(n) 54 | 55 | for q.Len() > 0 { 56 | node, ok := q.Dequeue() 57 | if !ok { 58 | break 59 | } 60 | 61 | if node.Value == value { 62 | return node, true 63 | } 64 | 65 | for _, c := range node.Children { 66 | q.Enqueue(c) 67 | } 68 | } 69 | 70 | return nil, false 71 | } 72 | 73 | func (n *Node[T]) DFS(value T) (*Node[T], bool) { 74 | if n.Value == value { 75 | return n, true 76 | } 77 | 78 | if len(n.Children) == 0 { 79 | return nil, false 80 | } 81 | 82 | for _, c := range n.Children { 83 | node, ok := c.DFS(value) 84 | if ok { 85 | return node, true 86 | } 87 | } 88 | 89 | return nil, false 90 | } 91 | -------------------------------------------------------------------------------- /tree/tree_test.go: -------------------------------------------------------------------------------- 1 | package tree_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "go.tomakado.io/containers/tree" 8 | ) 9 | 10 | func TestAppend(t *testing.T) { 11 | var ( 12 | node = &tree.Node[int]{Value: 1} 13 | child = &tree.Node[int]{Value: 2} 14 | ) 15 | 16 | node.Append(child) 17 | assert.Equal(t, node, child.Parent) 18 | assert.Contains(t, node.Children, child) 19 | } 20 | 21 | func TestRemove(t *testing.T) { 22 | var ( 23 | node = &tree.Node[int]{Value: 1} 24 | child = &tree.Node[int]{Value: 2} 25 | ) 26 | 27 | node.Append(child) 28 | child.Remove() 29 | 30 | assert.Nil(t, child.Parent) 31 | assert.NotContains(t, node.Children, child) 32 | } 33 | 34 | func TestDepth(t *testing.T) { 35 | var ( 36 | node = &tree.Node[int]{Value: 1} 37 | child1 = &tree.Node[int]{Value: 2} 38 | child2 = &tree.Node[int]{Value: 3} 39 | child3 = &tree.Node[int]{Value: 4} 40 | ) 41 | 42 | node.Append(child1) 43 | node.Append(child2) 44 | child2.Append(child3) 45 | 46 | assert.Equal(t, 3, node.Depth()) 47 | } 48 | 49 | func TestBFS(t *testing.T) { 50 | var ( 51 | node = &tree.Node[int]{Value: 1} 52 | child1 = &tree.Node[int]{Value: 2} 53 | child2 = &tree.Node[int]{Value: 3} 54 | child3 = &tree.Node[int]{Value: 4} 55 | ) 56 | 57 | node.Append(child1) 58 | node.Append(child2) 59 | child2.Append(child3) 60 | 61 | foundNode, found := node.BFS(3) 62 | assert.True(t, found) 63 | assert.Equal(t, child2, foundNode) 64 | 65 | notFoundNode, notFound := node.BFS(5) 66 | assert.False(t, notFound) 67 | assert.Nil(t, notFoundNode) 68 | } 69 | 70 | func TestDFS(t *testing.T) { 71 | var ( 72 | node = &tree.Node[int]{Value: 1} 73 | child1 = &tree.Node[int]{Value: 2} 74 | child2 = &tree.Node[int]{Value: 3} 75 | child3 = &tree.Node[int]{Value: 4} 76 | ) 77 | 78 | node.Append(child1) 79 | node.Append(child2) 80 | child2.Append(child3) 81 | 82 | foundNode, found := node.DFS(3) 83 | assert.True(t, found) 84 | assert.Equal(t, child2, foundNode) 85 | 86 | notFoundNode, notFound := node.DFS(5) 87 | assert.False(t, notFound) 88 | assert.Nil(t, notFoundNode) 89 | } 90 | -------------------------------------------------------------------------------- /binary/tree.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | type Node[K, V constraints.Ordered] struct { 8 | Key K 9 | Value V 10 | Left, Right *Node[K, V] 11 | } 12 | 13 | func (n *Node[K, V]) Append(node *Node[K, V]) { 14 | if node.Key < n.Key { 15 | if n.Left == nil { 16 | n.Left = node 17 | return 18 | } 19 | 20 | n.Left.Append(node) 21 | 22 | return 23 | } 24 | 25 | if n.Right == nil { 26 | n.Right = node 27 | return 28 | } 29 | 30 | n.Right.Append(node) 31 | } 32 | 33 | func (n *Node[K, V]) PreOrder(f func(node *Node[K, V])) { 34 | if n == nil { 35 | return 36 | } 37 | 38 | f(n) 39 | n.Left.PreOrder(f) 40 | n.Right.PreOrder(f) 41 | } 42 | 43 | func (n *Node[K, V]) InOrder(f func(node *Node[K, V])) { 44 | if n == nil { 45 | return 46 | } 47 | 48 | n.Left.InOrder(f) 49 | f(n) 50 | n.Right.InOrder(f) 51 | } 52 | 53 | func (n *Node[K, V]) PostOrder(f func(node *Node[K, V])) { 54 | if n == nil { 55 | return 56 | } 57 | 58 | n.Left.PostOrder(f) 59 | n.Right.PostOrder(f) 60 | f(n) 61 | } 62 | 63 | func (n *Node[K, V]) Search(key K) (*Node[K, V], bool) { 64 | if n == nil { 65 | return nil, false 66 | } 67 | 68 | if key < n.Key { 69 | return n.Left.Search(key) 70 | } 71 | 72 | if key > n.Key { 73 | return n.Right.Search(key) 74 | } 75 | 76 | return n, true 77 | } 78 | 79 | func (n *Node[K, V]) Remove(key K) *Node[K, V] { 80 | if n == nil { 81 | return nil 82 | } 83 | 84 | if key < n.Key { 85 | n.Left = n.Left.Remove(key) 86 | return n 87 | } 88 | 89 | if key > n.Key { 90 | n.Right = n.Right.Remove(key) 91 | return n 92 | } 93 | 94 | return n.removeSelf(key) 95 | } 96 | 97 | func (n *Node[K, V]) removeSelf(key K) *Node[K, V] { 98 | if n.Left == nil && n.Right == nil { 99 | n = nil 100 | return nil 101 | } 102 | 103 | if n.Left == nil { 104 | n = n.Right 105 | return n 106 | } 107 | 108 | if n.Right == nil { 109 | n = n.Left 110 | return n 111 | } 112 | 113 | leftMostRightSide := n.Right 114 | 115 | for { 116 | if leftMostRightSide == nil || leftMostRightSide.Left == nil { 117 | break 118 | } 119 | 120 | leftMostRightSide = leftMostRightSide.Left 121 | } 122 | 123 | n.Key, n.Value = leftMostRightSide.Key, leftMostRightSide.Value 124 | n.Right.Remove(n.Key) 125 | 126 | return n 127 | } 128 | -------------------------------------------------------------------------------- /list/list_test.go: -------------------------------------------------------------------------------- 1 | package list_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "go.tomakado.io/containers/list" 8 | ) 9 | 10 | func TestList(t *testing.T) { 11 | t.Run("single element list", func(t *testing.T) { 12 | var ( 13 | list = list.New[string]() 14 | e = list.PushBack("foo") 15 | ) 16 | 17 | assert.Equal(t, 1, list.Len()) 18 | assert.Equal(t, e, list.Front()) 19 | 20 | list.MoveToFront(e) 21 | assert.Equal(t, 1, list.Len()) 22 | assert.Equal(t, e, list.Front()) 23 | 24 | list.MoveToBack(e) 25 | assert.Equal(t, 1, list.Len()) 26 | assert.Equal(t, e, list.Front()) 27 | 28 | list.Remove(e) 29 | assert.Equal(t, 0, list.Len()) 30 | assert.Nil(t, list.Front()) 31 | }) 32 | 33 | t.Run("bigger list", func(t *testing.T) { 34 | var ( 35 | list = list.New[int]() 36 | e1 = list.PushBack(2) 37 | e2 = list.PushBack(1) 38 | e3 = list.PushBack(1) 39 | ) 40 | 41 | assert.Equal(t, 3, list.Len()) 42 | assert.Equal(t, e1, list.Front()) 43 | assert.Equal(t, e3, list.Back()) 44 | 45 | list.MoveToFront(e2) 46 | assert.Equal(t, 3, list.Len()) 47 | assert.Equal(t, e2, list.Front()) 48 | assert.Equal(t, e3, list.Back()) 49 | 50 | list.MoveToBack(e2) 51 | assert.Equal(t, 3, list.Len()) 52 | assert.Equal(t, e1, list.Front()) 53 | assert.Equal(t, e2, list.Back()) 54 | 55 | list.Remove(e2) 56 | assert.Equal(t, 2, list.Len()) 57 | assert.Equal(t, e1, list.Front()) 58 | assert.Equal(t, e3, list.Back()) 59 | }) 60 | 61 | t.Run("iteration", func(t *testing.T) { 62 | var ( 63 | list = list.New[int]() 64 | ) 65 | 66 | list.PushBack(1) 67 | list.PushBack(2) 68 | list.PushBack(3) 69 | 70 | var ( 71 | i = 0 72 | sum = 0 73 | ) 74 | 75 | for e := list.Front(); e != nil; e = e.Next() { 76 | sum += e.Value 77 | i++ 78 | } 79 | 80 | assert.Equal(t, 3, i) 81 | assert.Equal(t, 6, sum) 82 | }) 83 | 84 | t.Run("clear all elements by iterating", func(t *testing.T) { 85 | var ( 86 | list = list.New[int]() 87 | ) 88 | 89 | list.PushBack(1) 90 | list.PushBack(2) 91 | list.PushBack(3) 92 | 93 | for e := list.Front(); e != nil; e = e.Next() { 94 | toDelete := *e 95 | list.Remove(&toDelete) 96 | } 97 | 98 | assert.Equal(t, 0, list.Len()) 99 | }) 100 | 101 | t.Run("extend", func(t *testing.T) { 102 | var ( 103 | l1 = list.New[int]() 104 | l2 = list.New[int]() 105 | l3 = list.New[int]() 106 | ) 107 | 108 | l1.PushBack(1) 109 | l1.PushBack(2) 110 | l1.PushBack(3) 111 | 112 | l2.PushBack(4) 113 | l2.PushBack(5) 114 | 115 | l3.PushBackList(l1) 116 | assert.Equal(t, 3, l3.Len()) 117 | 118 | l3.PushBackList(l2) 119 | assert.Equal(t, 5, l3.Len()) 120 | 121 | l3 = list.New[int]() 122 | l3.PushFrontList(l1) 123 | assert.Equal(t, 3, l3.Len()) 124 | 125 | l3.PushFrontList(l2) 126 | assert.Equal(t, 5, l3.Len()) 127 | }) 128 | 129 | t.Run("remove", func(t *testing.T) { 130 | var ( 131 | list = list.New[int]() 132 | e1 = list.PushBack(1) 133 | e2 = list.PushBack(2) 134 | ) 135 | 136 | list.Remove(e1) 137 | assert.Equal(t, 1, list.Len()) 138 | assert.Equal(t, e2, list.Front()) 139 | 140 | list.Remove(e2) 141 | assert.Equal(t, 0, list.Len()) 142 | assert.Nil(t, list.Front()) 143 | }) 144 | 145 | t.Run("move", func(t *testing.T) { 146 | var ( 147 | list = list.New[int]() 148 | e1 = list.PushBack(1) 149 | e2 = list.PushBack(2) 150 | ) 151 | 152 | list.MoveToFront(e2) 153 | assert.Equal(t, 2, list.Len()) 154 | assert.Equal(t, e2, list.Front()) 155 | assert.Equal(t, e1, list.Back()) 156 | 157 | list.MoveToBack(e2) 158 | assert.Equal(t, 2, list.Len()) 159 | assert.Equal(t, e1, list.Front()) 160 | assert.Equal(t, e2, list.Back()) 161 | 162 | list.MoveBefore(e1, e2) 163 | assert.Equal(t, 2, list.Len()) 164 | assert.Equal(t, e1, list.Front()) 165 | assert.Equal(t, e2, list.Back()) 166 | 167 | list.MoveAfter(e1, e2) 168 | assert.Equal(t, 2, list.Len()) 169 | assert.Equal(t, e2, list.Front()) 170 | assert.Equal(t, e1, list.Back()) 171 | }) 172 | 173 | t.Run("zero list", func(t *testing.T) { 174 | var ( 175 | list = list.New[int]() 176 | ) 177 | 178 | assert.Equal(t, 0, list.Len()) 179 | assert.Nil(t, list.Front()) 180 | assert.Nil(t, list.Back()) 181 | }) 182 | } 183 | -------------------------------------------------------------------------------- /queue/queue_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestQueue_Clear(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | items []int 13 | }{ 14 | { 15 | name: "empty_queue", 16 | items: []int{}, 17 | }, 18 | { 19 | name: "filled_queue", 20 | items: []int{1, 2, 3, 4, 5}, 21 | }, 22 | } 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | q := New(tt.items...) 26 | q.Clear() 27 | assert.Equal(t, 0, q.Len()) 28 | assert.Nil(t, q.head) 29 | assert.Nil(t, q.tail) 30 | }) 31 | } 32 | } 33 | 34 | func TestQueue_Contains(t *testing.T) { 35 | tests := []struct { 36 | name string 37 | items []int 38 | item int 39 | contains bool 40 | }{ 41 | { 42 | name: "doesn't contain", 43 | items: []int{1, 2, 3, 4}, 44 | item: 5, 45 | contains: false, 46 | }, 47 | { 48 | name: "contains", 49 | items: []int{1, 2, 3, 4}, 50 | item: 1, 51 | contains: true, 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | q := New(tt.items...) 57 | assert.Equal(t, tt.contains, q.Contains(tt.item)) 58 | }) 59 | } 60 | } 61 | 62 | func TestQueue_Len(t *testing.T) { 63 | tests := []struct { 64 | name string 65 | items []int 66 | expectedLen int 67 | }{ 68 | { 69 | name: "empty_queue", 70 | items: []int{}, 71 | expectedLen: 0, 72 | }, 73 | { 74 | name: "filled_queue_with_1_item", 75 | items: []int{1}, 76 | expectedLen: 1, 77 | }, 78 | { 79 | name: "filled_queue_with_2_items", 80 | items: []int{1, 2}, 81 | expectedLen: 2, 82 | }, 83 | } 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | q := New(tt.items...) 87 | assert.Equal(t, tt.expectedLen, q.Len()) 88 | }) 89 | } 90 | } 91 | 92 | func TestQueue_Dequeue(t *testing.T) { 93 | tests := []struct { 94 | name string 95 | items []int 96 | dequeued int 97 | ok bool 98 | expectedLen int 99 | }{ 100 | { 101 | name: "empty_queue", 102 | items: []int{}, 103 | dequeued: 0, 104 | ok: false, 105 | expectedLen: 0, 106 | }, 107 | { 108 | name: "filled_queue_with_1_item", 109 | items: []int{1}, 110 | dequeued: 1, 111 | ok: true, 112 | expectedLen: 0, 113 | }, 114 | { 115 | name: "filled_queue_with_2_item", 116 | items: []int{1, 2}, 117 | dequeued: 1, 118 | ok: true, 119 | expectedLen: 1, 120 | }, 121 | } 122 | for _, tt := range tests { 123 | t.Run(tt.name, func(t *testing.T) { 124 | q := New(tt.items...) 125 | item, ok := q.Dequeue() 126 | 127 | assert.Equal(t, tt.ok, ok) 128 | assert.Equal(t, tt.dequeued, item) 129 | assert.Equal(t, tt.expectedLen, q.Len()) 130 | }) 131 | } 132 | 133 | t.Run("dequeue_several_times", func(t *testing.T) { 134 | items := []int{1, 2, 3, 4, 5} 135 | q := New(items...) 136 | for _, item := range items { 137 | got, ok := q.Dequeue() 138 | assert.True(t, ok) 139 | assert.Equal(t, item, got) 140 | } 141 | _, ok := q.Dequeue() 142 | assert.False(t, ok) 143 | }) 144 | } 145 | 146 | func TestQueue_Enqueue(t *testing.T) { 147 | tests := []struct { 148 | name string 149 | items []int 150 | }{ 151 | { 152 | name: "one time", 153 | items: []int{1}, 154 | }, 155 | { 156 | name: "two times", 157 | items: []int{1, 2}, 158 | }, 159 | } 160 | for _, tt := range tests { 161 | t.Run(tt.name, func(t *testing.T) { 162 | q := New[int]() 163 | for i, item := range tt.items { 164 | q.Enqueue(item) 165 | assert.Equal(t, i+1, q.Len()) 166 | assert.Equal(t, item, q.tail.value) 167 | } 168 | assert.Equal(t, tt.items[0], q.head.value) 169 | }) 170 | } 171 | } 172 | 173 | func TestQueue_Peek(t *testing.T) { 174 | tests := []struct { 175 | name string 176 | items []int 177 | ok bool 178 | expected int 179 | }{ 180 | { 181 | name: "empty_queue", 182 | items: []int{}, 183 | ok: false, 184 | expected: 0, 185 | }, 186 | { 187 | name: "filled_queue", 188 | items: []int{1, 2, 3}, 189 | ok: true, 190 | expected: 1, 191 | }, 192 | } 193 | for _, tt := range tests { 194 | t.Run(tt.name, func(t *testing.T) { 195 | q := New(tt.items...) 196 | actual, ok := q.Peek() 197 | assert.Equal(t, tt.ok, ok) 198 | assert.Equal(t, tt.expected, actual) 199 | }) 200 | } 201 | } 202 | 203 | func TestQueue_Slice(t *testing.T) { 204 | tests := []struct { 205 | name string 206 | items []int 207 | }{ 208 | { 209 | name: "empty_queue", 210 | items: []int{}, 211 | }, 212 | { 213 | name: "filled_queue", 214 | items: []int{1, 2, 3}, 215 | }, 216 | } 217 | for _, tt := range tests { 218 | t.Run(tt.name, func(t *testing.T) { 219 | q := New(tt.items...) 220 | actual := q.Slice() 221 | assert.Equal(t, tt.items, actual) 222 | }) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /binary/tree_test.go: -------------------------------------------------------------------------------- 1 | package binary_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "go.tomakado.io/containers/binary" 8 | ) 9 | 10 | func TestAppend(t *testing.T) { 11 | t.Run("no children", func(t *testing.T) { 12 | var ( 13 | root = &binary.Node[int, int]{Key: 5, Value: 5} 14 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 15 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 16 | ) 17 | 18 | root.Append(node1) 19 | root.Append(node2) 20 | 21 | assert.Equal(t, root.Left.Key, 3, "Expected left node key to be 3") 22 | assert.Equal(t, root.Right.Key, 7, "Expected right node key to be 7") 23 | }) 24 | 25 | t.Run("left children", func(t *testing.T) { 26 | var ( 27 | root = &binary.Node[int, int]{Key: 5, Value: 5} 28 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 29 | node2 = &binary.Node[int, int]{Key: 1, Value: 1} 30 | ) 31 | 32 | root.Append(node1) 33 | root.Append(node2) 34 | 35 | assert.Equal(t, root.Left.Key, 3, "Expected left node key to be 3") 36 | assert.Nil(t, root.Right, "Expected right node to be nil") 37 | }) 38 | 39 | t.Run("right children", func(t *testing.T) { 40 | var ( 41 | root = &binary.Node[int, int]{Key: 5, Value: 5} 42 | 43 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 44 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 45 | node3 = &binary.Node[int, int]{Key: 9, Value: 9} 46 | ) 47 | 48 | root.Append(node1) 49 | root.Append(node2) 50 | root.Append(node3) 51 | 52 | assert.Equal(t, root.Left.Key, 3, "Expected left node key to be 3") 53 | assert.Equal(t, root.Right.Key, 7, "Expected left node key to be 7") 54 | assert.Equal(t, root.Right.Right.Key, 9, "Expected left node key to be 9") 55 | }) 56 | } 57 | 58 | func TestPreOrder(t *testing.T) { 59 | var ( 60 | root = &binary.Node[int, int]{Key: 5, Value: 5} 61 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 62 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 63 | ) 64 | 65 | root.Append(node1) 66 | root.Append(node2) 67 | 68 | var keys []int 69 | 70 | root.PreOrder(func(node *binary.Node[int, int]) { 71 | keys = append(keys, node.Key) 72 | }) 73 | 74 | assert.Equal(t, []int{5, 3, 7}, keys, "Expected pre-order traversal to be [5, 3, 7]") 75 | } 76 | 77 | func TestInOrder(t *testing.T) { 78 | var ( 79 | root = &binary.Node[int, int]{Key: 5, Value: 5} 80 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 81 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 82 | ) 83 | 84 | root.Append(node1) 85 | root.Append(node2) 86 | 87 | var keys []int 88 | 89 | root.InOrder(func(node *binary.Node[int, int]) { 90 | keys = append(keys, node.Key) 91 | }) 92 | 93 | assert.Equal(t, []int{3, 5, 7}, keys, "Expected pre-order traversal to be [3, 5, 7]") 94 | } 95 | 96 | func TestPostOrder(t *testing.T) { 97 | var ( 98 | root = &binary.Node[int, int]{Key: 5, Value: 5} 99 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 100 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 101 | ) 102 | 103 | root.Append(node1) 104 | root.Append(node2) 105 | 106 | var keys []int 107 | 108 | root.PostOrder(func(node *binary.Node[int, int]) { 109 | keys = append(keys, node.Key) 110 | }) 111 | 112 | assert.Equal(t, []int{3, 7, 5}, keys, "Expected pre-order traversal to be [3, 7, 5]") 113 | } 114 | 115 | func TestSearch(t *testing.T) { 116 | var ( 117 | root = &binary.Node[int, int]{Key: 5, Value: 5} 118 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 119 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 120 | ) 121 | 122 | root.Append(node1) 123 | root.Append(node2) 124 | 125 | node, found := root.Search(7) 126 | assert.True(t, found, "Expected to find node with key 7") 127 | assert.Equal(t, 7, node.Key, "Expected key to be 7") 128 | assert.Equal(t, 7, node.Value, "Expected value to be 7") 129 | 130 | node, found = root.Search(10) 131 | assert.False(t, found, "Expected not to find node with key 10") 132 | assert.Nil(t, node, "Expected node to be nil") 133 | } 134 | 135 | func TestRemove(t *testing.T) { 136 | t.Run("no children", func(t *testing.T) { 137 | var ( 138 | root = &binary.Node[int, int]{Key: 5, Value: 5} 139 | node1 = &binary.Node[int, int]{Key: 3, Value: 3} 140 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 141 | ) 142 | 143 | root.Append(node1) 144 | root.Append(node2) 145 | 146 | root.Remove(3) 147 | node, found := root.Search(3) 148 | assert.False(t, found, "Expected not to find node with key 3") 149 | assert.Nil(t, node, "Expected node to be nil") 150 | 151 | root.Remove(7) 152 | node, found = root.Search(7) 153 | assert.False(t, found, "Expected not to find node with key 7") 154 | assert.Nil(t, node, "Expected node to be nil") 155 | }) 156 | 157 | t.Run("left is nil", func(t *testing.T) { 158 | var ( 159 | root = &binary.Node[int, int]{Key: 5, Value: 5} 160 | node1 = &binary.Node[int, int]{Key: 6, Value: 6} 161 | node2 = &binary.Node[int, int]{Key: 7, Value: 7} 162 | ) 163 | 164 | root.Append(node1) 165 | root.Append(node2) 166 | 167 | root.Remove(6) 168 | node, found := root.Search(6) 169 | assert.False(t, found, "Expected not to find node with key 6") 170 | assert.Nil(t, node, "Expected node to be nil") 171 | 172 | root.Remove(7) 173 | node, found = root.Search(7) 174 | assert.False(t, found, "Expected not to find node with key 7") 175 | assert.Nil(t, node, "Expected node to be nil") 176 | }) 177 | 178 | t.Run("right is nil", func(t *testing.T) { 179 | var ( 180 | root = &binary.Node[int, int]{Key: 5, Value: 5} 181 | node1 = &binary.Node[int, int]{Key: 4, Value: 4} 182 | node2 = &binary.Node[int, int]{Key: 3, Value: 3} 183 | ) 184 | 185 | root.Append(node1) 186 | root.Append(node2) 187 | 188 | root.Remove(4) 189 | node, found := root.Search(4) 190 | assert.False(t, found, "Expected not to find node with key 4") 191 | assert.Nil(t, node, "Expected node to be nil") 192 | }) 193 | 194 | t.Run("both children are not nil", func(t *testing.T) { 195 | var ( 196 | root = &binary.Node[int, int]{Key: 5, Value: 5} 197 | node1 = &binary.Node[int, int]{Key: 4, Value: 4} 198 | node2 = &binary.Node[int, int]{Key: 6, Value: 6} 199 | ) 200 | 201 | root.Append(node1) 202 | root.Append(node2) 203 | 204 | root.Remove(5) 205 | node, found := root.Search(5) 206 | assert.False(t, found, "Expected not to find node with key 5") 207 | assert.Nil(t, node, "Expected node to be nil") 208 | }) 209 | } 210 | -------------------------------------------------------------------------------- /list/list.go: -------------------------------------------------------------------------------- 1 | // package list contains double linked list implmenetation. 2 | // It's mostly based on Go's standard library list implementation but with generics added. 3 | package list 4 | 5 | type Element[T any] struct { 6 | next, prev *Element[T] 7 | list *List[T] 8 | Value T 9 | } 10 | 11 | func (e *Element[T]) Next() *Element[T] { 12 | if p := e.next; e.list != nil && p != &e.list.root { 13 | return p 14 | } 15 | 16 | return nil 17 | } 18 | 19 | func (e *Element[T]) Prev() *Element[T] { 20 | if p := e.prev; e.list != nil && p != &e.list.root { 21 | return p 22 | } 23 | 24 | return nil 25 | } 26 | 27 | // List implements a doubly linked list. 28 | // 29 | // To iterate over a list (where l is a *List): 30 | // 31 | // for e := l.Front(); e != nil; e = e.Next() { 32 | // // do something with e.Value 33 | // } 34 | type List[T any] struct { 35 | root Element[T] 36 | len int 37 | } 38 | 39 | // New returns an initialized list. 40 | func New[T any]() *List[T] { 41 | return new(List[T]).Init() 42 | } 43 | 44 | // Init initializes or clears list l. 45 | func (l *List[T]) Init() *List[T] { 46 | l.root.next = &l.root 47 | l.root.prev = &l.root 48 | 49 | return l 50 | } 51 | 52 | // Len returns the number of elements of list l. 53 | // The complexity is O(1). 54 | func (l *List[T]) Len() int { 55 | return l.len 56 | } 57 | 58 | // Front returns the first element of list l or nil if the list is empty. 59 | func (l *List[T]) Front() *Element[T] { 60 | if l.len == 0 { 61 | return nil 62 | } 63 | 64 | return l.root.next 65 | } 66 | 67 | // Back returns the last element of list l or nil if the list is empty. 68 | func (l *List[T]) Back() *Element[T] { 69 | if l.len == 0 { 70 | return nil 71 | } 72 | 73 | return l.root.prev 74 | } 75 | 76 | // lazyInit lazily initializes a zero List value. 77 | func (l *List[T]) lazyInit() { 78 | if l.root.next == nil { 79 | l.Init() 80 | } 81 | } 82 | 83 | // insert inserts e after at, increments l.len, and returns e. 84 | func (l *List[T]) insert(e, at *Element[T]) *Element[T] { 85 | e.prev = at 86 | e.next = at.next 87 | e.prev.next = e 88 | e.next.prev = e 89 | e.list = l 90 | l.len++ 91 | 92 | return e 93 | } 94 | 95 | // insertValue is a convenience wrapper for insert(&Element{Value: v}, at). 96 | func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] { 97 | return l.insert(&Element[T]{Value: v}, at) 98 | } 99 | 100 | // remove removes e from its list, decrements l.len. 101 | func (l *List[T]) remove(e *Element[T]) { 102 | e.prev.next = e.next 103 | e.next.prev = e.prev 104 | e.next = nil // avoid memory leaks 105 | e.prev = nil // avoid memory leaks 106 | e.list = nil 107 | l.len-- 108 | } 109 | 110 | // move moves e to next to at. 111 | func (l *List[T]) move(e, at *Element[T]) { 112 | if e == at { 113 | return 114 | } 115 | 116 | e.prev.next = e.next 117 | e.next.prev = e.prev 118 | 119 | e.prev = at 120 | e.next = at.next 121 | e.prev.next = e 122 | e.next.prev = e 123 | } 124 | 125 | // Remove removes e from l if e is an element of list l. 126 | // It returns the element value e.Value. 127 | // The element must not be nil. 128 | func (l *List[T]) Remove(e *Element[T]) any { 129 | if e.list == l { 130 | // if e.list == l, l must have been initialized when e was inserted 131 | // in l or l == nil (e is a zero Element) and l.remove will crash 132 | l.remove(e) 133 | } 134 | 135 | return e.Value 136 | } 137 | 138 | // PushFront inserts a new element e with value v at the front of list l and returns e. 139 | func (l *List[T]) PushFront(v T) *Element[T] { 140 | l.lazyInit() 141 | return l.insertValue(v, &l.root) 142 | } 143 | 144 | // PushBack inserts a new element e with value v at the back of list l and returns e. 145 | func (l *List[T]) PushBack(v T) *Element[T] { 146 | l.lazyInit() 147 | return l.insertValue(v, l.root.prev) 148 | } 149 | 150 | // InsertBefore inserts a new element e with value v immediately before mark and returns e. 151 | // If mark is not an element of l, the list is not modified. 152 | // The mark must not be nil. 153 | func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] { 154 | if mark.list != l { 155 | return nil 156 | } 157 | // see comment in List.Remove about initialization of l 158 | return l.insertValue(v, mark.prev) 159 | } 160 | 161 | // InsertAfter inserts a new element e with value v immediately after mark and returns e. 162 | // If mark is not an element of l, the list is not modified. 163 | // The mark must not be nil. 164 | func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] { 165 | if mark.list != l { 166 | return nil 167 | } 168 | // see comment in List.Remove about initialization of l 169 | return l.insertValue(v, mark) 170 | } 171 | 172 | // MoveToFront moves element e to the front of list l. 173 | // If e is not an element of l, the list is not modified. 174 | // The element must not be nil. 175 | func (l *List[T]) MoveToFront(e *Element[T]) { 176 | if e.list != l || l.root.next == e { 177 | return 178 | } 179 | // see comment in List.Remove about initialization of l 180 | l.move(e, &l.root) 181 | } 182 | 183 | // MoveToBack moves element e to the back of list l. 184 | // If e is not an element of l, the list is not modified. 185 | // The element must not be nil. 186 | func (l *List[T]) MoveToBack(e *Element[T]) { 187 | if e.list != l || l.root.prev == e { 188 | return 189 | } 190 | // see comment in List.Remove about initialization of l 191 | 192 | l.move(e, l.root.prev) 193 | } 194 | 195 | // MoveBefore moves element e to its new position before mark. 196 | // If e or mark is not an element of l, or e == mark, the list is not modified. 197 | // The element and mark must not be nil. 198 | func (l *List[T]) MoveBefore(e, mark *Element[T]) { 199 | if e.list != l || e == mark || mark.list != l { 200 | return 201 | } 202 | 203 | l.move(e, mark.prev) 204 | } 205 | 206 | // MoveAfter moves element e to its new position after mark. 207 | // If e or mark is not an element of l, or e == mark, the list is not modified. 208 | // The element and mark must not be nil. 209 | func (l *List[T]) MoveAfter(e, mark *Element[T]) { 210 | if e.list != l || e == mark || mark.list != l { 211 | return 212 | } 213 | 214 | l.move(e, mark) 215 | } 216 | 217 | // PushBackList inserts a copy of another list at the back of list l. 218 | // The lists l and other may be the same. They must not be nil. 219 | func (l *List[T]) PushBackList(other *List[T]) { 220 | l.lazyInit() 221 | 222 | for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { 223 | l.insertValue(e.Value, l.root.prev) 224 | } 225 | } 226 | 227 | // PushFrontList inserts a copy of another list at the front of list l. 228 | // The lists l and other may be the same. They must not be nil. 229 | func (l *List[T]) PushFrontList(other *List[T]) { 230 | l.lazyInit() 231 | 232 | for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { 233 | l.insertValue(e.Value, &l.root) 234 | } 235 | } 236 | --------------------------------------------------------------------------------