├── go.sum ├── go.mod ├── .github └── workflows │ └── pull-request.yml ├── .gitignore ├── collection ├── ordered_collection.go ├── collection.go ├── collection_test.go ├── ordered_collection_test.go ├── iter_functions.go ├── functions.go ├── iter_functions_test.go ├── ordered_functions.go ├── functions_test.go └── ordered_functions_test.go ├── LICENSE ├── list ├── comparable_list.go ├── comparable_list_test.go ├── list.go └── list_test.go ├── sequence ├── comparable_sequence_test.go ├── comparable_sequence.go ├── sequence.go └── sequence_test.go ├── set ├── set.go └── set_test.go └── README.md /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charbz/gophers 2 | 3 | go 1.23.2 4 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Checks 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | test-and-lint: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Set up Go 14 | uses: actions/setup-go@v4 15 | with: 16 | go-version: '1.21' 17 | 18 | - name: Run tests 19 | run: go test ./... 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | main.go 28 | -------------------------------------------------------------------------------- /collection/ordered_collection.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package collection 6 | 7 | import "iter" 8 | 9 | // OrderedCollection is a generic interface for collections who's underlying 10 | // data structure is index-based, and the order of elements matters. 11 | type OrderedCollection[T any] interface { 12 | Collection[T] 13 | At(index int) T 14 | All() iter.Seq2[int, T] 15 | Backward() iter.Seq2[int, T] 16 | Slice(start, end int) OrderedCollection[T] 17 | NewOrdered(s ...[]T) OrderedCollection[T] 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Gophers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /collection/collection.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package collections implements support for generic Collections of data. 6 | // A collection wraps an underlying Go slice and provides convenience methods 7 | // and syntatic sugar on top of it. 8 | package collection 9 | 10 | import ( 11 | "fmt" 12 | "iter" 13 | ) 14 | 15 | // Collection is a generic interface that must be implemented by all collection sub-types. 16 | // At a minimum, collections must support the methods defined below. 17 | type Collection[T any] interface { 18 | Add(T) 19 | Length() int 20 | New(s ...[]T) Collection[T] 21 | Random() T 22 | Values() iter.Seq[T] 23 | } 24 | 25 | type CollectionError struct { 26 | code int 27 | msg string 28 | } 29 | 30 | func (e *CollectionError) Error() string { 31 | return fmt.Sprintf("error %d: %s", e.code, e.msg) 32 | } 33 | 34 | var ( 35 | EmptyCollectionError = &CollectionError{ 36 | code: 100, msg: "invalid operation on an empty collection", 37 | } 38 | ValueNotFoundError = &CollectionError{ 39 | code: 101, msg: "value not found", 40 | } 41 | IndexOutOfBoundsError = &CollectionError{ 42 | code: 102, msg: "index out of bounds", 43 | } 44 | ) 45 | -------------------------------------------------------------------------------- /collection/collection_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "iter" 5 | "math/rand" 6 | "slices" 7 | "testing" 8 | ) 9 | 10 | // MockCollection implements the Collection interface for testing purposes 11 | type MockCollection[T any] struct { 12 | items []T 13 | } 14 | 15 | func NewMockCollection[T any](items ...[]T) *MockCollection[T] { 16 | return &MockCollection[T]{items: slices.Concat(items...)} 17 | } 18 | 19 | // Implementing the Collection interface. 20 | 21 | func (m *MockCollection[T]) Add(item T) { 22 | m.items = append(m.items, item) 23 | } 24 | 25 | func (m *MockCollection[T]) Values() iter.Seq[T] { 26 | return slices.Values(m.items) 27 | } 28 | 29 | func (m *MockCollection[T]) Length() int { 30 | return len(m.items) 31 | } 32 | 33 | func (m *MockCollection[T]) ToSlice() []T { 34 | return m.items 35 | } 36 | 37 | func (c *MockCollection[T]) Random() T { 38 | if len(c.items) == 0 { 39 | return *new(T) 40 | } 41 | return c.items[rand.Intn(len(c.items))] 42 | } 43 | 44 | func (m *MockCollection[T]) Slice(start, end int) Collection[T] { 45 | return NewMockCollection(m.items[start:end]) 46 | } 47 | 48 | func (m *MockCollection[T]) New(s ...[]T) Collection[T] { 49 | mock := &MockCollection[T]{} 50 | if len(s) > 0 { 51 | mock.items = s[0] 52 | } 53 | return mock 54 | } 55 | 56 | func TestMockCollectionImplementsCollection(t *testing.T) { 57 | var m Collection[string] = NewMockCollection([]string{"a", "b", "c"}) 58 | if m.Length() != 3 { 59 | t.Errorf("expected length 3, got %d", m.Length()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /collection/ordered_collection_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "iter" 5 | "math/rand" 6 | "slices" 7 | "testing" 8 | ) 9 | 10 | // MockOrderedCollection implements the OrderedCollection interface for testing purposes 11 | type MockOrderedCollection[T any] struct { 12 | items []T 13 | } 14 | 15 | func NewMockOrderedCollection[T any](items ...[]T) *MockOrderedCollection[T] { 16 | return &MockOrderedCollection[T]{items: slices.Concat(items...)} 17 | } 18 | 19 | // Implementing the Collection & OrderedCollection interfaces. 20 | 21 | func (m *MockOrderedCollection[T]) At(index int) T { 22 | return m.items[index] 23 | } 24 | 25 | func (m *MockOrderedCollection[T]) All() iter.Seq2[int, T] { 26 | return slices.All(m.items) 27 | } 28 | 29 | func (m *MockOrderedCollection[T]) Add(item T) { 30 | m.items = append(m.items, item) 31 | } 32 | 33 | func (m *MockOrderedCollection[T]) Backward() iter.Seq2[int, T] { 34 | return slices.Backward(m.items) 35 | } 36 | 37 | func (m *MockOrderedCollection[T]) Values() iter.Seq[T] { 38 | return slices.Values(m.items) 39 | } 40 | 41 | func (m *MockOrderedCollection[T]) Length() int { 42 | return len(m.items) 43 | } 44 | 45 | func (m *MockOrderedCollection[T]) ToSlice() []T { 46 | return m.items 47 | } 48 | 49 | func (c *MockOrderedCollection[T]) Random() T { 50 | if len(c.items) == 0 { 51 | return *new(T) 52 | } 53 | return c.items[rand.Intn(len(c.items))] 54 | } 55 | 56 | func (m *MockOrderedCollection[T]) Slice(start, end int) OrderedCollection[T] { 57 | return NewMockOrderedCollection(m.items[start:end]) 58 | } 59 | 60 | func (m *MockOrderedCollection[T]) NewOrdered(s ...[]T) OrderedCollection[T] { 61 | return NewMockOrderedCollection(s...) 62 | } 63 | 64 | func (m *MockOrderedCollection[T]) New(s ...[]T) Collection[T] { 65 | return NewMockOrderedCollection(s...) 66 | } 67 | 68 | func TestMockOrderedCollectionImplementsOrderedCollection(t *testing.T) { 69 | var m OrderedCollection[string] = NewMockOrderedCollection([]string{"a", "b", "c"}) 70 | if m.Length() != 3 { 71 | t.Errorf("Length() = %v, want %v", m.Length(), 3) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /list/comparable_list.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package list 6 | 7 | import ( 8 | "cmp" 9 | "iter" 10 | 11 | "github.com/charbz/gophers/collection" 12 | ) 13 | 14 | // ComparableList is a list of comparable types. 15 | // it is similar to List, but with additional methods that do not require a 16 | // higher order function comparator to be provided as an argument: 17 | // Max(), Min(), Sum(), Distinct(), Diff(c), and Exists(v). 18 | type ComparableList[T cmp.Ordered] struct { 19 | List[T] 20 | } 21 | 22 | func (l *ComparableList[T]) New(s ...[]T) collection.Collection[T] { 23 | return NewComparableList(s...) 24 | } 25 | 26 | func (l *ComparableList[T]) NewOrdered(s ...[]T) collection.OrderedCollection[T] { 27 | return NewComparableList(s...) 28 | } 29 | 30 | func NewComparableList[T cmp.Ordered](s ...[]T) *ComparableList[T] { 31 | list := new(ComparableList[T]) 32 | if len(s) == 0 { 33 | return list 34 | } 35 | for _, slice := range s { 36 | for _, v := range slice { 37 | list.Add(v) 38 | } 39 | } 40 | return list 41 | } 42 | 43 | // Clone returns a copy of the list. This is a shallow clone. 44 | func (l *ComparableList[T]) Clone() *ComparableList[T] { 45 | clone := &ComparableList[T]{} 46 | for v := range l.Values() { 47 | clone.Add(v) 48 | } 49 | return clone 50 | } 51 | 52 | // Concat returns a new list concatenating the passed in lists. 53 | func (l *ComparableList[T]) Concat(lists ...*ComparableList[T]) *ComparableList[T] { 54 | clone := l.Clone() 55 | for _, list := range lists { 56 | for v := range list.Values() { 57 | clone.Add(v) 58 | } 59 | } 60 | return clone 61 | } 62 | 63 | // Concatenated is an alias for collection.Concatenated 64 | func (l *ComparableList[T]) Concatenated(l2 *ComparableList[T]) iter.Seq[T] { 65 | return collection.Concatenated(l, l2) 66 | } 67 | 68 | // Contains returns true if the list contains the given value. 69 | func (l *ComparableList[T]) Contains(v T) bool { 70 | for val := range l.Values() { 71 | if val == v { 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | // Corresponds is an alias for collection.Corresponds 79 | func (l *ComparableList[T]) Corresponds(s *ComparableList[T], f func(T, T) bool) bool { 80 | return collection.Corresponds(l, s, f) 81 | } 82 | 83 | // Distinct returns a new list containing only the unique elements from the original list. 84 | func (l *ComparableList[T]) Distinct() *ComparableList[T] { 85 | m := make(map[T]struct{}) 86 | r := &ComparableList[T]{} 87 | for v := range l.Values() { 88 | _, ok := m[v] 89 | if !ok { 90 | r.Add(v) 91 | m[v] = struct{}{} 92 | } 93 | } 94 | return r 95 | } 96 | 97 | // Distincted is an alias for collection.Distincted 98 | func (l *ComparableList[T]) Distincted() iter.Seq[T] { 99 | return collection.Distincted(l) 100 | } 101 | 102 | // Diff returns a new list containing the elements of the original list that are not in the other list. 103 | func (l *ComparableList[T]) Diff(s *ComparableList[T]) *ComparableList[T] { 104 | return collection.Diff(l, s).(*ComparableList[T]) 105 | } 106 | 107 | // Diffed is an alias for collection.Diffed 108 | func (l *ComparableList[T]) Diffed(s *ComparableList[T]) iter.Seq[T] { 109 | return collection.Diffed(l, s) 110 | } 111 | 112 | // Exists is an alias for Contains 113 | func (l *ComparableList[T]) Exists(v T) bool { 114 | return l.Contains(v) 115 | } 116 | 117 | // Equals returns true if the two lists are equal. 118 | func (l *ComparableList[T]) Equals(s *ComparableList[T]) bool { 119 | if l.size != s.size { 120 | return false 121 | } 122 | n1 := l.head 123 | n2 := s.head 124 | for n1 != nil && n2 != nil { 125 | if n1.value != n2.value { 126 | return false 127 | } 128 | n1 = n1.next 129 | n2 = n2.next 130 | } 131 | return true 132 | } 133 | 134 | // IndexOf returns the index of the first occurrence of the specified element in this list, 135 | func (l *ComparableList[T]) IndexOf(v T) int { 136 | for i, val := range l.All() { 137 | if val == v { 138 | return i 139 | } 140 | } 141 | return -1 142 | } 143 | 144 | // Intersect returns a new list containing the elements that are present in both lists. 145 | func (l *ComparableList[T]) Intersect(s *ComparableList[T]) *ComparableList[T] { 146 | return collection.Intersect(l, s).(*ComparableList[T]) 147 | } 148 | 149 | // Intersected is an alias for collection.Intersected 150 | func (l *ComparableList[T]) Intersected(s *ComparableList[T]) iter.Seq[T] { 151 | return collection.Intersected(l, s) 152 | } 153 | 154 | // LastIndexOf returns the index of the last occurrence of the specified element in this list, 155 | func (l *ComparableList[T]) LastIndexOf(v T) int { 156 | for i, val := range l.Backward() { 157 | if val == v { 158 | return i 159 | } 160 | } 161 | return -1 162 | } 163 | 164 | // Max returns the maximum element in the list. 165 | func (l *ComparableList[T]) Max() (T, error) { 166 | return collection.MaxBy(l, func(v T) T { return v }) 167 | } 168 | 169 | // Min returns the minimum element in the list. 170 | func (l *ComparableList[T]) Min() (T, error) { 171 | return collection.MinBy(l, func(v T) T { return v }) 172 | } 173 | 174 | // Sum returns the sum of the elements in the list. 175 | func (l *ComparableList[T]) Sum() T { 176 | var sum T 177 | for v := range l.Values() { 178 | sum += v 179 | } 180 | return sum 181 | } 182 | 183 | // StartsWith returns true if the list starts with the given list. 184 | func (l *ComparableList[T]) StartsWith(other *ComparableList[T]) bool { 185 | return collection.StartsWith(l, other) 186 | } 187 | 188 | // EndsWith returns true if the list ends with the given list. 189 | func (l *ComparableList[T]) EndsWith(other *ComparableList[T]) bool { 190 | return collection.EndsWith(l, other) 191 | } 192 | -------------------------------------------------------------------------------- /sequence/comparable_sequence_test.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import ( 4 | "slices" 5 | "testing" 6 | ) 7 | 8 | func TestContains(t *testing.T) { 9 | c := NewComparableSequence([]int{1, 2, 3, 4, 5, 6}) 10 | if !c.Contains(3) { 11 | t.Errorf("Contains() = %v, want %v", c.Contains(3), true) 12 | } 13 | if c.Contains(7) { 14 | t.Errorf("Contains() = %v, want %v", c.Contains(7), false) 15 | } 16 | } 17 | 18 | func TestExists(t *testing.T) { 19 | c := NewComparableSequence([]int{1, 2, 3, 4, 5, 6}) 20 | if !c.Exists(3) { 21 | t.Errorf("Exists() = %v, want %v", c.Exists(3), true) 22 | } 23 | if c.Exists(7) { 24 | t.Errorf("Exists() = %v, want %v", c.Exists(7), false) 25 | } 26 | } 27 | 28 | func TestEquals(t *testing.T) { 29 | c1 := NewComparableSequence([]int{1, 2, 3}) 30 | c2 := NewComparableSequence([]int{1, 2, 3}) 31 | c3 := NewComparableSequence([]int{1, 2, 4}) 32 | 33 | if !c1.Equals(c2) { 34 | t.Errorf("Equals() = %v, want %v", c1.Equals(c2), true) 35 | } 36 | if c1.Equals(c3) { 37 | t.Errorf("Equals() = %v, want %v", c1.Equals(c3), false) 38 | } 39 | } 40 | 41 | func TestDiff(t *testing.T) { 42 | tests := []struct { 43 | name string 44 | s1 []int 45 | s2 []int 46 | want []int 47 | }{ 48 | { 49 | name: "different sequences", 50 | s1: []int{1, 2, 3, 4}, 51 | s2: []int{3, 4, 5, 6}, 52 | want: []int{1, 2}, 53 | }, 54 | { 55 | name: "identical sequences", 56 | s1: []int{1, 2, 3}, 57 | s2: []int{1, 2, 3}, 58 | want: nil, 59 | }, 60 | } 61 | 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | c1 := NewComparableSequence(tt.s1) 65 | c2 := NewComparableSequence(tt.s2) 66 | result := c1.Diff(c2) 67 | if !slices.Equal(result.elements, tt.want) { 68 | t.Errorf("Diff() = %v, want %v", result.elements, tt.want) 69 | } 70 | }) 71 | } 72 | } 73 | 74 | func TestIndexOf(t *testing.T) { 75 | c := NewComparableSequence([]int{1, 2, 3, 4, 5}) 76 | if got := c.IndexOf(3); got != 2 { 77 | t.Errorf("IndexOf() = %v, want %v", got, 2) 78 | } 79 | if got := c.IndexOf(6); got != -1 { 80 | t.Errorf("IndexOf() = %v, want %v", got, -1) 81 | } 82 | } 83 | 84 | func TestMax(t *testing.T) { 85 | c := NewComparableSequence([]int{1, 5, 3, 9, 2}) 86 | if got := c.Max(); got != 9 { 87 | t.Errorf("Max() = %v, want %v", got, 9) 88 | } 89 | } 90 | 91 | func TestMin(t *testing.T) { 92 | c := NewComparableSequence([]int{4, 2, 7, 1, 9}) 93 | if got := c.Min(); got != 1 { 94 | t.Errorf("Min() = %v, want %v", got, 1) 95 | } 96 | } 97 | 98 | func TestSum(t *testing.T) { 99 | tests := []struct { 100 | name string 101 | input []int 102 | want int 103 | }{ 104 | { 105 | name: "positive numbers", 106 | input: []int{1, 2, 3, 4, 5}, 107 | want: 15, 108 | }, 109 | { 110 | name: "mixed numbers", 111 | input: []int{-1, 2, -3, 4}, 112 | want: 2, 113 | }, 114 | { 115 | name: "empty sequence", 116 | input: []int{}, 117 | want: 0, 118 | }, 119 | } 120 | 121 | for _, tt := range tests { 122 | t.Run(tt.name, func(t *testing.T) { 123 | c := NewComparableSequence(tt.input) 124 | if got := c.Sum(); got != tt.want { 125 | t.Errorf("Sum() = %v, want %v", got, tt.want) 126 | } 127 | }) 128 | } 129 | } 130 | 131 | func TestStartsWith(t *testing.T) { 132 | tests := []struct { 133 | name string 134 | s1 []int 135 | s2 []int 136 | startsWith bool 137 | }{ 138 | { 139 | name: "starts with matching elements", 140 | s1: []int{1, 2, 3, 4}, 141 | s2: []int{1, 2}, 142 | startsWith: true, 143 | }, 144 | { 145 | name: "does not start with different elements", 146 | s1: []int{1, 2, 3, 4}, 147 | s2: []int{2, 3}, 148 | startsWith: false, 149 | }, 150 | { 151 | name: "empty s2 (always true)", 152 | s1: []int{1, 2, 3, 4}, 153 | s2: []int{}, 154 | startsWith: true, 155 | }, 156 | { 157 | name: "s1 shorter than s2", 158 | s1: []int{1, 2}, 159 | s2: []int{1, 2, 3}, 160 | startsWith: false, 161 | }, 162 | { 163 | name: "both sequences empty", 164 | s1: []int{}, 165 | s2: []int{}, 166 | startsWith: true, 167 | }, 168 | } 169 | 170 | for _, tt := range tests { 171 | t.Run(tt.name, func(t *testing.T) { 172 | c1 := NewComparableSequence(tt.s1) 173 | c2 := NewComparableSequence(tt.s2) 174 | if got := c1.StartsWith(c2); got != tt.startsWith { 175 | t.Errorf("StartsWith() = %v, want %v", got, tt.startsWith) 176 | } 177 | }) 178 | } 179 | } 180 | 181 | func TestEndsWith(t *testing.T) { 182 | tests := []struct { 183 | name string 184 | s1 []int 185 | s2 []int 186 | endsWith bool 187 | }{ 188 | { 189 | name: "ends with matching elements", 190 | s1: []int{1, 2, 3, 4}, 191 | s2: []int{3, 4}, 192 | endsWith: true, 193 | }, 194 | { 195 | name: "does not end with different elements", 196 | s1: []int{1, 2, 3, 4}, 197 | s2: []int{2, 3}, 198 | endsWith: false, 199 | }, 200 | { 201 | name: "empty s2 (always true)", 202 | s1: []int{1, 2, 3, 4}, 203 | s2: []int{}, 204 | endsWith: true, 205 | }, 206 | { 207 | name: "s1 shorter than s2", 208 | s1: []int{1, 2}, 209 | s2: []int{1, 2, 3}, 210 | endsWith: false, 211 | }, 212 | { 213 | name: "both sequences empty", 214 | s1: []int{}, 215 | s2: []int{}, 216 | endsWith: true, 217 | }, 218 | } 219 | 220 | for _, tt := range tests { 221 | t.Run(tt.name, func(t *testing.T) { 222 | c1 := NewComparableSequence(tt.s1) 223 | c2 := NewComparableSequence(tt.s2) 224 | if got := c1.EndsWith(c2); got != tt.endsWith { 225 | t.Errorf("EndsWith() = %v, want %v", got, tt.endsWith) 226 | } 227 | }) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /sequence/comparable_sequence.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package sequence 6 | 7 | import ( 8 | "cmp" 9 | "iter" 10 | "slices" 11 | 12 | "github.com/charbz/gophers/collection" 13 | ) 14 | 15 | // ComparableSequence is a sequence of comparable types. 16 | // it is similar to Sequence, but with additional methods that do not require a 17 | // higher order function comparator to be provided as an argument: 18 | // Max(), Min(), Sum(), Distinct(), Diff(c), and Exists(v). 19 | type ComparableSequence[T cmp.Ordered] struct { 20 | Sequence[T] 21 | } 22 | 23 | func (c *ComparableSequence[T]) New(s ...[]T) collection.Collection[T] { 24 | return NewComparableSequence(s...) 25 | } 26 | 27 | func (c *ComparableSequence[T]) NewOrdered(s ...[]T) collection.OrderedCollection[T] { 28 | return NewComparableSequence(s...) 29 | } 30 | 31 | // NewComparableSequence is a constructor for a sequence of comparable types. 32 | func NewComparableSequence[T cmp.Ordered](s ...[]T) *ComparableSequence[T] { 33 | seq := new(ComparableSequence[T]) 34 | if len(s) == 0 { 35 | return seq 36 | } 37 | return &ComparableSequence[T]{Sequence[T]{elements: slices.Concat(s...)}} 38 | } 39 | 40 | // The following methods are mostly syntatic sugar 41 | // wrapping Collection functions to enable function chaining: 42 | // i.e. sequence.Filter(f).Take(n) 43 | 44 | // Clone returns a copy of the collection. This is a shallow clone. 45 | func (c *ComparableSequence[T]) Clone() *ComparableSequence[T] { 46 | return &ComparableSequence[T]{ 47 | Sequence[T]{elements: slices.Clone(c.elements)}, 48 | } 49 | } 50 | 51 | // Contains returns true if the sequence contains the given value. 52 | func (c *ComparableSequence[T]) Contains(v T) bool { 53 | return slices.Contains(c.elements, v) 54 | } 55 | 56 | // Concat returns a new sequence concatenating the passed in sequences. 57 | func (c *ComparableSequence[T]) Concat(sequences ...*ComparableSequence[T]) *ComparableSequence[T] { 58 | e := c.elements 59 | for _, col := range sequences { 60 | e = slices.Concat(e, col.elements) 61 | } 62 | return &ComparableSequence[T]{Sequence[T]{elements: e}} 63 | } 64 | 65 | // Concatenated is an alias for collection.Concatenated 66 | func (c *ComparableSequence[T]) Concatenated(s *ComparableSequence[T]) iter.Seq[T] { 67 | return collection.Concatenated(c, s) 68 | } 69 | 70 | // Corresponds is an alias for collection.Corresponds 71 | func (c *ComparableSequence[T]) Corresponds(s *ComparableSequence[T], f func(T, T) bool) bool { 72 | return collection.Corresponds(c, s, f) 73 | } 74 | 75 | // Distinct returns a new sequence containing only the unique elements from the original sequence. 76 | func (c *ComparableSequence[T]) Distinct() *ComparableSequence[T] { 77 | m := make(map[T]interface{}) 78 | r := &ComparableSequence[T]{} 79 | for v := range c.Values() { 80 | _, ok := m[v] 81 | if !ok { 82 | r.Add(v) 83 | m[v] = true 84 | } 85 | } 86 | return r 87 | } 88 | 89 | // Distincted is an alias for collection.Distincted 90 | func (c *ComparableSequence[T]) Distincted() iter.Seq[T] { 91 | return collection.Distincted(c) 92 | } 93 | 94 | // Diff is an alias for collection.Diff 95 | func (c *ComparableSequence[T]) Diff(s *ComparableSequence[T]) *ComparableSequence[T] { 96 | return collection.Diff(c, s).(*ComparableSequence[T]) 97 | } 98 | 99 | // Diffed is an alias for collection.Diffed 100 | func (c *ComparableSequence[T]) Diffed(s *ComparableSequence[T]) iter.Seq[T] { 101 | return collection.Diffed(c, s) 102 | } 103 | 104 | // Equals returns true if the two sequences are equal. 105 | func (c *ComparableSequence[T]) Equals(c2 *ComparableSequence[T]) bool { 106 | return slices.Equal(c.elements, c2.elements) 107 | } 108 | 109 | // Exists returns true if the sequence contains the given value. 110 | func (c *ComparableSequence[T]) Exists(v T) bool { 111 | return c.Contains(v) 112 | } 113 | 114 | // IndexOf returns the index of the first occurrence of the specified element in this sequence, 115 | // or -1 if this sequence does not contain the element. 116 | func (c *ComparableSequence[T]) IndexOf(v T) int { 117 | return slices.Index(c.elements, v) 118 | } 119 | 120 | // Intersect returns a new sequence containing the elements that are present in both sequences. 121 | func (c *ComparableSequence[T]) Intersect(s *ComparableSequence[T]) *ComparableSequence[T] { 122 | return collection.Intersect(c, s).(*ComparableSequence[T]) 123 | } 124 | 125 | // IntersectIterator is an alias for collection.IntersectIterator 126 | func (c *ComparableSequence[T]) Intersected(s *ComparableSequence[T]) iter.Seq[T] { 127 | return collection.Intersected(c, s) 128 | } 129 | 130 | // LastIndexOf returns the index of the last occurrence of the specified element in this sequence, 131 | // or -1 if this sequence does not contain the element. 132 | func (c *ComparableSequence[T]) LastIndexOf(v T) int { 133 | for i, val := range c.Backward() { 134 | if val == v { 135 | return i 136 | } 137 | } 138 | return -1 139 | } 140 | 141 | // Max returns the maximum value in the sequence. 142 | func (c *ComparableSequence[T]) Max() T { 143 | return slices.Max(c.elements) 144 | } 145 | 146 | // Min returns the minimum value in the sequence. 147 | func (c *ComparableSequence[T]) Min() T { 148 | return slices.Min(c.elements) 149 | } 150 | 151 | // Sum returns the sum of the elements in the sequence. 152 | func (c *ComparableSequence[T]) Sum() T { 153 | var sum T 154 | for _, v := range c.elements { 155 | sum += v 156 | } 157 | return sum 158 | } 159 | 160 | // StartsWith returns true if the sequence starts with the given sequence. 161 | func (c *ComparableSequence[T]) StartsWith(other *ComparableSequence[T]) bool { 162 | return collection.StartsWith(c, other) 163 | } 164 | 165 | // EndsWith returns true if the sequence ends with the given sequence. 166 | func (c *ComparableSequence[T]) EndsWith(other *ComparableSequence[T]) bool { 167 | return collection.EndsWith(c, other) 168 | } 169 | -------------------------------------------------------------------------------- /set/set.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package set implements support for a generic unordered Set. 6 | // A Set is a Collection that wraps an underlying hash map 7 | // and provides convenience methods and syntatic sugar on top of it. 8 | // 9 | // Set elements are unique and unordered by default. However Sets share 10 | // some methods with other collections and implement the Collection interface. 11 | package set 12 | 13 | import ( 14 | "fmt" 15 | "iter" 16 | "maps" 17 | 18 | "github.com/charbz/gophers/collection" 19 | ) 20 | 21 | type Set[T comparable] struct { 22 | elements map[T]struct{} 23 | } 24 | 25 | func NewSet[T comparable](s ...[]T) *Set[T] { 26 | set := new(Set[T]) 27 | set.elements = make(map[T]struct{}) 28 | for _, slice := range s { 29 | for _, v := range slice { 30 | set.elements[v] = struct{}{} 31 | } 32 | } 33 | return set 34 | } 35 | 36 | // The following methods implement 37 | // the Collection interface. 38 | 39 | func (s *Set[T]) Add(v T) { 40 | s.elements[v] = struct{}{} 41 | } 42 | 43 | func (s *Set[T]) Length() int { 44 | return len(s.elements) 45 | } 46 | 47 | func (s *Set[T]) Random() T { 48 | for v := range s.elements { 49 | return v 50 | } 51 | panic(collection.EmptyCollectionError) 52 | } 53 | 54 | func (s *Set[T]) New(s2 ...[]T) collection.Collection[T] { 55 | return NewSet(s2...) 56 | } 57 | 58 | func (s *Set[T]) Values() iter.Seq[T] { 59 | return func(yield func(T) bool) { 60 | for k := range s.elements { 61 | if !yield(k) { 62 | break 63 | } 64 | } 65 | } 66 | } 67 | 68 | func (s *Set[T]) ToSlice() []T { 69 | slice := make([]T, 0, len(s.elements)) 70 | for v := range s.elements { 71 | slice = append(slice, v) 72 | } 73 | return slice 74 | } 75 | 76 | // implement the Stringer interface 77 | func (s *Set[T]) String() string { 78 | return fmt.Sprintf("Set(%T) %v", *new(T), s.ToSlice()) 79 | } 80 | 81 | // The following methods are mostly syntatic sugar 82 | // wrapping Collection functions to enable function chaining: 83 | // i.e. set.Filter(f).Foreach(f2) 84 | 85 | // Apply applies a function to each element in the set. 86 | func (s *Set[T]) Apply(f func(T) T) *Set[T] { 87 | for k := range s.elements { 88 | v := f(k) 89 | s.Remove(k) 90 | s.Add(v) 91 | } 92 | return s 93 | } 94 | 95 | // Clone returns a copy of the collection. This is a shallow clone. 96 | func (s *Set[T]) Clone() *Set[T] { 97 | return &Set[T]{ 98 | elements: maps.Clone(s.elements), 99 | } 100 | } 101 | 102 | func (s *Set[T]) Count(f func(T) bool) int { 103 | return collection.Count(s, f) 104 | } 105 | 106 | // Contains returns true if the set contains the value. 107 | func (s *Set[T]) Contains(v T) bool { 108 | _, ok := s.elements[v] 109 | return ok 110 | } 111 | 112 | // ContainsFunc returns true if the set contains a value that satisfies the predicate. 113 | func (s *Set[T]) ContainsFunc(f func(T) bool) bool { 114 | for v := range s.Values() { 115 | if f(v) { 116 | return true 117 | } 118 | } 119 | return false 120 | } 121 | 122 | // Difference returns a new set containing the difference of the current set and the passed in set. 123 | func (s *Set[T]) Diff(set *Set[T]) *Set[T] { 124 | newSet := s.Clone() 125 | for k := range set.Values() { 126 | delete(newSet.elements, k) 127 | } 128 | return newSet 129 | } 130 | 131 | // DiffIterator returns an iterator over the difference of the current set and the passed in set. 132 | func (s *Set[T]) DiffIterator(set *Set[T]) iter.Seq[T] { 133 | return func(yield func(T) bool) { 134 | for k := range s.elements { 135 | if !set.Contains(k) { 136 | yield(k) 137 | } 138 | } 139 | } 140 | } 141 | 142 | // Equals returns true if the two sets contain the same elements. 143 | func (s *Set[T]) Equals(s2 *Set[T]) bool { 144 | if s.Length() != s2.Length() { 145 | return false 146 | } 147 | for k := range s.Values() { 148 | if !s2.Contains(k) { 149 | return false 150 | } 151 | } 152 | return true 153 | } 154 | 155 | // Filter is an alias for collection.Filter 156 | func (s *Set[T]) Filter(f func(T) bool) *Set[T] { 157 | return collection.Filter(s, f).(*Set[T]) 158 | } 159 | 160 | // Filtered is an alias for collection.Filtered 161 | func (s *Set[T]) Filtered(f func(T) bool) iter.Seq[T] { 162 | return collection.Filtered(s, f) 163 | } 164 | 165 | // FilterNot is an alias for collection.FilterNot 166 | func (s *Set[T]) FilterNot(f func(T) bool) *Set[T] { 167 | return collection.FilterNot(s, f).(*Set[T]) 168 | } 169 | 170 | // ForAll is an alias for collection.ForAll 171 | func (s *Set[T]) ForAll(f func(T) bool) bool { 172 | return collection.ForAll(s, f) 173 | } 174 | 175 | // IsEmpty returns true if the set is empty. 176 | func (s *Set[T]) IsEmpty() bool { 177 | return s.Length() == 0 178 | } 179 | 180 | // Intersection returns a new set containing the intersection of the current set and the passed in set. 181 | func (s *Set[T]) Intersection(s2 *Set[T]) *Set[T] { 182 | result := NewSet[T]() 183 | for k := range s2.elements { 184 | if _, ok := s.elements[k]; ok { 185 | result.Add(k) 186 | } 187 | } 188 | return result 189 | } 190 | 191 | // Intersected returns an iterator over the intersection of 192 | // the current set and the passed in set. 193 | func (s *Set[T]) Intersected(s2 *Set[T]) iter.Seq[T] { 194 | return func(yield func(T) bool) { 195 | for k := range s.elements { 196 | if s2.Contains(k) { 197 | yield(k) 198 | } 199 | } 200 | } 201 | } 202 | 203 | // NonEmpty returns true if the set is not empty. 204 | func (s *Set[T]) NonEmpty() bool { 205 | return s.Length() > 0 206 | } 207 | 208 | // Partition is an alias for collection.Partition 209 | func (s *Set[T]) Partition(f func(T) bool) (*Set[T], *Set[T]) { 210 | left, right := collection.Partition(s, f) 211 | return left.(*Set[T]), right.(*Set[T]) 212 | } 213 | 214 | // Remove removes a value from the set. 215 | func (s *Set[T]) Remove(v T) { 216 | delete(s.elements, v) 217 | } 218 | 219 | // Reject is an alias for collection.FilterNot 220 | func (l *Set[T]) Reject(f func(T) bool) *Set[T] { 221 | return collection.FilterNot(l, f).(*Set[T]) 222 | } 223 | 224 | // Rejected is an alias for collection.Rejected 225 | func (s *Set[T]) Rejected(f func(T) bool) iter.Seq[T] { 226 | return collection.Rejected(s, f) 227 | } 228 | 229 | // Union returns a new set containing the union of the current set and the passed in set. 230 | func (s *Set[T]) Union(s2 *Set[T]) *Set[T] { 231 | result := s.Clone() 232 | for k := range s2.elements { 233 | result.Add(k) 234 | } 235 | return result 236 | } 237 | 238 | // Unioned returns an iterator over the union of the current set and the passed in set. 239 | func (s *Set[T]) Unioned(s2 *Set[T]) iter.Seq[T] { 240 | return func(yield func(T) bool) { 241 | for k := range s.elements { 242 | yield(k) 243 | } 244 | for k := range s2.elements { 245 | if !s.Contains(k) { 246 | yield(k) 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /collection/iter_functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // iter_functions implements functions that take a collection as input, and 6 | // return an iterator to the result instead of a new collection. 7 | 8 | package collection 9 | 10 | import "iter" 11 | 12 | // Concatenated returns an iterator that yields the elements of s1 and s2. 13 | // 14 | // example usage: 15 | // 16 | // a := NewList([]int{1,2}) 17 | // b := NewList([]int{3,4}) 18 | // for v := range Concatenated(a, b) { 19 | // fmt.Println(v) 20 | // } 21 | // 22 | // output: 23 | // 24 | // 1 25 | // 2 26 | // 3 27 | // 4 28 | func Concatenated[T any](s1, s2 Collection[T]) iter.Seq[T] { 29 | return func(yield func(T) bool) { 30 | for v := range s1.Values() { 31 | yield(v) 32 | } 33 | for v := range s2.Values() { 34 | yield(v) 35 | } 36 | } 37 | } 38 | 39 | // Diffed returns an iterator that yields the elements of s1 that are not present in s2. 40 | // 41 | // example usage: 42 | // 43 | // a := NewList([]int{1,2,3,4,5,6}) 44 | // b := NewList([]int{2,4,6,8,10,12}) 45 | // for v := range Diffed(a, b) { 46 | // fmt.Println(v) 47 | // } 48 | // 49 | // output: 50 | // 51 | // 1 52 | // 3 53 | // 5 54 | func Diffed[T comparable](s1 OrderedCollection[T], s2 OrderedCollection[T]) iter.Seq[T] { 55 | return func(yield func(T) bool) { 56 | for v := range s1.Values() { 57 | i, _ := Find(s2, func(t T) bool { return t == v }) 58 | if i == -1 { 59 | yield(v) 60 | } 61 | } 62 | } 63 | } 64 | 65 | // DiffedFunc is similar to Diffed but applies to non-comparable types. 66 | // It takes two collections (s1, s2) and an "equality" function as an argument such as 67 | // func(a T, b T) bool {return a == b} 68 | // and returns an iterator that yields the elements of s1 that are not present in s2. 69 | // 70 | // example usage: 71 | // 72 | // a := NewList([]int{1,2,3,4,5,6}) 73 | // b := NewList([]int{2,4,6,8,10,12}) 74 | // for v := range DiffedFunc(a, b, func(a int, b int) bool { return a == b }) { 75 | // fmt.Println(v) 76 | // } 77 | // 78 | // output: 79 | // 80 | // 1 81 | // 3 82 | // 5 83 | func DiffedFunc[T any](s1 OrderedCollection[T], s2 OrderedCollection[T], f func(T, T) bool) iter.Seq[T] { 84 | return func(yield func(T) bool) { 85 | for v := range s1.Values() { 86 | i, _ := Find(s2, func(t T) bool { return f(v, t) }) 87 | if i == -1 { 88 | yield(v) 89 | } 90 | } 91 | } 92 | } 93 | 94 | // Distincted returns an iterator that yields the unique elements of s. 95 | // 96 | // example usage: 97 | // 98 | // a := NewList([]int{1,1,1,2,2,3}) 99 | // for v := range Distincted(a) { 100 | // fmt.Println(v) 101 | // } 102 | // 103 | // output: 104 | // 105 | // 1 106 | // 2 107 | // 3 108 | func Distincted[T comparable](s Collection[T]) iter.Seq[T] { 109 | seen := make(map[T]bool) 110 | return func(yield func(T) bool) { 111 | for v := range s.Values() { 112 | if !seen[v] { 113 | seen[v] = true 114 | yield(v) 115 | } 116 | } 117 | } 118 | } 119 | 120 | // DistinctedFunc is similar to Distincted but applies to non-comparable types. 121 | // It takes a collection (s) and an "equality" function as an argument such as 122 | // func(a T, b T) bool {return a == b} 123 | // and returns an iterator that yields the unique elements of s. 124 | // 125 | // example usage: 126 | // 127 | // a := NewList([]int{1,1,1,2,2,3}) 128 | // for v := range DistinctedFunc(a, func(a int, b int) bool { return a == b }) { 129 | // fmt.Println(v) 130 | // } 131 | // 132 | // output: 133 | // 134 | // 1 135 | // 2 136 | // 3 137 | func DistinctedFunc[T any](s Collection[T], f func(T, T) bool) iter.Seq[T] { 138 | s2 := s.New() 139 | return func(yield func(T) bool) { 140 | for v := range s.Values() { 141 | match := false 142 | for v2 := range s2.Values() { 143 | if f(v, v2) { 144 | match = true 145 | break 146 | } 147 | } 148 | if !match { 149 | s2.Add(v) 150 | yield(v) 151 | } 152 | } 153 | } 154 | } 155 | 156 | // Filtered returns an iterator that yields the elements of s 157 | // that satisfy the predicate function f. 158 | // 159 | // example usage: 160 | // 161 | // a := NewList([]int{1,2,3,4,5,6}) 162 | // for v := range Filtered(a, func(i int) bool { return i % 2 == 0 }) { 163 | // fmt.Println(v) 164 | // } 165 | // 166 | // output: 167 | // 168 | // 2 169 | // 4 170 | // 6 171 | func Filtered[T any](s Collection[T], f func(T) bool) iter.Seq[T] { 172 | return func(yield func(T) bool) { 173 | for v := range s.Values() { 174 | if f(v) { 175 | yield(v) 176 | } 177 | } 178 | } 179 | } 180 | 181 | // Intersected returns an iterator that yields the elements of s1 182 | // that are also present in s2. 183 | // 184 | // example usage: 185 | // 186 | // a := NewList([]int{1,3,4,5,6}) 187 | // b := NewList([]int{2,4,6,8,10,12}) 188 | // for v := range Intersected(a, b) { 189 | // fmt.Println(v) 190 | // } 191 | // 192 | // output: 193 | // 194 | // 4 195 | // 6 196 | func Intersected[T comparable](s1 Collection[T], s2 Collection[T]) iter.Seq[T] { 197 | return func(yield func(T) bool) { 198 | for v := range s1.Values() { 199 | for v2 := range s2.Values() { 200 | if v == v2 { 201 | yield(v) 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | // IntersectedFunc is similar to Intersected but applies to non-comparable types. 209 | // It takes two collections (s1, s2) and an "equality" function as an argument such as 210 | // func(a T, b T) bool {return a == b} 211 | // and returns an iterator that yields the elements of s1 that are also present in s2. 212 | // 213 | // example usage: 214 | // 215 | // a := NewList([]int{1,3,4,5,6}) 216 | // b := NewList([]int{2,4,6,8,10,12}) 217 | // for v := range IntersectedFunc(a, b, func(a int, b int) bool { return a == b }) { 218 | // fmt.Println(v) 219 | // } 220 | // 221 | // output: 222 | // 223 | // 4 224 | // 6 225 | func IntersectedFunc[T any](s1 Collection[T], s2 Collection[T], f func(T, T) bool) iter.Seq[T] { 226 | return func(yield func(T) bool) { 227 | for v := range s1.Values() { 228 | for v2 := range s2.Values() { 229 | if f(v, v2) { 230 | yield(v) 231 | } 232 | } 233 | } 234 | } 235 | } 236 | 237 | // Mapped returns an iterator that yields the elements of s 238 | // transformed by the function f. 239 | // 240 | // example usage: 241 | // 242 | // a := NewList([]int{1,2,3}) 243 | // for v := range Mapped(a, func(i int) int { return i * 2 }) { 244 | // fmt.Println(v) 245 | // } 246 | // 247 | // output: 248 | // 249 | // 2 250 | // 4 251 | // 6 252 | func Mapped[T, K any](s Collection[T], f func(T) K) iter.Seq[K] { 253 | return func(yield func(K) bool) { 254 | for v := range s.Values() { 255 | yield(f(v)) 256 | } 257 | } 258 | } 259 | 260 | // Rejected returns an iterator that yields the elements of s 261 | // that do not satisfy the predicate function f. 262 | // 263 | // example usage: 264 | // 265 | // a := NewList([]int{1,2,3,4,5,6}) 266 | // for v := range Rejected(a, func(i int) bool { return i % 2 == 0 }) { 267 | // fmt.Println(v) 268 | // } 269 | // 270 | // output: 271 | // 272 | // 1 273 | // 3 274 | // 5 275 | func Rejected[T any](s Collection[T], f func(T) bool) iter.Seq[T] { 276 | return Filtered(s, func(t T) bool { return !f(t) }) 277 | } 278 | -------------------------------------------------------------------------------- /collection/functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // functions.go defines all the package functions that operate on a Collection. 6 | // These functions apply to both Collection and OrderedCollection types. 7 | 8 | // Unfortunately Go does not allow Generic type parameters to be defined directly on struct methods, 9 | // Given that the Collection struct is bound to 1 generic argument [T any] representing the underlying type, 10 | // operations that map into a different type altogether such as f(Collection[T]) -> Collection[K] 11 | // must be defined as functions. 12 | 13 | package collection 14 | 15 | import ( 16 | "cmp" 17 | ) 18 | 19 | // Count returns the number of elements in the collection that satisfy the predicate function. 20 | // 21 | // example usage: 22 | // 23 | // c := NewSequence([]int{1,2,3,4,5,6}) 24 | // Count(c, func(i int) bool { return i % 2 == 0 }) 25 | // 26 | // output: 27 | // 28 | // 3 29 | func Count[T any](s Collection[T], f func(T) bool) int { 30 | count := 0 31 | for v := range s.Values() { 32 | if f(v) { 33 | count++ 34 | } 35 | } 36 | return count 37 | } 38 | 39 | // Diff returns a new collection containing elements that are present in the first collection but not in the second. 40 | // 41 | // example usage: 42 | // 43 | // c1 := NewSequence([]int{1,2,3,4,5,6}) 44 | // c2 := NewSequence([]int{2,4,6,8,10,12}) 45 | // Diff(c1, c2) 46 | // 47 | // output: 48 | // 49 | // [1,3,5] 50 | func Diff[T comparable](s1 Collection[T], s2 Collection[T]) Collection[T] { 51 | return FilterNot(s1, func(t T) bool { 52 | for v := range s2.Values() { 53 | if v == t { 54 | return true 55 | } 56 | } 57 | return false 58 | }) 59 | } 60 | 61 | // DiffFunc is similar to Diff but applies to non-comparable types. 62 | // It takes two collections (s1, s2) and an "equality" function as an argument such as 63 | // func(a T, b T) bool {return a == b} 64 | // and returns a new sequence containing all the elements of s1 that are not present in s2. 65 | // 66 | // example usage: 67 | // 68 | // c1 := NewSequence([]int{1,2,3,4,5,6}) 69 | // c2 := NewSequence([]int{2,4,6,8,10,12}) 70 | // DiffFunc(c1, c2, func(a int, b int) bool { return a == b }) 71 | // 72 | // output: 73 | // 74 | // [1,3,5] 75 | func DiffFunc[T any](s1 Collection[T], s2 Collection[T], f func(T, T) bool) Collection[T] { 76 | return FilterNot(s1, func(t T) bool { 77 | for v := range s2.Values() { 78 | if f(t, v) { 79 | return true 80 | } 81 | } 82 | return false 83 | }) 84 | } 85 | 86 | // Distinct returns a new collection containing only the unique elements of the collection. 87 | // 88 | // example usage: 89 | // 90 | // c := NewSequence([]int{1,1,1,4,5,1,2,2}) 91 | // Distinct(c, func(i int, i2 int) bool { return i == i2 }) 92 | // 93 | // output: 94 | // 95 | // [1,4,5,2] 96 | func Distinct[T any](s Collection[T], f func(T, T) bool) Collection[T] { 97 | s2 := s.New() 98 | for v := range s.Values() { 99 | match := false 100 | for v2 := range s2.Values() { 101 | if f(v, v2) { 102 | match = true 103 | break 104 | } 105 | } 106 | if !match { 107 | s2.Add(v) 108 | } 109 | } 110 | return s2 111 | } 112 | 113 | // Filter returns a new collection containing only the elements that 114 | // satisfy the predicate function. 115 | // 116 | // example usage: 117 | // 118 | // numbers := NewSequence([]int{1,2,3,4,5,6}) 119 | // Filter(numbers, func(t int) bool { return t % 2 == 0 }) 120 | // 121 | // output: 122 | // 123 | // [2,4,6] 124 | func Filter[T any](s Collection[T], f func(T) bool) Collection[T] { 125 | result := s.New() 126 | for v := range s.Values() { 127 | if f(v) { 128 | result.Add(v) 129 | } 130 | } 131 | return result 132 | } 133 | 134 | // FilterNot returns the complement of the Filter function. 135 | // 136 | // example usage: 137 | // 138 | // c := NewSequence([]int{1,2,3,4,5,6}) 139 | // FilterNot(c, func(t int) bool { return t % 2 == 0 }) 140 | // 141 | // output: 142 | // 143 | // [1,3,5] 144 | func FilterNot[T any](s Collection[T], f func(T) bool) Collection[T] { 145 | return Filter(s, func(t T) bool { return !f(t) }) 146 | } 147 | 148 | // ForAll tests whether a predicate holds for all elements of this sequence. 149 | // 150 | // example usage: 151 | // 152 | // c := NewSequence([]int{1,2,3,4,5,6}) 153 | // ForAll(c, func(i int) bool { return i < 10 }) 154 | // 155 | // output: 156 | // 157 | // true 158 | func ForAll[T any](s Collection[T], f func(T) bool) bool { 159 | for v := range s.Values() { 160 | if !f(v) { 161 | return false 162 | } 163 | } 164 | return true 165 | } 166 | 167 | // GroupBy takes a collection and a grouping function as input and returns a map 168 | // where the key is the result of the grouping function and the value is a collection 169 | // of elements that satisfy the predicate. 170 | // 171 | // example usage: 172 | // 173 | // c := NewSequence([]int{1,2,3,4,5,6}) 174 | // GroupBy(c, func(i int) int { return i % 2 }) 175 | // 176 | // output: 177 | // 178 | // {0:[2,4,6], 1:[1,3,5]} 179 | func GroupBy[T any, K comparable](s Collection[T], f func(T) K) map[K]Collection[T] { 180 | m := make(map[K]Collection[T]) 181 | for v := range s.Values() { 182 | k := f(v) 183 | if _, ok := m[k]; !ok { 184 | m[k] = s.New() 185 | } 186 | m[k].Add(v) 187 | } 188 | return m 189 | } 190 | 191 | // Intersect returns a new collection containing elements that are present in both input collections. 192 | // 193 | // example usage: 194 | // 195 | // c1 := NewSequence([]int{1,2,3,4,5,6}) 196 | // c2 := NewSequence([]int{2,4,6,8,10,12}) 197 | // Intersect(c1, c2) 198 | // 199 | // output: 200 | // 201 | // [2,4,6] 202 | func Intersect[T comparable](s1 Collection[T], s2 Collection[T]) Collection[T] { 203 | return Filter(s1, func(t T) bool { 204 | for v := range s2.Values() { 205 | if v == t { 206 | return true 207 | } 208 | } 209 | return false 210 | }) 211 | } 212 | 213 | // IntersectFunc is similar to Intersect but applies to non-comparable types. 214 | // It takes two collections (s1, s2) and an "equality" function as an argument such as 215 | // func(a T, b T) bool {return a == b} 216 | // and returns a new sequence containing all the elements of s1 that are also present in s2. 217 | // 218 | // example usage: 219 | // 220 | // c1 := NewSequence([]int{1,2,3,4,5,6}) 221 | // c2 := NewSequence([]int{2,4,6,8,10,12}) 222 | // IntersectFunc(c1, c2, func(a int, b int) bool { return a == b }) 223 | // 224 | // output: 225 | // 226 | // [2,4,6] 227 | func IntersectFunc[T any](s1 Collection[T], s2 Collection[T], f func(T, T) bool) Collection[T] { 228 | return Filter(s1, func(t T) bool { 229 | for v := range s2.Values() { 230 | if f(t, v) { 231 | return true 232 | } 233 | } 234 | return false 235 | }) 236 | } 237 | 238 | // Map takes a collection of type T and a mapping function func(T) K, 239 | // applies the mapping function to each element and returns a slice of type K. 240 | // 241 | // example usage: 242 | // 243 | // names := NewCollection([]string{"Alice", "Bob", "Charlie"}) 244 | // Map(names, func(name string) int { 245 | // return len(name) 246 | // }) 247 | // 248 | // output: 249 | // 250 | // [5,3,6] 251 | func Map[T, K any](s Collection[T], f func(T) K) []K { 252 | k := make([]K, 0, s.Length()) 253 | for v := range s.Values() { 254 | k = append(k, f(v)) 255 | } 256 | return k 257 | } 258 | 259 | // MaxBy returns the element in the collection that has the maximum value 260 | // according to a comparison function. 261 | // 262 | // example usage: 263 | // 264 | // c := NewSequence([]int{1,2,3,4,5,6}) 265 | // MaxBy(c, func(a int, b int) int { return a - b }) 266 | // 267 | // output: 268 | // 269 | // 6 270 | func MaxBy[T any, K cmp.Ordered](s Collection[T], f func(T) K) (T, error) { 271 | if s.Length() == 0 { 272 | return *new(T), EmptyCollectionError 273 | } 274 | maxElement := s.Random() 275 | maxValue := f(maxElement) 276 | for v := range s.Values() { 277 | if f(v) > maxValue { 278 | maxElement = v 279 | maxValue = f(v) 280 | } 281 | } 282 | return maxElement, nil 283 | } 284 | 285 | // MinBy returns the element in the collection that has the minimum value 286 | // according to a comparison function. 287 | // 288 | // example usage: 289 | // 290 | // c := NewSequence([]int{1,2,3,4,5,6}) 291 | // MinBy(c, func(a int, b int) int { return a - b }) 292 | // 293 | // output: 294 | // 295 | // 1 296 | func MinBy[T any, K cmp.Ordered](s Collection[T], f func(T) K) (T, error) { 297 | if s.Length() == 0 { 298 | return *new(T), EmptyCollectionError 299 | } 300 | minElement := s.Random() 301 | minValue := f(minElement) 302 | for v := range s.Values() { 303 | if f(v) < minValue { 304 | minElement = v 305 | minValue = f(v) 306 | } 307 | } 308 | return minElement, nil 309 | } 310 | 311 | // Partition takes a partitioning function as input and returns two collections, 312 | // the first one contains the elements that match the partitioning condition, 313 | // the second one contains the rest of the elements. 314 | // 315 | // example usage: 316 | // 317 | // c := NewSequence([]int{1,2,3,4,5,6}) 318 | // Partition(c, func(i int) bool { 319 | // return i%2==0 320 | // }) 321 | // 322 | // output: 323 | // 324 | // [2,4,6], [1,3,5] 325 | func Partition[T any](s Collection[T], f func(T) bool) (Collection[T], Collection[T]) { 326 | match := s.New() 327 | noMatch := s.New() 328 | for v := range s.Values() { 329 | if f(v) { 330 | match.Add(v) 331 | } else { 332 | noMatch.Add(v) 333 | } 334 | } 335 | return match, noMatch 336 | } 337 | 338 | // Reduce takes a collection of type T, a reducing function func(K, T) K, 339 | // and an initial value of type K as parameters. It applies the reducing 340 | // function to each element and returns the resulting value K. 341 | // 342 | // example usage: 343 | // 344 | // numbers := NewCollection([]int{1,2,3,4,5,6}) 345 | // 346 | // Reduce(numbers, func(accumulator int, number int) int { 347 | // return accumulator + number 348 | // }, 0) 349 | // 350 | // output: 351 | // 352 | // 21 353 | func Reduce[T, K any](s Collection[T], f func(K, T) K, init K) K { 354 | accumulator := init 355 | for v := range s.Values() { 356 | accumulator = f(accumulator, v) 357 | } 358 | return accumulator 359 | } 360 | -------------------------------------------------------------------------------- /collection/iter_functions_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "slices" 5 | "testing" 6 | ) 7 | 8 | func TestConcatenated(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | a OrderedCollection[int] 12 | b OrderedCollection[int] 13 | want []int 14 | }{ 15 | { 16 | name: "concat two lists", 17 | a: NewMockOrderedCollection([]int{1, 2}), 18 | b: NewMockOrderedCollection([]int{3, 4}), 19 | want: []int{1, 2, 3, 4}, 20 | }, 21 | { 22 | name: "concat empty list A", 23 | a: NewMockOrderedCollection([]int{}), 24 | b: NewMockOrderedCollection([]int{1, 2, 3}), 25 | want: []int{1, 2, 3}, 26 | }, 27 | { 28 | name: "concat empty list B", 29 | a: NewMockOrderedCollection([]int{1, 2, 3}), 30 | b: NewMockOrderedCollection([]int{}), 31 | want: []int{1, 2, 3}, 32 | }, 33 | { 34 | name: "concat empty lists A and B", 35 | a: NewMockOrderedCollection([]int{}), 36 | b: NewMockOrderedCollection([]int{}), 37 | want: []int{}, 38 | }, 39 | } 40 | 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | collected := []int{} 44 | for v := range Concatenated(tt.a, tt.b) { 45 | collected = append(collected, v) 46 | } 47 | if !slices.Equal(collected, tt.want) { 48 | t.Errorf("Concatenated() = %v, want %v", collected, tt.want) 49 | } 50 | }) 51 | } 52 | } 53 | 54 | func TestDiffed(t *testing.T) { 55 | tests := []struct { 56 | name string 57 | a OrderedCollection[int] 58 | b OrderedCollection[int] 59 | want []int 60 | }{ 61 | { 62 | name: "diff same elements", 63 | a: NewMockOrderedCollection([]int{1, 2, 3}), 64 | b: NewMockOrderedCollection([]int{1, 2, 3}), 65 | want: []int{}, 66 | }, 67 | { 68 | name: "diff different elements", 69 | a: NewMockOrderedCollection([]int{1, 2, 3, 6, 5}), 70 | b: NewMockOrderedCollection([]int{2, 4, 6}), 71 | want: []int{1, 3, 5}, 72 | }, 73 | { 74 | name: "diff empty B", 75 | a: NewMockOrderedCollection([]int{1, 2, 3}), 76 | b: NewMockOrderedCollection([]int{}), 77 | want: []int{1, 2, 3}, 78 | }, 79 | { 80 | name: "diff empty A", 81 | a: NewMockOrderedCollection([]int{}), 82 | b: NewMockOrderedCollection([]int{1, 2, 3}), 83 | want: []int{}, 84 | }, 85 | } 86 | 87 | for _, tt := range tests { 88 | t.Run(tt.name, func(t *testing.T) { 89 | collected := []int{} 90 | for v := range Diffed(tt.a, tt.b) { 91 | collected = append(collected, v) 92 | } 93 | if !slices.Equal(collected, tt.want) { 94 | t.Errorf("Diffed() = %v, want %v", collected, tt.want) 95 | } 96 | }) 97 | } 98 | } 99 | 100 | func TestDiffedFunc(t *testing.T) { 101 | tests := []struct { 102 | name string 103 | a OrderedCollection[int] 104 | b OrderedCollection[int] 105 | want []int 106 | }{ 107 | { 108 | name: "diff same elements", 109 | a: NewMockOrderedCollection([]int{1, 2, 3}), 110 | b: NewMockOrderedCollection([]int{1, 2, 3}), 111 | want: []int{}, 112 | }, 113 | { 114 | name: "diff different elements", 115 | a: NewMockOrderedCollection([]int{1, 2, 3, 6, 5}), 116 | b: NewMockOrderedCollection([]int{2, 4, 6}), 117 | want: []int{1, 3, 5}, 118 | }, 119 | { 120 | name: "diff empty B", 121 | a: NewMockOrderedCollection([]int{1, 2, 3}), 122 | b: NewMockOrderedCollection([]int{}), 123 | want: []int{1, 2, 3}, 124 | }, 125 | { 126 | name: "diff empty A", 127 | a: NewMockOrderedCollection([]int{}), 128 | b: NewMockOrderedCollection([]int{1, 2, 3}), 129 | want: []int{}, 130 | }, 131 | } 132 | 133 | for _, tt := range tests { 134 | t.Run(tt.name, func(t *testing.T) { 135 | collected := []int{} 136 | for v := range DiffedFunc(tt.a, tt.b, func(a, b int) bool { return a == b }) { 137 | collected = append(collected, v) 138 | } 139 | if !slices.Equal(collected, tt.want) { 140 | t.Errorf("DiffedFunc() = %v, want %v", collected, tt.want) 141 | } 142 | }) 143 | } 144 | } 145 | 146 | func TestDistincted(t *testing.T) { 147 | tests := []struct { 148 | name string 149 | a OrderedCollection[int] 150 | want []int 151 | }{ 152 | { 153 | name: "distinct elements", 154 | a: NewMockOrderedCollection([]int{1, 1, 2, 2, 3}), 155 | want: []int{1, 2, 3}, 156 | }, 157 | { 158 | name: "distinct with no duplicates", 159 | a: NewMockOrderedCollection([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), 160 | want: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 161 | }, 162 | { 163 | name: "distinct with empty collection", 164 | a: NewMockOrderedCollection([]int{}), 165 | want: []int{}, 166 | }, 167 | } 168 | 169 | for _, tt := range tests { 170 | t.Run(tt.name, func(t *testing.T) { 171 | collected := []int{} 172 | for v := range Distincted(tt.a) { 173 | collected = append(collected, v) 174 | } 175 | if !slices.Equal(collected, tt.want) { 176 | t.Errorf("Distincted() = %v, want %v", collected, tt.want) 177 | } 178 | }) 179 | } 180 | } 181 | 182 | func TestDistinctedFunc(t *testing.T) { 183 | tests := []struct { 184 | name string 185 | a OrderedCollection[int] 186 | want []int 187 | }{ 188 | { 189 | name: "distinct elements", 190 | a: NewMockOrderedCollection([]int{1, 1, 2, 2, 3}), 191 | want: []int{1, 2, 3}, 192 | }, 193 | { 194 | name: "distinct with no duplicates", 195 | a: NewMockOrderedCollection([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}), 196 | want: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 197 | }, 198 | { 199 | name: "distinct with empty collection", 200 | a: NewMockOrderedCollection([]int{}), 201 | want: []int{}, 202 | }, 203 | } 204 | 205 | for _, tt := range tests { 206 | t.Run(tt.name, func(t *testing.T) { 207 | collected := []int{} 208 | for v := range DistinctedFunc(tt.a, func(a, b int) bool { return a == b }) { 209 | collected = append(collected, v) 210 | } 211 | if !slices.Equal(collected, tt.want) { 212 | t.Errorf("DistinctedFunc() = %v, want %v", collected, tt.want) 213 | } 214 | }) 215 | } 216 | } 217 | 218 | func TestIntersected(t *testing.T) { 219 | tests := []struct { 220 | name string 221 | a OrderedCollection[int] 222 | b OrderedCollection[int] 223 | want []int 224 | }{ 225 | { 226 | name: "intersect", 227 | a: NewMockOrderedCollection([]int{1, 2, 3, 4, 5, 6, 6, 4}), 228 | b: NewMockOrderedCollection([]int{2, 4, 6, 8, 10, 12}), 229 | want: []int{2, 4, 6, 6, 4}, 230 | }, 231 | } 232 | 233 | for _, tt := range tests { 234 | t.Run(tt.name, func(t *testing.T) { 235 | collected := []int{} 236 | for v := range Intersected(tt.a, tt.b) { 237 | collected = append(collected, v) 238 | } 239 | if !slices.Equal(collected, tt.want) { 240 | t.Errorf("Intersected() = %v, want %v", collected, tt.want) 241 | } 242 | }) 243 | } 244 | } 245 | 246 | func TestIntersectedFunc(t *testing.T) { 247 | tests := []struct { 248 | name string 249 | a OrderedCollection[int] 250 | b OrderedCollection[int] 251 | want []int 252 | }{ 253 | { 254 | name: "intersect", 255 | a: NewMockOrderedCollection([]int{1, 2, 3, 4, 5, 6, 6, 4}), 256 | b: NewMockOrderedCollection([]int{2, 4, 6, 8, 10, 12}), 257 | want: []int{2, 4, 6, 6, 4}, 258 | }, 259 | } 260 | 261 | for _, tt := range tests { 262 | t.Run(tt.name, func(t *testing.T) { 263 | collected := []int{} 264 | for v := range IntersectedFunc(tt.a, tt.b, func(a, b int) bool { return a == b }) { 265 | collected = append(collected, v) 266 | } 267 | if !slices.Equal(collected, tt.want) { 268 | t.Errorf("IntersectedFunc() = %v, want %v", collected, tt.want) 269 | } 270 | }) 271 | } 272 | } 273 | 274 | func TestFiltered(t *testing.T) { 275 | tests := []struct { 276 | name string 277 | a OrderedCollection[int] 278 | want []int 279 | }{ 280 | { 281 | name: "filter even numbers", 282 | a: NewMockOrderedCollection([]int{1, 2, 3, 4, 5, 6}), 283 | want: []int{2, 4, 6}, 284 | }, 285 | { 286 | name: "filter no matches", 287 | a: NewMockOrderedCollection([]int{1, 3, 5, 7, 9}), 288 | want: []int{}, 289 | }, 290 | { 291 | name: "filter empty collection", 292 | a: NewMockOrderedCollection([]int{}), 293 | want: []int{}, 294 | }, 295 | } 296 | 297 | for _, tt := range tests { 298 | t.Run(tt.name, func(t *testing.T) { 299 | collected := []int{} 300 | for v := range Filtered(tt.a, func(i int) bool { return i%2 == 0 }) { 301 | collected = append(collected, v) 302 | } 303 | if !slices.Equal(collected, tt.want) { 304 | t.Errorf("Filtered() = %v, want %v", collected, tt.want) 305 | } 306 | }) 307 | } 308 | } 309 | 310 | func TestMapped(t *testing.T) { 311 | tests := []struct { 312 | name string 313 | a OrderedCollection[int] 314 | want []int 315 | }{ 316 | { 317 | name: "double numbers", 318 | a: NewMockOrderedCollection([]int{1, 2, 3}), 319 | want: []int{2, 4, 6}, 320 | }, 321 | { 322 | name: "empty collection", 323 | a: NewMockOrderedCollection([]int{}), 324 | want: []int{}, 325 | }, 326 | } 327 | 328 | for _, tt := range tests { 329 | t.Run(tt.name, func(t *testing.T) { 330 | collected := []int{} 331 | for v := range Mapped(tt.a, func(i int) int { return i * 2 }) { 332 | collected = append(collected, v) 333 | } 334 | if !slices.Equal(collected, tt.want) { 335 | t.Errorf("Mapped() = %v, want %v", collected, tt.want) 336 | } 337 | }) 338 | } 339 | } 340 | 341 | func TestRejected(t *testing.T) { 342 | tests := []struct { 343 | name string 344 | a OrderedCollection[int] 345 | want []int 346 | }{ 347 | { 348 | name: "reject even numbers", 349 | a: NewMockOrderedCollection([]int{1, 2, 3, 4, 5, 6}), 350 | want: []int{1, 3, 5}, 351 | }, 352 | { 353 | name: "reject no matches", 354 | a: NewMockOrderedCollection([]int{2, 4, 6, 8}), 355 | want: []int{}, 356 | }, 357 | { 358 | name: "reject empty collection", 359 | a: NewMockOrderedCollection([]int{}), 360 | want: []int{}, 361 | }, 362 | } 363 | 364 | for _, tt := range tests { 365 | t.Run(tt.name, func(t *testing.T) { 366 | collected := []int{} 367 | for v := range Rejected(tt.a, func(i int) bool { return i%2 == 0 }) { 368 | collected = append(collected, v) 369 | } 370 | if !slices.Equal(collected, tt.want) { 371 | t.Errorf("Rejected() = %v, want %v", collected, tt.want) 372 | } 373 | }) 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /collection/ordered_functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // ordered_functions.go defines all the package functions that operate only on an OrderedCollection. 6 | 7 | package collection 8 | 9 | import "math/rand" 10 | 11 | // Corresponds tests whether every element of this sequence relates to the corresponding 12 | // element of another sequence by satisfying a test predicate. 13 | // 14 | // example usage: 15 | // 16 | // c1 := NewSequence([]int{1,2,3,4,5,6}) 17 | // c2 := NewSequence([]int{2,4,6,8,10,12}) 18 | // Corresponds(c1, c2, func(i int, j int) bool { return i*2 == j }) 19 | // 20 | // output: 21 | // 22 | // true 23 | func Corresponds[T, K any](s1 OrderedCollection[T], s2 OrderedCollection[K], f func(T, K) bool) bool { 24 | if s1.Length() != s2.Length() { 25 | return false 26 | } 27 | for i, v := range s1.All() { 28 | if !f(v, s2.At(i)) { 29 | return false 30 | } 31 | } 32 | return true 33 | } 34 | 35 | // Drop returns a new sequence with the first n elements removed. 36 | // 37 | // example usage: 38 | // 39 | // c := NewSequence([]int{1,2,3,4,5,6}) 40 | // c.Drop(2) 41 | // 42 | // output: 43 | // 44 | // [3,4,5,6] 45 | func Drop[T any](s OrderedCollection[T], n int) OrderedCollection[T] { 46 | if n <= 0 { 47 | return s 48 | } else if n >= s.Length() { 49 | return s.NewOrdered() 50 | } 51 | return s.Slice(n, s.Length()) 52 | } 53 | 54 | // DropRight returns a sequence with the last n elements removed. 55 | // 56 | // example usage: 57 | // 58 | // c := NewSequence([]int{1,2,3,4,5,6}) 59 | // c.DropRight(2) 60 | // 61 | // output: 62 | // 63 | // [1,2,3,4] 64 | func DropRight[T any](s OrderedCollection[T], n int) OrderedCollection[T] { 65 | if n <= 0 { 66 | return s 67 | } else if n >= s.Length() { 68 | return s.NewOrdered() 69 | } 70 | return s.Slice(0, s.Length()-n) 71 | } 72 | 73 | // DropWhile returns a sequence with the first n elements that 74 | // satisfy a predicate removed. 75 | // 76 | // example usage: 77 | // 78 | // c := NewSequence([]int{1,2,3,4,5,6}) 79 | // c.DropWhile(func(i int) bool { return i < 4 }) 80 | // 81 | // output: 82 | // 83 | // [4,5,6] 84 | func DropWhile[T any](s OrderedCollection[T], f func(T) bool) OrderedCollection[T] { 85 | count := 0 86 | for v := range s.Values() { 87 | if !f(v) { 88 | break 89 | } 90 | count++ 91 | } 92 | return s.Slice(count, s.Length()) 93 | } 94 | 95 | // Find returns the index and value of the first element 96 | // that satisfies a predicate, otherwise returns -1 and the zero value. 97 | // 98 | // example usage: 99 | // 100 | // c := NewSequence([]int{1,2,3,4,5,6}) 101 | // Find(c, func(i int) bool { 102 | // return (i + 3) > 5 103 | // }) 104 | // 105 | // output 106 | // 107 | // 2, 3 108 | func Find[T any](s OrderedCollection[T], f func(T) bool) (index int, value T) { 109 | for i, v := range s.All() { 110 | if f(v) { 111 | return i, v 112 | } 113 | } 114 | return -1, *new(T) 115 | } 116 | 117 | // FindLast returns the index and value of the last element 118 | // that satisfies a predicate, otherwise returns -1 and the zero value. 119 | // 120 | // example usage: 121 | // 122 | // c := NewSequence([]int{1,2,3,4,5,6}) 123 | // FindLast(c, func(i int) bool { return i < 6 }) 124 | // 125 | // output: 126 | // 127 | // 4, 5 128 | func FindLast[T any](s OrderedCollection[T], f func(T) bool) (index int, value T) { 129 | for i, v := range s.Backward() { 130 | if f(v) { 131 | return i, v 132 | } 133 | } 134 | return -1, *new(T) 135 | } 136 | 137 | // Head returns the first element in a Sequence and a nil error. 138 | // If the sequence is empty, it returns the zero value and an error. 139 | // 140 | // example usage: 141 | // 142 | // c := NewSequence([]string{"A","B","C"}) 143 | // c.Head() 144 | // 145 | // output: 146 | // 147 | // "A", nil 148 | func Head[T any](s OrderedCollection[T]) (T, error) { 149 | if s.Length() == 0 { 150 | return *new(T), EmptyCollectionError 151 | } 152 | return s.At(0), nil 153 | } 154 | 155 | // Init returns a collection containing all elements excluding the last one. 156 | // 157 | // example usage: 158 | // 159 | // c := NewSequence([]int{1,2,3,4,5,6}) 160 | // c.Init() 161 | // 162 | // output: 163 | // 164 | // [1,2,3,4,5] 165 | func Init[T any](s OrderedCollection[T]) OrderedCollection[T] { 166 | if s.Length() == 0 { 167 | return s 168 | } 169 | return s.Slice(0, s.Length()-1) 170 | } 171 | 172 | // Last returns the last element in the Sequence and a nil error. 173 | // If the sequence is empty, it returns the zero value and an error. 174 | // 175 | // example usage: 176 | // 177 | // c := NewSequence([]string{"A","B","C"}) 178 | // c.Last() 179 | // 180 | // output: 181 | // 182 | // "C", nil 183 | func Last[T any](s OrderedCollection[T]) (T, error) { 184 | if s.Length() == 0 { 185 | return *new(T), EmptyCollectionError 186 | } 187 | return s.At(s.Length() - 1), nil 188 | } 189 | 190 | // ReduceRight takes a collection of type T, a reducing function func(K, T) K, 191 | // and an initial value of type K as parameters. It applies the reducing 192 | // function to each element in reverse order and returns the resulting value K. 193 | // 194 | // example usage: 195 | // 196 | // c := NewSequence([]string{"A","B","C"}) 197 | // ReduceRight(c, func(acc string, i string) string { return acc + i }, "") 198 | // 199 | // output: 200 | // 201 | // "CBA" 202 | func ReduceRight[T, K any](s OrderedCollection[T], f func(K, T) K, init K) K { 203 | accumulator := init 204 | for _, v := range s.Backward() { 205 | accumulator = f(accumulator, v) 206 | } 207 | return accumulator 208 | } 209 | 210 | // Reverse returns a new sequence with all elements in reverse order. 211 | // 212 | // example usage: 213 | // 214 | // c := NewSequence([]int{1,2,3,4,5,6}) 215 | // c.Reverse() 216 | // 217 | // output: 218 | // 219 | // [6,5,4,3,2,1] 220 | func Reverse[T any](s OrderedCollection[T]) OrderedCollection[T] { 221 | c := s.NewOrdered() 222 | for _, v := range s.Backward() { 223 | c.Add(v) 224 | } 225 | return c 226 | } 227 | 228 | // ReverseMap takes a collection of type T and a mapping function func(T) K, 229 | // applies the mapping function to each element in reverseand returns a collection of type K. 230 | // 231 | // example usage: 232 | // 233 | // names := NewCollection([]string{"Alice", "Bob", "Charlie"}) 234 | // Map(names, func(name string) int { 235 | // return len(name) 236 | // }) 237 | // 238 | // output: 239 | // 240 | // [6, 3, 5] 241 | func ReverseMap[T, K any](s OrderedCollection[T], f func(T) K) OrderedCollection[K] { 242 | r := s.NewOrdered().(OrderedCollection[K]) 243 | for _, v := range s.Backward() { 244 | r.Add(f(v)) 245 | } 246 | return r 247 | } 248 | 249 | // SplitAt returns two new sequences containing the first n elements and the rest of the elements. 250 | // 251 | // example usage: 252 | // 253 | // c := NewSequence([]int{1,2,3,4,5,6}) 254 | // SplitAt(c, 3) 255 | // 256 | // output: 257 | // 258 | // [1,2,3], [4,5,6] 259 | func SplitAt[T any](s OrderedCollection[T], n int) (OrderedCollection[T], OrderedCollection[T]) { 260 | return s.Slice(0, n), s.Slice(n, s.Length()) 261 | } 262 | 263 | // Tail returns a new sequence containing all elements excluding the first one. 264 | // 265 | // example usage: 266 | // 267 | // c := NewSequence([]int{1,2,3,4,5,6}) 268 | // c.Tail() 269 | // 270 | // output: 271 | // 272 | // [2,3,4,5,6] 273 | func Tail[T any](s OrderedCollection[T]) OrderedCollection[T] { 274 | if s.Length() == 0 { 275 | return s 276 | } 277 | return s.Slice(1, s.Length()) 278 | } 279 | 280 | // Take returns a new sequence containing the first n elements. 281 | // 282 | // example usage: 283 | // 284 | // [c := NewSequence([]int{1,2,3,4,5,6}) 285 | // c.Take(3) 286 | // 287 | // output: 288 | // 289 | // [1,2,3] 290 | func Take[T any](s OrderedCollection[T], n int) OrderedCollection[T] { 291 | if n <= 0 { 292 | return s.NewOrdered() 293 | } 294 | return s.Slice(0, min(n, s.Length())) 295 | } 296 | 297 | // TakeRight returns a new sequence containing the last n elements. 298 | // 299 | // example usage: 300 | // 301 | // c := NewSequence([]int{1,2,3,4,5,6}) 302 | // c.TakeRight(3) 303 | // 304 | // output: 305 | // 306 | // [4,5,6] 307 | func TakeRight[T any](s OrderedCollection[T], n int) OrderedCollection[T] { 308 | if n <= 0 { 309 | return s.NewOrdered() 310 | } 311 | return s.Slice(max(s.Length()-n, 0), s.Length()) 312 | } 313 | 314 | // Shuffle returns a new sequence with the elements randomly shuffled 315 | // This function makes use of the Fisher-Yates shuffle algorithm for optimal performance 316 | // 317 | // Example usage: 318 | // 319 | // c := NewSequence([]int{1,2,3,4,5}) 320 | // c.Shuffle() 321 | // 322 | // possible output: 323 | // 324 | // [4,2,5,1,3] 325 | func Shuffle[T any](s OrderedCollection[T]) OrderedCollection[T] { 326 | // Create a slice to hold indices for shuffling 327 | indices := make([]int, s.Length()) 328 | for i := 0; i < s.Length(); i++ { 329 | indices[i] = i 330 | } 331 | 332 | // Perform Fisher-Yates shuffle algorithm on the indices 333 | for i := len(indices) - 1; i > 0; i-- { 334 | j := rand.Intn(i + 1) 335 | indices[i], indices[j] = indices[j], indices[i] 336 | } 337 | 338 | newCollection := s.NewOrdered() 339 | // Add elements to the new collection in the shuffled order 340 | for _, idx := range indices { 341 | newCollection.Add(s.At(idx)) 342 | } 343 | 344 | return newCollection 345 | } 346 | 347 | // StartsWith checks if the elements of the second collection (s2) match the 348 | // initial elements of the first collection (s1) in order. 349 | // 350 | // Example usage: 351 | // 352 | // c1 := NewSequence([]int{1, 2, 3, 4, 5}) 353 | // c2 := NewSequence([]int{1, 2}) 354 | // StartsWith(c1, c2) 355 | // 356 | // Output: 357 | // 358 | // true 359 | func StartsWith[T comparable](s1 OrderedCollection[T], s2 OrderedCollection[T]) bool { 360 | if s1.Length() < s2.Length() { 361 | return false 362 | } 363 | 364 | for i, v := range s2.All() { 365 | if v != s1.At(i) { 366 | return false 367 | } 368 | } 369 | return true 370 | } 371 | 372 | // EndsWith checks if the elements of the second collection (s2) match the 373 | // final elements of the first collection (s1) in reverse order. 374 | // 375 | // Example usage: 376 | // 377 | // c1 := NewSequence([]int{1, 2, 3, 4, 5}) 378 | // c2 := NewSequence([]int{4, 5}) 379 | // EndsWith(c1, c2) 380 | // 381 | // Output: 382 | // 383 | // true 384 | func EndsWith[T comparable](s1 OrderedCollection[T], s2 OrderedCollection[T]) bool { 385 | // If s2 is longer than s1, s1 cannot end with s2 386 | if s1.Length() < s2.Length() { 387 | return false 388 | } 389 | 390 | offset := s1.Length() - s2.Length() 391 | 392 | for i, v := range s2.All() { 393 | if s1.At(offset+i) != v { 394 | return false 395 | } 396 | } 397 | return true 398 | } 399 | -------------------------------------------------------------------------------- /list/comparable_list_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "slices" 5 | "testing" 6 | ) 7 | 8 | func TestComparableList_Contains(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | slice []int 12 | value int 13 | want bool 14 | }{ 15 | { 16 | name: "contains value", 17 | slice: []int{1, 2, 3, 4, 5}, 18 | value: 3, 19 | want: true, 20 | }, 21 | { 22 | name: "does not contain value", 23 | slice: []int{1, 2, 3, 4, 5}, 24 | value: 6, 25 | want: false, 26 | }, 27 | { 28 | name: "empty list", 29 | slice: []int{}, 30 | value: 1, 31 | want: false, 32 | }, 33 | } 34 | 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | l := NewComparableList(tt.slice) 38 | got := l.Contains(tt.value) 39 | if got != tt.want { 40 | t.Errorf("Contains() = %v, want %v", got, tt.want) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func TestComparableList_Distinct(t *testing.T) { 47 | tests := []struct { 48 | name string 49 | slice []int 50 | want []int 51 | }{ 52 | { 53 | name: "no duplicates", 54 | slice: []int{1, 2, 3, 4, 5}, 55 | want: []int{1, 2, 3, 4, 5}, 56 | }, 57 | { 58 | name: "with duplicates", 59 | slice: []int{1, 2, 2, 3, 3, 3, 4, 5, 5}, 60 | want: []int{1, 2, 3, 4, 5}, 61 | }, 62 | { 63 | name: "empty list", 64 | slice: []int{}, 65 | want: []int{}, 66 | }, 67 | } 68 | 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | l := NewComparableList(tt.slice) 72 | got := l.Distinct() 73 | if !slices.Equal(got.ToSlice(), tt.want) { 74 | t.Errorf("Distinct() = %v, want %v", got.ToSlice(), tt.want) 75 | } 76 | }) 77 | } 78 | } 79 | 80 | func TestComparableList_Diff(t *testing.T) { 81 | tests := []struct { 82 | name string 83 | slice1 []int 84 | slice2 []int 85 | want []int 86 | }{ 87 | { 88 | name: "different lists", 89 | slice1: []int{1, 2, 3, 4, 5}, 90 | slice2: []int{4, 5, 6, 7}, 91 | want: []int{1, 2, 3}, 92 | }, 93 | { 94 | name: "identical lists", 95 | slice1: []int{1, 2, 3}, 96 | slice2: []int{1, 2, 3}, 97 | want: []int{}, 98 | }, 99 | { 100 | name: "empty first list", 101 | slice1: []int{}, 102 | slice2: []int{1, 2, 3}, 103 | want: []int{}, 104 | }, 105 | { 106 | name: "empty second list", 107 | slice1: []int{1, 2, 3}, 108 | slice2: []int{}, 109 | want: []int{1, 2, 3}, 110 | }, 111 | } 112 | 113 | for _, tt := range tests { 114 | t.Run(tt.name, func(t *testing.T) { 115 | l1 := NewComparableList(tt.slice1) 116 | l2 := NewComparableList(tt.slice2) 117 | got := l1.Diff(l2) 118 | if !slices.Equal(got.ToSlice(), tt.want) { 119 | t.Errorf("Diff() = %v, want %v", got.ToSlice(), tt.want) 120 | } 121 | }) 122 | } 123 | } 124 | 125 | func TestComparableList_Equals(t *testing.T) { 126 | tests := []struct { 127 | name string 128 | slice1 []int 129 | slice2 []int 130 | want bool 131 | }{ 132 | { 133 | name: "equal lists", 134 | slice1: []int{1, 2, 3}, 135 | slice2: []int{1, 2, 3}, 136 | want: true, 137 | }, 138 | { 139 | name: "different values", 140 | slice1: []int{1, 2, 3}, 141 | slice2: []int{1, 2, 4}, 142 | want: false, 143 | }, 144 | { 145 | name: "different lengths", 146 | slice1: []int{1, 2, 3}, 147 | slice2: []int{1, 2}, 148 | want: false, 149 | }, 150 | { 151 | name: "empty lists", 152 | slice1: []int{}, 153 | slice2: []int{}, 154 | want: true, 155 | }, 156 | } 157 | 158 | for _, tt := range tests { 159 | t.Run(tt.name, func(t *testing.T) { 160 | l1 := NewComparableList(tt.slice1) 161 | l2 := NewComparableList(tt.slice2) 162 | got := l1.Equals(l2) 163 | if got != tt.want { 164 | t.Errorf("Equals() = %v, want %v", got, tt.want) 165 | } 166 | }) 167 | } 168 | } 169 | 170 | func TestComparableList_IndexOf(t *testing.T) { 171 | tests := []struct { 172 | name string 173 | slice []int 174 | value int 175 | want int 176 | }{ 177 | { 178 | name: "value exists", 179 | slice: []int{1, 2, 3, 4, 5}, 180 | value: 3, 181 | want: 2, 182 | }, 183 | { 184 | name: "value does not exist", 185 | slice: []int{1, 2, 3, 4, 5}, 186 | value: 6, 187 | want: -1, 188 | }, 189 | { 190 | name: "empty list", 191 | slice: []int{}, 192 | value: 1, 193 | want: -1, 194 | }, 195 | } 196 | 197 | for _, tt := range tests { 198 | t.Run(tt.name, func(t *testing.T) { 199 | l := NewComparableList(tt.slice) 200 | got := l.IndexOf(tt.value) 201 | if got != tt.want { 202 | t.Errorf("IndexOf() = %v, want %v", got, tt.want) 203 | } 204 | }) 205 | } 206 | } 207 | 208 | func TestComparableList_LastIndexOf(t *testing.T) { 209 | tests := []struct { 210 | name string 211 | slice []int 212 | value int 213 | want int 214 | }{ 215 | { 216 | name: "value exists once", 217 | slice: []int{1, 2, 3, 4, 5}, 218 | value: 3, 219 | want: 2, 220 | }, 221 | { 222 | name: "value exists multiple times", 223 | slice: []int{1, 2, 3, 2, 4}, 224 | value: 2, 225 | want: 3, 226 | }, 227 | { 228 | name: "value does not exist", 229 | slice: []int{1, 2, 3, 4, 5}, 230 | value: 6, 231 | want: -1, 232 | }, 233 | { 234 | name: "empty list", 235 | slice: []int{}, 236 | value: 1, 237 | want: -1, 238 | }, 239 | } 240 | 241 | for _, tt := range tests { 242 | t.Run(tt.name, func(t *testing.T) { 243 | l := NewComparableList(tt.slice) 244 | got := l.LastIndexOf(tt.value) 245 | if got != tt.want { 246 | t.Errorf("LastIndexOf() = %v, want %v", got, tt.want) 247 | } 248 | }) 249 | } 250 | } 251 | 252 | func TestComparableList_Max(t *testing.T) { 253 | tests := []struct { 254 | name string 255 | slice []int 256 | want int 257 | wantErr bool 258 | }{ 259 | { 260 | name: "non-empty list", 261 | slice: []int{1, 5, 3, 4, 2}, 262 | want: 5, 263 | wantErr: false, 264 | }, 265 | { 266 | name: "single element", 267 | slice: []int{1}, 268 | want: 1, 269 | wantErr: false, 270 | }, 271 | { 272 | name: "empty list", 273 | slice: []int{}, 274 | want: 0, 275 | wantErr: true, 276 | }, 277 | } 278 | 279 | for _, tt := range tests { 280 | t.Run(tt.name, func(t *testing.T) { 281 | l := NewComparableList(tt.slice) 282 | got, err := l.Max() 283 | if tt.wantErr { 284 | if err == nil { 285 | t.Errorf("Max() = %v, want error", got) 286 | } 287 | } else { 288 | if err != nil { 289 | t.Errorf("Max() = %v, want no error", got) 290 | } 291 | if got != tt.want { 292 | t.Errorf("Max() = %v, want %v", got, tt.want) 293 | } 294 | } 295 | }) 296 | } 297 | } 298 | 299 | func TestComparableList_Min(t *testing.T) { 300 | tests := []struct { 301 | name string 302 | slice []int 303 | want int 304 | wantErr bool 305 | }{ 306 | { 307 | name: "non-empty list", 308 | slice: []int{5, 1, 3, 4, 2}, 309 | want: 1, 310 | wantErr: false, 311 | }, 312 | { 313 | name: "single element", 314 | slice: []int{1}, 315 | want: 1, 316 | wantErr: false, 317 | }, 318 | { 319 | name: "empty list", 320 | slice: []int{}, 321 | want: 0, 322 | wantErr: true, 323 | }, 324 | } 325 | 326 | for _, tt := range tests { 327 | t.Run(tt.name, func(t *testing.T) { 328 | l := NewComparableList(tt.slice) 329 | got, err := l.Min() 330 | if tt.wantErr { 331 | if err == nil { 332 | t.Errorf("Min() = %v, want error", got) 333 | } 334 | } else { 335 | if err != nil { 336 | t.Errorf("Min() = %v, want no error", got) 337 | } 338 | if got != tt.want { 339 | t.Errorf("Min() = %v, want %v", got, tt.want) 340 | } 341 | } 342 | }) 343 | } 344 | } 345 | 346 | func TestComparableList_Sum(t *testing.T) { 347 | tests := []struct { 348 | name string 349 | slice []int 350 | want int 351 | }{ 352 | { 353 | name: "non-empty list", 354 | slice: []int{1, 2, 3, 4, 5}, 355 | want: 15, 356 | }, 357 | { 358 | name: "single element", 359 | slice: []int{5}, 360 | want: 5, 361 | }, 362 | { 363 | name: "empty list", 364 | slice: []int{}, 365 | want: 0, 366 | }, 367 | } 368 | 369 | for _, tt := range tests { 370 | t.Run(tt.name, func(t *testing.T) { 371 | l := NewComparableList(tt.slice) 372 | got := l.Sum() 373 | if got != tt.want { 374 | t.Errorf("Sum() = %v, want %v", got, tt.want) 375 | } 376 | }) 377 | } 378 | } 379 | 380 | func TestComparableList_StartsWith(t *testing.T) { 381 | tests := []struct { 382 | name string 383 | list1 []int 384 | list2 []int 385 | startsWith bool 386 | }{ 387 | { 388 | name: "starts with matching elements", 389 | list1: []int{1, 2, 3, 4}, 390 | list2: []int{1, 2}, 391 | startsWith: true, 392 | }, 393 | { 394 | name: "does not start with different elements", 395 | list1: []int{1, 2, 3, 4}, 396 | list2: []int{2, 3}, 397 | startsWith: false, 398 | }, 399 | { 400 | name: "empty list2 (always true)", 401 | list1: []int{1, 2, 3, 4}, 402 | list2: []int{}, 403 | startsWith: true, 404 | }, 405 | { 406 | name: "list1 shorter than list2", 407 | list1: []int{1, 2}, 408 | list2: []int{1, 2, 3}, 409 | startsWith: false, 410 | }, 411 | { 412 | name: "both lists empty", 413 | list1: []int{}, 414 | list2: []int{}, 415 | startsWith: true, 416 | }, 417 | } 418 | 419 | for _, tt := range tests { 420 | t.Run(tt.name, func(t *testing.T) { 421 | l1 := NewComparableList(tt.list1) 422 | l2 := NewComparableList(tt.list2) 423 | if got := l1.StartsWith(l2); got != tt.startsWith { 424 | t.Errorf("StartsWith() = %v, want %v", got, tt.startsWith) 425 | } 426 | }) 427 | } 428 | } 429 | 430 | func TestComparableList_EndsWith(t *testing.T) { 431 | tests := []struct { 432 | name string 433 | list1 []int 434 | list2 []int 435 | endsWith bool 436 | }{ 437 | { 438 | name: "ends with matching elements", 439 | list1: []int{1, 2, 3, 4}, 440 | list2: []int{3, 4}, 441 | endsWith: true, 442 | }, 443 | { 444 | name: "does not end with different elements", 445 | list1: []int{1, 2, 3, 4}, 446 | list2: []int{2, 3}, 447 | endsWith: false, 448 | }, 449 | { 450 | name: "empty list2 (always true)", 451 | list1: []int{1, 2, 3, 4}, 452 | list2: []int{}, 453 | endsWith: true, 454 | }, 455 | { 456 | name: "list1 shorter than list2", 457 | list1: []int{3, 4}, 458 | list2: []int{2, 3, 4}, 459 | endsWith: false, 460 | }, 461 | { 462 | name: "both lists empty", 463 | list1: []int{}, 464 | list2: []int{}, 465 | endsWith: true, 466 | }, 467 | } 468 | 469 | for _, tt := range tests { 470 | t.Run(tt.name, func(t *testing.T) { 471 | l1 := NewComparableList(tt.list1) 472 | l2 := NewComparableList(tt.list2) 473 | if got := l1.EndsWith(l2); got != tt.endsWith { 474 | t.Errorf("EndsWith() = %v, want %v", got, tt.endsWith) 475 | } 476 | }) 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /sequence/sequence.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package sequence implements support for a generic ordered sequence. 6 | // A Sequence is a Collection that wraps an underlying Go slice and provides 7 | // convenience methods and syntatic sugar on top of it. 8 | // 9 | // Compared to a List, a Sequence allows for efficient O(1) access to arbitrary elements 10 | // but slower insertion and removal time, making it ideal for situations where fast random access is needed. 11 | // 12 | // for comparable types it is recommended to use 13 | // ComparableSequence which provides additional methods 14 | package sequence 15 | 16 | import ( 17 | "fmt" 18 | "iter" 19 | "math/rand" 20 | "slices" 21 | 22 | "github.com/charbz/gophers/collection" 23 | ) 24 | 25 | type Sequence[T any] struct { 26 | elements []T 27 | } 28 | 29 | func NewSequence[T any](s ...[]T) *Sequence[T] { 30 | seq := new(Sequence[T]) 31 | if len(s) == 0 { 32 | return seq 33 | } 34 | return &Sequence[T]{elements: slices.Concat(s...)} 35 | } 36 | 37 | // The following methods implement 38 | // the Collection interface. 39 | 40 | // Add appends an element to the sequence. 41 | func (c *Sequence[T]) Add(v T) { 42 | c.elements = append(c.elements, v) 43 | } 44 | 45 | // Length returns the number of elements in the sequence. 46 | func (c *Sequence[T]) Length() int { 47 | return len(c.elements) 48 | } 49 | 50 | // New is a constructor for a generic sequence. 51 | func (c *Sequence[T]) New(s ...[]T) collection.Collection[T] { 52 | return NewSequence(s...) 53 | } 54 | 55 | // Random returns a random element from the sequence. 56 | func (c *Sequence[T]) Random() T { 57 | if len(c.elements) == 0 { 58 | return *new(T) 59 | } 60 | return c.elements[rand.Intn(len(c.elements))] 61 | } 62 | 63 | // Values returns an iterator over all values of the underlying slice. 64 | func (c *Sequence[T]) Values() iter.Seq[T] { 65 | return slices.Values(c.elements) 66 | } 67 | 68 | // The following methods implement 69 | // the OrderedCollection interface. 70 | 71 | // At returns the element at the given index. 72 | func (c *Sequence[T]) At(index int) T { 73 | if index < 0 || index >= len(c.elements) { 74 | panic(collection.IndexOutOfBoundsError) 75 | } 76 | return c.elements[index] 77 | } 78 | 79 | // All returns an iterator over all elements of the sequence. 80 | func (c *Sequence[T]) All() iter.Seq2[int, T] { 81 | return slices.All(c.elements) 82 | } 83 | 84 | // Backward returns an iterator over all elements of the sequence in reverse order. 85 | func (c *Sequence[T]) Backward() iter.Seq2[int, T] { 86 | return slices.Backward(c.elements) 87 | } 88 | 89 | // Slice returns a new sequence containing the elements from the start index to the end index. 90 | func (c *Sequence[T]) Slice(start, end int) collection.OrderedCollection[T] { 91 | return &Sequence[T]{ 92 | c.elements[start:end], 93 | } 94 | } 95 | 96 | // NewOrdered returns a new ordered collection. 97 | func (c *Sequence[T]) NewOrdered(s ...[]T) collection.OrderedCollection[T] { 98 | return NewSequence(s...) 99 | } 100 | 101 | // Apply applies a function to each element in the sequence. 102 | func (c *Sequence[T]) Apply(f func(T) T) *Sequence[T] { 103 | for i := range c.elements { 104 | c.elements[i] = f(c.elements[i]) 105 | } 106 | return c 107 | } 108 | 109 | // The following methods are mostly syntatic sugar 110 | // wrapping Collection functions to enable function chaining: 111 | // i.e. sequence.Filter(f).Take(n) 112 | 113 | // Clone returns a copy of the collection. This is a shallow clone. 114 | func (c *Sequence[T]) Clone() *Sequence[T] { 115 | return &Sequence[T]{ 116 | slices.Clone(c.elements), 117 | } 118 | } 119 | 120 | // Count is an alias for collection.Count 121 | func (c *Sequence[T]) Count(f func(T) bool) int { 122 | return collection.Count(c, f) 123 | } 124 | 125 | // Concat returns a new sequence concatenating the passed in sequences. 126 | func (c *Sequence[T]) Concat(sequences ...Sequence[T]) *Sequence[T] { 127 | e := c.elements 128 | for _, col := range sequences { 129 | e = slices.Concat(e, col.elements) 130 | } 131 | return &Sequence[T]{e} 132 | } 133 | 134 | // Concatenated is an alias for collection.Concatenated 135 | func (c *Sequence[T]) Concatenated(s *Sequence[T]) iter.Seq[T] { 136 | return collection.Concatenated(c, s) 137 | } 138 | 139 | // Contains tests whether a predicate holds for at least one element of this sequence. 140 | func (c *Sequence[T]) Contains(f func(T) bool) bool { 141 | i, _ := collection.Find(c, f) 142 | return i > -1 143 | } 144 | 145 | // Corresponds is an alias for collection.Corresponds 146 | func (c *Sequence[T]) Corresponds(s *Sequence[T], f func(T, T) bool) bool { 147 | return collection.Corresponds(c, s, f) 148 | } 149 | 150 | // Dequeue removes and returns the first element of the sequence. 151 | func (c *Sequence[T]) Dequeue() (T, error) { 152 | if len(c.elements) == 0 { 153 | return *new(T), collection.EmptyCollectionError 154 | } 155 | element := c.elements[0] 156 | c.elements = c.elements[1:] 157 | return element, nil 158 | } 159 | 160 | // Diff is an alias for collection.Diff 161 | func (c *Sequence[T]) Diff(s *Sequence[T], f func(T, T) bool) *Sequence[T] { 162 | return collection.DiffFunc(c, s, f).(*Sequence[T]) 163 | } 164 | 165 | // Diffed is an alias for collection.Diffed 166 | func (c *Sequence[T]) Diffed(s *Sequence[T], f func(T, T) bool) iter.Seq[T] { 167 | return collection.DiffedFunc(c, s, f) 168 | } 169 | 170 | // Distinct takes an "equality" function as an argument 171 | // and returns a new sequence containing all the unique elements 172 | // If you prefer not to pass an equality function use a ComparableSequence. 173 | func (c *Sequence[T]) Distinct(f func(T, T) bool) *Sequence[T] { 174 | return collection.Distinct(c, f).(*Sequence[T]) 175 | } 176 | 177 | // DistinctIterator is an alias for collection.DistinctIterator 178 | func (c *Sequence[T]) Distincted(f func(T, T) bool) iter.Seq[T] { 179 | return collection.DistinctedFunc(c, f) 180 | } 181 | 182 | // Drop is an alias for collection.Drop 183 | func (c *Sequence[T]) Drop(n int) *Sequence[T] { 184 | return collection.Drop(c, n).(*Sequence[T]) 185 | } 186 | 187 | // DropWhile is an alias for collection.DropWhile 188 | func (c *Sequence[T]) DropWhile(f func(T) bool) *Sequence[T] { 189 | return collection.DropWhile(c, f).(*Sequence[T]) 190 | } 191 | 192 | // DropRight is an alias for collection.DropRight 193 | func (c *Sequence[T]) DropRight(n int) *Sequence[T] { 194 | return collection.DropRight(c, n).(*Sequence[T]) 195 | } 196 | 197 | // Enqueue appends an element to the sequence. 198 | func (c *Sequence[T]) Enqueue(v T) { 199 | c.elements = append(c.elements, v) 200 | } 201 | 202 | // Equals takes a sequence and an equality function as an argument 203 | // and returns true if the two sequences are equal. 204 | // If you prefer not to pass an equality function use a ComparableSequence. 205 | func (c *Sequence[T]) Equals(c2 *Sequence[T], f func(T, T) bool) bool { 206 | return slices.EqualFunc(c.elements, c2.elements, f) 207 | } 208 | 209 | // Exists is an alias for Contains 210 | func (c *Sequence[T]) Exists(f func(T) bool) bool { 211 | return c.Contains(f) 212 | } 213 | 214 | // Filter is an alias for collection.Filter 215 | func (c *Sequence[T]) Filter(f func(T) bool) *Sequence[T] { 216 | return collection.Filter(c, f).(*Sequence[T]) 217 | } 218 | 219 | // FilterIterator is an alias for collection.FilterIterator 220 | func (c *Sequence[T]) Filtered(f func(T) bool) iter.Seq[T] { 221 | return collection.Filtered(c, f) 222 | } 223 | 224 | // FilterNot is an alias for collection.FilterNot 225 | func (c *Sequence[T]) FilterNot(f func(T) bool) *Sequence[T] { 226 | return collection.FilterNot(c, f).(*Sequence[T]) 227 | } 228 | 229 | // Find is an alias for collection.Find 230 | func (c *Sequence[T]) Find(f func(T) bool) (int, T) { 231 | return collection.Find(c, f) 232 | } 233 | 234 | // FindLast is an alias for collection.FindLast 235 | func (c *Sequence[T]) FindLast(f func(T) bool) (int, T) { 236 | return collection.FindLast(c, f) 237 | } 238 | 239 | // ForAll is an alias for collection.ForAll 240 | func (c *Sequence[T]) ForAll(f func(T) bool) bool { 241 | return collection.ForAll(c, f) 242 | } 243 | 244 | // Head is an alias for collection.Head 245 | func (c *Sequence[T]) Head() (T, error) { 246 | return collection.Head(c) 247 | } 248 | 249 | // Init is an alias for collection.Init 250 | func (c *Sequence[T]) Init() *Sequence[T] { 251 | return collection.Init(c).(*Sequence[T]) 252 | } 253 | 254 | // Intersect is an alias for collection.Intersect 255 | func (c *Sequence[T]) Intersect(s *Sequence[T], f func(T, T) bool) *Sequence[T] { 256 | return collection.IntersectFunc(c, s, f).(*Sequence[T]) 257 | } 258 | 259 | // IntersectIterator is an alias for collection.IntersectIterator 260 | func (c *Sequence[T]) Intersected(s *Sequence[T], f func(T, T) bool) iter.Seq[T] { 261 | return collection.IntersectedFunc(c, s, f) 262 | } 263 | 264 | // IsEmpty returns true if the sequence is empty. 265 | func (c *Sequence[T]) IsEmpty() bool { 266 | return len(c.elements) == 0 267 | } 268 | 269 | // Last is an alias for collection.Last 270 | func (c *Sequence[T]) Last() (T, error) { 271 | return collection.Last(c) 272 | } 273 | 274 | // returns true if the sequence is not empty. 275 | func (c *Sequence[T]) NonEmpty() bool { 276 | return len(c.elements) > 0 277 | } 278 | 279 | // Pop removes and returns the last element of the sequence. 280 | func (c *Sequence[T]) Pop() (T, error) { 281 | if len(c.elements) == 0 { 282 | return *new(T), collection.EmptyCollectionError 283 | } 284 | element := c.elements[len(c.elements)-1] 285 | c.elements = c.elements[:len(c.elements)-1] 286 | return element, nil 287 | } 288 | 289 | // Push appends an element to the sequence. 290 | func (c *Sequence[T]) Push(v T) { 291 | c.elements = append(c.elements, v) 292 | } 293 | 294 | // Partition is an alias for collection.Partition 295 | func (c *Sequence[T]) Partition(f func(T) bool) (*Sequence[T], *Sequence[T]) { 296 | left, right := collection.Partition(c, f) 297 | return left.(*Sequence[T]), right.(*Sequence[T]) 298 | } 299 | 300 | // SplitAt splits the sequence at the given index. 301 | func (c *Sequence[T]) SplitAt(n int) (*Sequence[T], *Sequence[T]) { 302 | left := NewSequence(c.elements[:n+1]) 303 | right := NewSequence(c.elements[n+1:]) 304 | return left, right 305 | } 306 | 307 | // Reverse is an alias for collection.Reverse 308 | func (c *Sequence[T]) Reverse() *Sequence[T] { 309 | return collection.Reverse(c).(*Sequence[T]) 310 | } 311 | 312 | // Reject is an alias for collection.FilterNot 313 | func (l *Sequence[T]) Reject(f func(T) bool) *Sequence[T] { 314 | return collection.FilterNot(l, f).(*Sequence[T]) 315 | } 316 | 317 | // Rejected is an alias for collection.Rejected 318 | func (c *Sequence[T]) Rejected(f func(T) bool) iter.Seq[T] { 319 | return collection.Rejected(c, f) 320 | } 321 | 322 | // String implements the Stringer interface. 323 | func (c *Sequence[T]) String() string { 324 | return fmt.Sprintf("Seq(%T) %v", *new(T), c.elements) 325 | } 326 | 327 | // Take is an alias for collection.Take 328 | func (c *Sequence[T]) Take(n int) *Sequence[T] { 329 | return collection.Take(c, n).(*Sequence[T]) 330 | } 331 | 332 | // TakeRight is an alias for collection.TakeRight 333 | func (c *Sequence[T]) TakeRight(n int) *Sequence[T] { 334 | return collection.TakeRight(c, n).(*Sequence[T]) 335 | } 336 | 337 | // Tail is an alias for collection.Tail 338 | func (c *Sequence[T]) Tail() *Sequence[T] { 339 | return collection.Tail(c).(*Sequence[T]) 340 | } 341 | 342 | // ToSlice returns the underlying slice. 343 | func (c *Sequence[T]) ToSlice() []T { 344 | return c.elements 345 | } 346 | 347 | func (c *Sequence[T]) Shuffle() *Sequence[T] { 348 | return collection.Shuffle(c).(*Sequence[T]) 349 | } 350 | -------------------------------------------------------------------------------- /list/list.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Gophers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package list implements support for a generic ordered List. 6 | // A List is a Collection that wraps an underlying doubly linked list 7 | // and provides convenience methods and syntatic sugar on top of it. 8 | // 9 | // Compared to a Sequence, a List allows for efficient insertion and removal of elements 10 | // at the edges of the list, but slower access to arbitrary elements. This makes the List a good choice 11 | // for implementing queues or stacks. 12 | // 13 | // For a list of comparable types, consider using ComparableList, 14 | // which provides additional methods for comparable types. 15 | package list 16 | 17 | import ( 18 | "fmt" 19 | "iter" 20 | "math/rand" 21 | 22 | "github.com/charbz/gophers/collection" 23 | ) 24 | 25 | type Node[T any] struct { 26 | value T 27 | next *Node[T] 28 | prev *Node[T] 29 | } 30 | 31 | type List[T any] struct { 32 | head *Node[T] 33 | tail *Node[T] 34 | size int 35 | } 36 | 37 | func NewList[T any](s ...[]T) *List[T] { 38 | list := new(List[T]) 39 | if len(s) == 0 { 40 | return list 41 | } 42 | for _, slice := range s { 43 | for _, v := range slice { 44 | list.Add(v) 45 | } 46 | } 47 | return list 48 | } 49 | 50 | // The following methods implement 51 | // the Collection interface. 52 | 53 | // Add adds a value to the end of the list. 54 | func (l *List[T]) Add(v T) { 55 | node := &Node[T]{value: v} 56 | if l.head == nil { 57 | l.head = node 58 | l.tail = node 59 | } else { 60 | l.tail.next = node 61 | node.prev = l.tail 62 | l.tail = node 63 | } 64 | l.size++ 65 | } 66 | 67 | // Length returns the number of nodes in the list. 68 | func (l *List[T]) Length() int { 69 | return l.size 70 | } 71 | 72 | // New returns a new list. 73 | func (l *List[T]) New(s ...[]T) collection.Collection[T] { 74 | return NewList(s...) 75 | } 76 | 77 | // Random returns a random value from the list. 78 | func (l *List[T]) Random() T { 79 | if l.size == 0 { 80 | return *new(T) 81 | } 82 | return l.At(rand.Intn(l.size)) 83 | } 84 | 85 | // Values returns an iterator for all values in the list. 86 | func (l *List[T]) Values() iter.Seq[T] { 87 | return func(yield func(T) bool) { 88 | for node := l.head; node != nil; node = node.next { 89 | if !yield(node.value) { 90 | break 91 | } 92 | } 93 | } 94 | } 95 | 96 | // The following methods implement 97 | // the OrderedCollection interface. 98 | 99 | // At returns the value of the node at the given index. 100 | func (l *List[T]) At(index int) T { 101 | if index < 0 || index >= l.size { 102 | panic(collection.IndexOutOfBoundsError) 103 | } 104 | node := l.head 105 | for i := 0; i < index; i++ { 106 | node = node.next 107 | } 108 | return node.value 109 | } 110 | 111 | // All returns an index/value iterator for all nodes in the list. 112 | func (l *List[T]) All() iter.Seq2[int, T] { 113 | return func(yield func(int, T) bool) { 114 | i := 0 115 | for node := l.head; node != nil; node = node.next { 116 | if !yield(i, node.value) { 117 | break 118 | } 119 | i++ 120 | } 121 | } 122 | } 123 | 124 | // Backward returns an index/value iterator for all nodes in the list in reverse order. 125 | func (l *List[T]) Backward() iter.Seq2[int, T] { 126 | return func(yield func(int, T) bool) { 127 | i := l.size - 1 128 | for node := l.tail; node != nil; node = node.prev { 129 | if !yield(i, node.value) { 130 | break 131 | } 132 | i-- 133 | } 134 | } 135 | } 136 | 137 | // Slice returns a new list containing only the nodes between the start and end indices. 138 | func (l *List[T]) Slice(start, end int) collection.OrderedCollection[T] { 139 | if start < 0 || end > l.size || start > end { 140 | panic(collection.IndexOutOfBoundsError) 141 | } 142 | list := &List[T]{} 143 | for i, v := range l.All() { 144 | if i < start { 145 | continue 146 | } 147 | if i >= start && i < end { 148 | list.Add(v) 149 | } 150 | if i >= end { 151 | break 152 | } 153 | } 154 | return list 155 | } 156 | 157 | // NewOrdered returns a new ordered collection. 158 | func (l *List[T]) NewOrdered(s ...[]T) collection.OrderedCollection[T] { 159 | return NewList(s...) 160 | } 161 | 162 | // ToSlice returns a slice containing all values in the list. 163 | func (l *List[T]) ToSlice() []T { 164 | slice := make([]T, 0, l.size) 165 | for v := range l.Values() { 166 | slice = append(slice, v) 167 | } 168 | return slice 169 | } 170 | 171 | // Implement the Stringer interface. 172 | func (l *List[T]) String() string { 173 | return fmt.Sprintf("List(%T) %v", *new(T), l.ToSlice()) 174 | } 175 | 176 | // The following methods are specific to the List type. 177 | // most of them are aliases for Collection Functions, 178 | // the reason for defining them here is to provide a more 179 | // idiomatic API for working with lists, enabling method chaining. 180 | 181 | // Apply applies a function to each element in the list. 182 | func (l *List[T]) Apply(f func(T) T) *List[T] { 183 | for node := l.head; node != nil; node = node.next { 184 | node.value = f(node.value) 185 | } 186 | return l 187 | } 188 | 189 | // Clone returns a copy of the list. This is a shallow clone. 190 | func (l *List[T]) Clone() *List[T] { 191 | clone := &List[T]{} 192 | for v := range l.Values() { 193 | clone.Add(v) 194 | } 195 | return clone 196 | } 197 | 198 | // Count is an alias for collection.Count 199 | func (l *List[T]) Count(f func(T) bool) int { 200 | return collection.Count(l, f) 201 | } 202 | 203 | // Concat returns a new list concatenating the passed in lists. 204 | func (l *List[T]) Concat(lists ...*List[T]) *List[T] { 205 | clone := l.Clone() 206 | for _, list := range lists { 207 | for v := range list.Values() { 208 | clone.Add(v) 209 | } 210 | } 211 | return clone 212 | } 213 | 214 | // Concatenated is an alias for collection.Concatenated 215 | func (l *List[T]) Concatenated(l2 *List[T]) iter.Seq[T] { 216 | return collection.Concatenated(l, l2) 217 | } 218 | 219 | // Contains tests whether a predicate holds for at least one element of this list. 220 | func (l *List[T]) Contains(f func(T) bool) bool { 221 | i, _ := collection.Find(l, f) 222 | return i > -1 223 | } 224 | 225 | // Corresponds is an alias for collection.Corresponds 226 | func (l *List[T]) Corresponds(s *List[T], f func(T, T) bool) bool { 227 | return collection.Corresponds(l, s, f) 228 | } 229 | 230 | // Dequeue removes and returns the first element of the list. 231 | func (l *List[T]) Dequeue() (T, error) { 232 | if l.size == 0 { 233 | return *new(T), collection.EmptyCollectionError 234 | } 235 | element := l.head.value 236 | l.head = l.head.next 237 | l.size-- 238 | return element, nil 239 | } 240 | 241 | // Diff is an alias for collection.DiffFunc 242 | func (l *List[T]) Diff(s *List[T], f func(T, T) bool) *List[T] { 243 | return collection.DiffFunc(l, s, f).(*List[T]) 244 | } 245 | 246 | // Diffed is an alias for collection.Diffed 247 | func (l *List[T]) Diffed(s *List[T], f func(T, T) bool) iter.Seq[T] { 248 | return collection.DiffedFunc(l, s, f) 249 | } 250 | 251 | // Distinct takes an "equality" function as an argument such as 252 | // func(a T, b T) bool {return a == b} 253 | // and returns a new sequence containing all the unique elements. 254 | // If you don't want to pass an equality function use a ComparableList. 255 | func (l *List[T]) Distinct(f func(T, T) bool) *List[T] { 256 | return collection.Distinct(l, f).(*List[T]) 257 | } 258 | 259 | // Distincted is an alias for collection.Distincted 260 | func (l *List[T]) Distincted(f func(T, T) bool) iter.Seq[T] { 261 | return collection.DistinctedFunc(l, f) 262 | } 263 | 264 | // Drop is an alias for collection.Drop 265 | func (l *List[T]) Drop(n int) *List[T] { 266 | return collection.Drop(l, n).(*List[T]) 267 | } 268 | 269 | // DropWhile is an alias for collection.DropWhile 270 | func (l *List[T]) DropWhile(f func(T) bool) *List[T] { 271 | return collection.DropWhile(l, f).(*List[T]) 272 | } 273 | 274 | // DropRight is an alias for collection.DropRight 275 | func (l *List[T]) DropRight(n int) *List[T] { 276 | return collection.DropRight(l, n).(*List[T]) 277 | } 278 | 279 | // Enqueue appends an element to the list. 280 | func (l *List[T]) Enqueue(v T) { 281 | l.Add(v) 282 | } 283 | 284 | // Equals takes a list and an equality function as arguments 285 | // and returns true if the two sequences are equal. 286 | // If you prefer not to pass an equality function use a ComparableList. 287 | func (l *List[T]) Equals(s *List[T], f func(T, T) bool) bool { 288 | if l.size != s.size { 289 | return false 290 | } 291 | n1 := l.head 292 | n2 := s.head 293 | for n1 != nil && n2 != nil { 294 | if !f(n1.value, n2.value) { 295 | return false 296 | } 297 | n1 = n1.next 298 | n2 = n2.next 299 | } 300 | return true 301 | } 302 | 303 | // Exists is an alias for Contains 304 | func (l *List[T]) Exists(f func(T) bool) bool { 305 | return l.Contains(f) 306 | } 307 | 308 | // Filter is an alias for collection.Filter 309 | func (l *List[T]) Filter(f func(T) bool) *List[T] { 310 | return collection.Filter(l, f).(*List[T]) 311 | } 312 | 313 | // Filtered is an alias for collection.Filtered 314 | func (l *List[T]) Filtered(f func(T) bool) iter.Seq[T] { 315 | return collection.Filtered(l, f) 316 | } 317 | 318 | // FilterNot is an alias for collection.FilterNot 319 | func (l *List[T]) FilterNot(f func(T) bool) *List[T] { 320 | return collection.FilterNot(l, f).(*List[T]) 321 | } 322 | 323 | // Find is an alias for collection.Find 324 | func (l *List[T]) Find(f func(T) bool) (int, T) { 325 | return collection.Find(l, f) 326 | } 327 | 328 | // FindLast is an alias for collection.FindLast 329 | func (l *List[T]) FindLast(f func(T) bool) (int, T) { 330 | return collection.FindLast(l, f) 331 | } 332 | 333 | // ForAll is an alias for collection.ForAll 334 | func (l *List[T]) ForAll(f func(T) bool) bool { 335 | return collection.ForAll(l, f) 336 | } 337 | 338 | // Head is an alias for collection.Head 339 | func (l *List[T]) Head() (T, error) { 340 | return collection.Head(l) 341 | } 342 | 343 | // Init is an alias for collection.Init 344 | func (l *List[T]) Init() *List[T] { 345 | return collection.Init(l).(*List[T]) 346 | } 347 | 348 | // Intersect is an alias for collection.IntersectFunc 349 | func (l *List[T]) Intersect(s *List[T], f func(T, T) bool) *List[T] { 350 | return collection.IntersectFunc(l, s, f).(*List[T]) 351 | } 352 | 353 | // Intersected is an alias for collection.Intersected 354 | func (l *List[T]) Intersected(s *List[T], f func(T, T) bool) iter.Seq[T] { 355 | return collection.IntersectedFunc(l, s, f) 356 | } 357 | 358 | // IsEmpty returns true if the list is empty. 359 | func (l *List[T]) IsEmpty() bool { 360 | return l.size == 0 361 | } 362 | 363 | // Last is an alias for collection.Last 364 | func (l *List[T]) Last() (T, error) { 365 | return collection.Last(l) 366 | } 367 | 368 | // NonEmpty returns true if the list is not empty. 369 | func (l *List[T]) NonEmpty() bool { 370 | return l.size > 0 371 | } 372 | 373 | // Pop removes and returns the last element of the list. 374 | func (l *List[T]) Pop() (T, error) { 375 | if l.size == 0 { 376 | return *new(T), collection.EmptyCollectionError 377 | } 378 | element := l.tail.value 379 | l.tail = l.tail.prev 380 | l.size-- 381 | return element, nil 382 | } 383 | 384 | // Push appends an element to the list. 385 | func (l *List[T]) Push(v T) { 386 | l.Add(v) 387 | } 388 | 389 | // Partition is an alias for collection.Partition 390 | func (l *List[T]) Partition(f func(T) bool) (*List[T], *List[T]) { 391 | left, right := collection.Partition(l, f) 392 | return left.(*List[T]), right.(*List[T]) 393 | } 394 | 395 | // SplitAt splits the list at the given index. 396 | func (l *List[T]) SplitAt(n int) (*List[T], *List[T]) { 397 | left := NewList[T]() 398 | right := NewList[T]() 399 | for i, v := range l.All() { 400 | if i <= n { 401 | left.Add(v) 402 | } else { 403 | right.Add(v) 404 | } 405 | } 406 | return left, right 407 | } 408 | 409 | // Reverse is an alias for collection.Reverse 410 | func (l *List[T]) Reverse() *List[T] { 411 | return collection.Reverse(l).(*List[T]) 412 | } 413 | 414 | func (l *List[T]) Shuffle() *List[T] { 415 | return collection.Shuffle(l).(*List[T]) 416 | } 417 | 418 | // Reject is an alias for collection.FilterNot 419 | func (l *List[T]) Reject(f func(T) bool) *List[T] { 420 | return collection.FilterNot(l, f).(*List[T]) 421 | } 422 | 423 | // Rejected is an alias for collection.Rejected 424 | func (l *List[T]) Rejected(f func(T) bool) iter.Seq[T] { 425 | return collection.Rejected(l, f) 426 | } 427 | 428 | // Take is an alias for collection.Take 429 | func (l *List[T]) Take(n int) *List[T] { 430 | return collection.Take(l, n).(*List[T]) 431 | } 432 | 433 | // TakeRight is an alias for collection.TakeRight 434 | func (l *List[T]) TakeRight(n int) *List[T] { 435 | return collection.TakeRight(l, n).(*List[T]) 436 | } 437 | 438 | // Tail is an alias for collection.Tail 439 | func (l *List[T]) Tail() *List[T] { 440 | return collection.Tail(l).(*List[T]) 441 | } 442 | -------------------------------------------------------------------------------- /collection/functions_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "slices" 5 | "testing" 6 | ) 7 | 8 | func TestCount(t *testing.T) { 9 | countEvens := func(n int) bool { return n%2 == 0 } 10 | tests := []struct { 11 | name string 12 | input []int 13 | count int 14 | }{ 15 | {name: "count evens", input: []int{1, 2, 3, 4, 5, 6}, count: 3}, 16 | {name: "count evens", input: []int{1, 3, 5}, count: 0}, 17 | {name: "count evens", input: []int{2}, count: 1}, 18 | {name: "count evens", input: []int{}, count: 0}, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | if got := Count(NewMockCollection(tt.input), countEvens); got != tt.count { 23 | t.Errorf("Count() = %v, want %v", got, tt.count) 24 | } 25 | }) 26 | } 27 | } 28 | 29 | func TestDiff(t *testing.T) { 30 | tests := []struct { 31 | name string 32 | A []int 33 | B []int 34 | diff []int 35 | }{ 36 | {name: "diff", A: []int{1, 2, 3, 4, 5, 6}, B: []int{2, 4, 6, 8, 10, 12}, diff: []int{1, 3, 5}}, 37 | {name: "diff with empty B", A: []int{1, 2, 3, 4, 5, 6}, B: []int{}, diff: []int{1, 2, 3, 4, 5, 6}}, 38 | {name: "diff with empty A", A: []int{}, B: []int{1, 2, 3, 4, 5, 6}, diff: nil}, 39 | {name: "diff with same elements", A: []int{1, 2, 3, 4, 3, 6}, B: []int{1, 2, 3, 4, 5, 6}, diff: nil}, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | got := Diff(NewMockCollection(tt.A), NewMockCollection(tt.B)).(*MockCollection[int]).items 44 | want := NewMockCollection(tt.diff).items 45 | if !slices.Equal(got, want) { 46 | t.Errorf("Diff() = %v, want %v", got, want) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | func TestDiffFunc(t *testing.T) { 53 | tests := []struct { 54 | name string 55 | a []int 56 | b []int 57 | diff []int 58 | }{ 59 | {name: "diff", a: []int{1, 2, 3, 4, 5, 6}, b: []int{2, 4, 6, 8, 10, 12}, diff: []int{1, 3, 5}}, 60 | {name: "diff with empty b", a: []int{1, 2, 3, 4, 5, 6}, b: []int{}, diff: []int{1, 2, 3, 4, 5, 6}}, 61 | {name: "diff with empty a", a: []int{}, b: []int{1, 2, 3, 4, 5, 6}, diff: nil}, 62 | {name: "diff with same elements", a: []int{1, 2, 3, 4, 3, 6}, b: []int{1, 2, 3, 4, 5, 6}, diff: nil}, 63 | } 64 | for _, tt := range tests { 65 | t.Run(tt.name, func(t *testing.T) { 66 | got := DiffFunc(NewMockCollection(tt.a), NewMockCollection(tt.b), func(a, b int) bool { return a == b }).(*MockCollection[int]).items 67 | want := NewMockCollection(tt.diff).items 68 | if !slices.Equal(got, want) { 69 | t.Errorf("DiffFunc() = %v, want %v", got, want) 70 | } 71 | }) 72 | } 73 | } 74 | 75 | func TestDistinct(t *testing.T) { 76 | tests := []struct { 77 | name string 78 | a []int 79 | want []int 80 | }{ 81 | {name: "distinct", a: []int{1, 1, 1, 2, 2, 3}, want: []int{1, 2, 3}}, 82 | {name: "distinct with no duplicates", a: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, want: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}}, 83 | {name: "distinct with empty collection", a: []int{}, want: []int{}}, 84 | } 85 | for _, tt := range tests { 86 | t.Run(tt.name, func(t *testing.T) { 87 | got := Distinct(NewMockCollection(tt.a), func(a, b int) bool { return a == b }).(*MockCollection[int]).items 88 | if !slices.Equal(got, tt.want) { 89 | t.Errorf("DistinctFunc() = %v, want %v", got, tt.want) 90 | } 91 | }) 92 | } 93 | } 94 | 95 | func TestReduce(t *testing.T) { 96 | sum := func(acc, curr int) int { return acc + curr } 97 | 98 | tests := []struct { 99 | name string 100 | input []int 101 | reducer func(int, int) int 102 | init int 103 | expected int 104 | }{ 105 | { 106 | name: "empty slice", 107 | input: []int{}, 108 | reducer: sum, 109 | init: 0, 110 | expected: 0, 111 | }, 112 | { 113 | name: "sum numbers", 114 | input: []int{1, 2, 3, 4, 5}, 115 | reducer: sum, 116 | init: 0, 117 | expected: 15, 118 | }, 119 | { 120 | name: "sum with non-zero init", 121 | input: []int{1, 2, 3}, 122 | reducer: sum, 123 | init: 10, 124 | expected: 16, 125 | }, 126 | } 127 | 128 | for _, tt := range tests { 129 | t.Run(tt.name, func(t *testing.T) { 130 | if got := Reduce(NewMockCollection(tt.input), tt.reducer, tt.init); got != tt.expected { 131 | t.Errorf("Reduce() = %v, want %v", got, tt.expected) 132 | } 133 | }) 134 | } 135 | } 136 | 137 | func TestFilter(t *testing.T) { 138 | isEven := func(n int) bool { return n%2 == 0 } 139 | tests := []struct { 140 | name string 141 | input []int 142 | want []int 143 | }{ 144 | { 145 | name: "filter evens", 146 | input: []int{1, 2, 3, 4, 5, 6}, 147 | want: []int{2, 4, 6}, 148 | }, 149 | { 150 | name: "no matches", 151 | input: []int{1, 3, 5}, 152 | want: nil, 153 | }, 154 | { 155 | name: "empty slice", 156 | input: []int{}, 157 | want: nil, 158 | }, 159 | } 160 | 161 | for _, tt := range tests { 162 | t.Run(tt.name, func(t *testing.T) { 163 | got := Filter(NewMockCollection(tt.input), isEven) 164 | want := NewMockCollection(tt.want) 165 | if !slices.Equal(got.(*MockCollection[int]).items, want.items) { 166 | t.Errorf("Filter() = %v, want %v", got, want) 167 | } 168 | }) 169 | } 170 | } 171 | 172 | func TestFilterNot(t *testing.T) { 173 | isEven := func(n int) bool { return n%2 == 0 } 174 | tests := []struct { 175 | name string 176 | input []int 177 | want []int 178 | }{ 179 | { 180 | name: "filter not evens", 181 | input: []int{1, 2, 3, 4, 5, 6}, 182 | want: []int{1, 3, 5}, 183 | }, 184 | { 185 | name: "no matches", 186 | input: []int{2, 4, 6}, 187 | want: nil, 188 | }, 189 | { 190 | name: "empty slice", 191 | input: []int{}, 192 | want: nil, 193 | }, 194 | } 195 | 196 | for _, tt := range tests { 197 | t.Run(tt.name, func(t *testing.T) { 198 | got := FilterNot(NewMockCollection(tt.input), isEven) 199 | want := NewMockCollection(tt.want) 200 | if !slices.Equal(got.(*MockCollection[int]).items, want.items) { 201 | t.Errorf("FilterNot() = %v, want %v", got, want) 202 | } 203 | }) 204 | } 205 | } 206 | 207 | func TestForAll(t *testing.T) { 208 | isLessThan10 := func(n int) bool { return n < 10 } 209 | tests := []struct { 210 | name string 211 | input []int 212 | expected bool 213 | }{ 214 | { 215 | name: "all less than 10", 216 | input: []int{1, 2, 3, 4, 5}, 217 | expected: true, 218 | }, 219 | { 220 | name: "not all less than 10", 221 | input: []int{1, 5, 10, 15}, 222 | expected: false, 223 | }, 224 | { 225 | name: "empty slice", 226 | input: []int{}, 227 | expected: true, 228 | }, 229 | } 230 | 231 | for _, tt := range tests { 232 | t.Run(tt.name, func(t *testing.T) { 233 | if got := ForAll(NewMockCollection(tt.input), isLessThan10); got != tt.expected { 234 | t.Errorf("ForAll() = %v, want %v", got, tt.expected) 235 | } 236 | }) 237 | } 238 | } 239 | 240 | func TestGroupBy(t *testing.T) { 241 | modTwo := func(n int) int { return n % 2 } 242 | tests := []struct { 243 | name string 244 | input []int 245 | expected map[int][]int 246 | }{ 247 | { 248 | name: "group by mod 2", 249 | input: []int{1, 2, 3, 4, 5, 6}, 250 | expected: map[int][]int{ 251 | 0: {2, 4, 6}, 252 | 1: {1, 3, 5}, 253 | }, 254 | }, 255 | { 256 | name: "empty slice", 257 | input: []int{}, 258 | expected: map[int][]int{}, 259 | }, 260 | } 261 | 262 | for _, tt := range tests { 263 | t.Run(tt.name, func(t *testing.T) { 264 | result := GroupBy(NewMockCollection(tt.input), modTwo) 265 | for k, v := range tt.expected { 266 | want := NewMockCollection(v) 267 | got := result[k] 268 | if !slices.Equal(got.(*MockCollection[int]).items, want.items) { 269 | t.Errorf("GroupBy()[%v] = %v, want %v", k, got, want) 270 | } 271 | } 272 | }) 273 | } 274 | } 275 | 276 | func TestIntersect(t *testing.T) { 277 | tests := []struct { 278 | name string 279 | a []int 280 | b []int 281 | want []int 282 | }{ 283 | { 284 | name: "intersect", 285 | a: []int{1, 2, 3, 4, 5, 6}, 286 | b: []int{2, 4, 6, 8, 10}, 287 | want: []int{2, 4, 6}, 288 | }, 289 | { 290 | name: "no intersection", 291 | a: []int{1, 3, 5}, 292 | b: []int{2, 4, 6}, 293 | want: nil, 294 | }, 295 | { 296 | name: "empty slices", 297 | a: []int{}, 298 | b: []int{}, 299 | want: nil, 300 | }, 301 | } 302 | 303 | for _, tt := range tests { 304 | t.Run(tt.name, func(t *testing.T) { 305 | got := Intersect(NewMockCollection(tt.a), NewMockCollection(tt.b)) 306 | want := NewMockCollection(tt.want) 307 | if !slices.Equal(got.(*MockCollection[int]).items, want.items) { 308 | t.Errorf("Intersect() = %v, want %v", got, want) 309 | } 310 | }) 311 | } 312 | } 313 | 314 | func TestIntersectFunc(t *testing.T) { 315 | tests := []struct { 316 | name string 317 | a []int 318 | b []int 319 | want []int 320 | }{ 321 | { 322 | name: "intersect", 323 | a: []int{1, 2, 3, 4, 5, 6}, 324 | b: []int{2, 4, 6, 8, 10}, 325 | want: []int{2, 4, 6}, 326 | }, 327 | { 328 | name: "no intersection", 329 | a: []int{1, 3, 5}, 330 | b: []int{2, 4, 6}, 331 | want: nil, 332 | }, 333 | { 334 | name: "empty slices", 335 | a: []int{}, 336 | b: []int{}, 337 | want: nil, 338 | }, 339 | } 340 | 341 | for _, tt := range tests { 342 | t.Run(tt.name, func(t *testing.T) { 343 | got := IntersectFunc(NewMockCollection(tt.a), NewMockCollection(tt.b), func(a, b int) bool { return a == b }) 344 | want := NewMockCollection(tt.want) 345 | if !slices.Equal(got.(*MockCollection[int]).items, want.items) { 346 | t.Errorf("IntersectFunc() = %v, want %v", got, want) 347 | } 348 | }) 349 | } 350 | } 351 | 352 | func TestMap(t *testing.T) { 353 | double := func(n int) int { return n * 2 } 354 | tests := []struct { 355 | name string 356 | input []int 357 | want []int 358 | }{ 359 | { 360 | name: "double numbers", 361 | input: []int{1, 2, 3}, 362 | want: []int{2, 4, 6}, 363 | }, 364 | { 365 | name: "empty slice", 366 | input: []int{}, 367 | want: []int{}, 368 | }, 369 | } 370 | 371 | for _, tt := range tests { 372 | t.Run(tt.name, func(t *testing.T) { 373 | got := Map(NewMockCollection(tt.input), double) 374 | if !slices.Equal(got, tt.want) { 375 | t.Errorf("Map() = %v, want %v", got, tt.want) 376 | } 377 | }) 378 | } 379 | } 380 | 381 | func TestMaxBy(t *testing.T) { 382 | identity := func(a int) int { return a } 383 | tests := []struct { 384 | name string 385 | input []int 386 | expectedValue int 387 | expectedErr error 388 | }{ 389 | { 390 | name: "find max", 391 | input: []int{1, 2, 3, 4, 5}, 392 | expectedValue: 5, 393 | expectedErr: nil, 394 | }, 395 | { 396 | name: "find max in middle", 397 | input: []int{1, 2, 5, 3, 4}, 398 | expectedValue: 5, 399 | expectedErr: nil, 400 | }, 401 | { 402 | name: "empty collection", 403 | input: []int{}, 404 | expectedValue: 0, 405 | expectedErr: EmptyCollectionError, 406 | }, 407 | } 408 | 409 | for _, tt := range tests { 410 | t.Run(tt.name, func(t *testing.T) { 411 | value, err := MaxBy(NewMockCollection(tt.input), identity) 412 | if value != tt.expectedValue { 413 | t.Errorf("MaxBy() value = %v, want %v", value, tt.expectedValue) 414 | } 415 | if err != tt.expectedErr { 416 | t.Errorf("MaxBy() error = %v, want %v", err, tt.expectedErr) 417 | } 418 | }) 419 | } 420 | } 421 | 422 | func TestMinBy(t *testing.T) { 423 | identity := func(a int) int { return a } 424 | tests := []struct { 425 | name string 426 | input []int 427 | expectedValue int 428 | expectedErr error 429 | }{ 430 | { 431 | name: "find min", 432 | input: []int{5, 4, 3, 2, 1}, 433 | expectedValue: 1, 434 | expectedErr: nil, 435 | }, 436 | { 437 | name: "find min in middle", 438 | input: []int{5, 4, 1, 3, 2}, 439 | expectedValue: 1, 440 | expectedErr: nil, 441 | }, 442 | { 443 | name: "empty collection", 444 | input: []int{}, 445 | expectedValue: 0, 446 | expectedErr: EmptyCollectionError, 447 | }, 448 | } 449 | 450 | for _, tt := range tests { 451 | t.Run(tt.name, func(t *testing.T) { 452 | value, err := MinBy(NewMockCollection(tt.input), identity) 453 | if value != tt.expectedValue { 454 | t.Errorf("MinBy() value = %v, want %v", value, tt.expectedValue) 455 | } 456 | if err != tt.expectedErr { 457 | t.Errorf("MinBy() error = %v, want %v", err, tt.expectedErr) 458 | } 459 | }) 460 | } 461 | } 462 | 463 | func TestPartition(t *testing.T) { 464 | isEven := func(n int) bool { return n%2 == 0 } 465 | tests := []struct { 466 | name string 467 | input []int 468 | wantMatch []int 469 | wantRest []int 470 | }{ 471 | { 472 | name: "partition evens", 473 | input: []int{1, 2, 3, 4, 5, 6}, 474 | wantMatch: []int{2, 4, 6}, 475 | wantRest: []int{1, 3, 5}, 476 | }, 477 | { 478 | name: "empty slice", 479 | input: []int{}, 480 | wantMatch: nil, 481 | wantRest: nil, 482 | }, 483 | } 484 | 485 | for _, tt := range tests { 486 | t.Run(tt.name, func(t *testing.T) { 487 | match, rest := Partition(NewMockCollection(tt.input), isEven) 488 | wantMatch := NewMockCollection(tt.wantMatch) 489 | wantRest := NewMockCollection(tt.wantRest) 490 | 491 | if len(match.(*MockCollection[int]).items) != len(wantMatch.items) { 492 | t.Errorf("Partition() match = %v, want %v", match, wantMatch) 493 | } 494 | for i := range wantMatch.items { 495 | if match.(*MockCollection[int]).items[i] != wantMatch.items[i] { 496 | t.Errorf("Partition() match = %v, want %v", match, wantMatch) 497 | } 498 | } 499 | 500 | if len(rest.(*MockCollection[int]).items) != len(wantRest.items) { 501 | t.Errorf("Partition() rest = %v, want %v", rest, wantRest) 502 | } 503 | for i := range wantRest.items { 504 | if rest.(*MockCollection[int]).items[i] != wantRest.items[i] { 505 | t.Errorf("Partition() rest = %v, want %v", rest, wantRest) 506 | } 507 | } 508 | }) 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /set/set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "cmp" 5 | "slices" 6 | "testing" 7 | ) 8 | 9 | func TestSet_Contains(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | slice []int 13 | predicate int 14 | want bool 15 | }{ 16 | { 17 | name: "contains element matching predicate", 18 | slice: []int{1, 2, 3}, 19 | predicate: 2, 20 | want: true, 21 | }, 22 | { 23 | name: "does not contain element matching predicate", 24 | slice: []int{1, 2, 3}, 25 | predicate: 4, 26 | want: false, 27 | }, 28 | } 29 | 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | c := NewSet(tt.slice) 33 | got := c.Contains(tt.predicate) 34 | if got != tt.want { 35 | t.Errorf("Contains() = %v, want %v", got, tt.want) 36 | } 37 | }) 38 | } 39 | } 40 | 41 | func TestSet_ContainsFunc(t *testing.T) { 42 | tests := []struct { 43 | name string 44 | slice []int 45 | predicate func(int) bool 46 | want bool 47 | }{ 48 | { 49 | name: "contains element matching predicate", 50 | slice: []int{1, 2, 3}, 51 | predicate: func(i int) bool { return i == 2 }, 52 | want: true, 53 | }, 54 | { 55 | name: "does not contain element matching predicate", 56 | slice: []int{1, 2, 3}, 57 | predicate: func(i int) bool { return i == 4 }, 58 | want: false, 59 | }, 60 | } 61 | 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | s := NewSet(tt.slice) 65 | got := s.ContainsFunc(tt.predicate) 66 | if got != tt.want { 67 | t.Errorf("ContainsFunc() = %v, want %v", got, tt.want) 68 | } 69 | }) 70 | } 71 | } 72 | 73 | func TestSet_Union(t *testing.T) { 74 | tests := []struct { 75 | name string 76 | base []int 77 | others []int 78 | want []int 79 | }{ 80 | { 81 | name: "union with empty set", 82 | base: []int{1, 2, 3}, 83 | others: []int{}, 84 | want: []int{1, 2, 3}, 85 | }, 86 | { 87 | name: "union with non-empty set", 88 | base: []int{1, 2, 3}, 89 | others: []int{3, 4, 5}, 90 | want: []int{1, 2, 3, 4, 5}, 91 | }, 92 | { 93 | name: "union with no change", 94 | base: []int{1, 2}, 95 | others: []int{2, 1}, 96 | want: []int{1, 2}, 97 | }, 98 | } 99 | 100 | for _, tt := range tests { 101 | t.Run(tt.name, func(t *testing.T) { 102 | s := NewSet(tt.base) 103 | result := s.Union(NewSet(tt.others)) 104 | if !assertEqualValues(result.ToSlice(), tt.want) { 105 | t.Errorf("Union() = %v, want %v", result.ToSlice(), tt.want) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | func TestSet_Unioned(t *testing.T) { 112 | tests := []struct { 113 | name string 114 | base []int 115 | others []int 116 | want []int 117 | }{ 118 | { 119 | name: "union with empty set", 120 | base: []int{1, 2, 3}, 121 | others: []int{}, 122 | want: []int{1, 2, 3}, 123 | }, 124 | { 125 | name: "union with non-empty set", 126 | base: []int{1, 2, 3}, 127 | others: []int{3, 4, 5}, 128 | want: []int{1, 2, 3, 4, 5}, 129 | }, 130 | { 131 | name: "union with no change", 132 | base: []int{1, 2}, 133 | others: []int{2, 1}, 134 | want: []int{1, 2}, 135 | }, 136 | } 137 | 138 | for _, tt := range tests { 139 | t.Run(tt.name, func(t *testing.T) { 140 | got := []int{} 141 | s := NewSet(tt.base) 142 | for v := range s.Unioned(NewSet(tt.others)) { 143 | got = append(got, v) 144 | } 145 | if !assertEqualValues(got, tt.want) { 146 | t.Errorf("UnionIterator() = %v, want %v", got, tt.want) 147 | } 148 | }) 149 | } 150 | } 151 | 152 | func TestSet_Intersection(t *testing.T) { 153 | tests := []struct { 154 | name string 155 | base []int 156 | others []int 157 | want []int 158 | }{ 159 | { 160 | name: "intersection with empty set", 161 | base: []int{1, 2, 3}, 162 | others: []int{}, 163 | want: []int{}, 164 | }, 165 | { 166 | name: "intersection with non-empty set", 167 | base: []int{1, 2, 3}, 168 | others: []int{2, 3, 4}, 169 | want: []int{2, 3}, 170 | }, 171 | { 172 | name: "intersection with non-empty set 2", 173 | base: []int{1, 2, 3, 4}, 174 | others: []int{4, 5, 6}, 175 | want: []int{4}, 176 | }, 177 | { 178 | name: "intersection with no overlap", 179 | base: []int{1, 2, 3, 4}, 180 | others: []int{5, 6, 7}, 181 | want: []int{}, 182 | }, 183 | } 184 | 185 | for _, tt := range tests { 186 | t.Run(tt.name, func(t *testing.T) { 187 | s := NewSet(tt.base) 188 | result := s.Intersection(NewSet(tt.others)) 189 | if !assertEqualValues(result.ToSlice(), tt.want) { 190 | t.Errorf("Intersection() = %v, want %v", result.ToSlice(), tt.want) 191 | } 192 | }) 193 | } 194 | } 195 | 196 | func TestSet_Intersected(t *testing.T) { 197 | tests := []struct { 198 | name string 199 | base []int 200 | others []int 201 | want []int 202 | }{ 203 | { 204 | name: "intersection with empty set", 205 | base: []int{1, 2, 3}, 206 | others: []int{}, 207 | want: []int{}, 208 | }, 209 | { 210 | name: "intersection with non-empty set", 211 | base: []int{1, 2, 3}, 212 | others: []int{2, 3, 4}, 213 | want: []int{2, 3}, 214 | }, 215 | { 216 | name: "intersection with non-empty set 2", 217 | base: []int{1, 2, 3, 4}, 218 | others: []int{4, 5, 6}, 219 | want: []int{4}, 220 | }, 221 | { 222 | name: "intersection with no overlap", 223 | base: []int{1, 2, 3, 4}, 224 | others: []int{5, 6, 7}, 225 | want: []int{}, 226 | }, 227 | } 228 | 229 | for _, tt := range tests { 230 | t.Run(tt.name, func(t *testing.T) { 231 | got := []int{} 232 | s := NewSet(tt.base) 233 | for v := range s.Intersected(NewSet(tt.others)) { 234 | got = append(got, v) 235 | } 236 | if !assertEqualValues(got, tt.want) { 237 | t.Errorf("IntersectionIterator() = %v, want %v", got, tt.want) 238 | } 239 | }) 240 | } 241 | } 242 | 243 | func TestSet_Diff(t *testing.T) { 244 | tests := []struct { 245 | name string 246 | base []int 247 | diff []int 248 | want []int 249 | }{ 250 | { 251 | name: "diff with empty set", 252 | base: []int{1, 2, 3}, 253 | diff: []int{}, 254 | want: []int{1, 2, 3}, 255 | }, 256 | { 257 | name: "diff with non-empty set", 258 | base: []int{1, 2, 3}, 259 | diff: []int{2, 3}, 260 | want: []int{1}, 261 | }, 262 | { 263 | name: "diff with no overlap", 264 | base: []int{1, 2, 3}, 265 | diff: []int{4, 5}, 266 | want: []int{1, 2, 3}, 267 | }, 268 | } 269 | 270 | for _, tt := range tests { 271 | t.Run(tt.name, func(t *testing.T) { 272 | s1 := NewSet(tt.base) 273 | s2 := NewSet(tt.diff) 274 | result := s1.Diff(s2) 275 | if !assertEqualValues(result.ToSlice(), tt.want) { 276 | t.Errorf("Diff() = %v, want %v", result.ToSlice(), tt.want) 277 | } 278 | }) 279 | } 280 | } 281 | 282 | func TestSet_Equals(t *testing.T) { 283 | tests := []struct { 284 | name string 285 | s1 []int 286 | s2 []int 287 | want bool 288 | }{ 289 | { 290 | name: "equal sets", 291 | s1: []int{1, 2, 3}, 292 | s2: []int{1, 2, 3}, 293 | want: true, 294 | }, 295 | { 296 | name: "different lengths", 297 | s1: []int{1, 2, 3}, 298 | s2: []int{1, 2}, 299 | want: false, 300 | }, 301 | { 302 | name: "different elements", 303 | s1: []int{1, 2, 3}, 304 | s2: []int{1, 2, 4}, 305 | want: false, 306 | }, 307 | } 308 | 309 | for _, tt := range tests { 310 | t.Run(tt.name, func(t *testing.T) { 311 | s1 := NewSet(tt.s1) 312 | s2 := NewSet(tt.s2) 313 | got := s1.Equals(s2) 314 | if got != tt.want { 315 | t.Errorf("Equals() = %v, want %v", got, tt.want) 316 | } 317 | }) 318 | } 319 | } 320 | 321 | func TestSet_IsEmpty(t *testing.T) { 322 | tests := []struct { 323 | name string 324 | slice []int 325 | want bool 326 | }{ 327 | { 328 | name: "empty set", 329 | slice: []int{}, 330 | want: true, 331 | }, 332 | { 333 | name: "non-empty set", 334 | slice: []int{1, 2, 3}, 335 | want: false, 336 | }, 337 | } 338 | 339 | for _, tt := range tests { 340 | t.Run(tt.name, func(t *testing.T) { 341 | s := NewSet(tt.slice) 342 | got := s.IsEmpty() 343 | if got != tt.want { 344 | t.Errorf("IsEmpty() = %v, want %v", got, tt.want) 345 | } 346 | }) 347 | } 348 | } 349 | 350 | func TestSet_Clone(t *testing.T) { 351 | original := NewSet([]int{1, 2, 3}) 352 | clone := original.Clone() 353 | 354 | // Verify clone has same elements 355 | if !assertEqualValues(original.ToSlice(), clone.ToSlice()) { 356 | t.Errorf("Clone() = %v, want %v", clone.ToSlice(), original.ToSlice()) 357 | } 358 | 359 | // Verify modifying clone doesn't affect original 360 | clone.Add(4) 361 | if slices.Contains(original.ToSlice(), 4) { 362 | t.Errorf("Clone() = %v, want %v", clone.ToSlice(), original.ToSlice()) 363 | } 364 | if !slices.Contains(clone.ToSlice(), 4) { 365 | t.Errorf("Clone() = %v, want %v", clone.ToSlice(), original.ToSlice()) 366 | } 367 | } 368 | 369 | func TestSet_Partition(t *testing.T) { 370 | tests := []struct { 371 | name string 372 | slice []int 373 | predicate func(int) bool 374 | wantLeft []int 375 | wantRight []int 376 | }{ 377 | { 378 | name: "partition evens and odds", 379 | slice: []int{1, 2, 3, 4, 5, 6}, 380 | predicate: func(i int) bool { return i%2 == 0 }, 381 | wantLeft: []int{2, 4, 6}, 382 | wantRight: []int{1, 3, 5}, 383 | }, 384 | } 385 | 386 | for _, tt := range tests { 387 | t.Run(tt.name, func(t *testing.T) { 388 | s := NewSet(tt.slice) 389 | left, right := s.Partition(tt.predicate) 390 | if !assertEqualValues(left.ToSlice(), tt.wantLeft) { 391 | t.Errorf("Partition() = %v, want %v", left.ToSlice(), tt.wantLeft) 392 | } 393 | if !assertEqualValues(right.ToSlice(), tt.wantRight) { 394 | t.Errorf("Partition() = %v, want %v", right.ToSlice(), tt.wantRight) 395 | } 396 | }) 397 | } 398 | } 399 | 400 | func TestSet_NonEmpty(t *testing.T) { 401 | tests := []struct { 402 | name string 403 | slice []int 404 | want bool 405 | }{ 406 | { 407 | name: "empty set", 408 | slice: []int{}, 409 | want: false, 410 | }, 411 | { 412 | name: "non-empty set", 413 | slice: []int{1, 2, 3}, 414 | want: true, 415 | }, 416 | } 417 | 418 | for _, tt := range tests { 419 | t.Run(tt.name, func(t *testing.T) { 420 | s := NewSet(tt.slice) 421 | got := s.NonEmpty() 422 | if got != tt.want { 423 | t.Errorf("NonEmpty() = %v, want %v", got, tt.want) 424 | } 425 | }) 426 | } 427 | } 428 | 429 | func TestSet_ForAll(t *testing.T) { 430 | tests := []struct { 431 | name string 432 | slice []int 433 | predicate func(int) bool 434 | want bool 435 | }{ 436 | { 437 | name: "all elements match predicate", 438 | slice: []int{2, 4, 6}, 439 | predicate: func(i int) bool { return i%2 == 0 }, 440 | want: true, 441 | }, 442 | { 443 | name: "not all elements match predicate", 444 | slice: []int{1, 2, 3}, 445 | predicate: func(i int) bool { return i%2 == 0 }, 446 | want: false, 447 | }, 448 | } 449 | 450 | for _, tt := range tests { 451 | t.Run(tt.name, func(t *testing.T) { 452 | s := NewSet(tt.slice) 453 | got := s.ForAll(tt.predicate) 454 | if got != tt.want { 455 | t.Errorf("ForAll() = %v, want %v", got, tt.want) 456 | } 457 | }) 458 | } 459 | } 460 | 461 | func TestSet_Filter(t *testing.T) { 462 | tests := []struct { 463 | name string 464 | slice []int 465 | predicate func(int) bool 466 | want []int 467 | }{ 468 | { 469 | name: "filter even numbers", 470 | slice: []int{1, 2, 3, 4, 5, 6}, 471 | predicate: func(i int) bool { return i%2 == 0 }, 472 | want: []int{2, 4, 6}, 473 | }, 474 | { 475 | name: "filter nothing matches", 476 | slice: []int{1, 3, 5}, 477 | predicate: func(i int) bool { return i%2 == 0 }, 478 | want: []int{}, 479 | }, 480 | } 481 | 482 | for _, tt := range tests { 483 | t.Run(tt.name, func(t *testing.T) { 484 | s := NewSet(tt.slice) 485 | result := s.Filter(tt.predicate) 486 | if !assertEqualValues(result.ToSlice(), tt.want) { 487 | t.Errorf("Filter() = %v, want %v", result.ToSlice(), tt.want) 488 | } 489 | }) 490 | } 491 | } 492 | 493 | func TestSet_FilterNot(t *testing.T) { 494 | tests := []struct { 495 | name string 496 | slice []int 497 | predicate func(int) bool 498 | want []int 499 | }{ 500 | { 501 | name: "filter out even numbers", 502 | slice: []int{1, 2, 3, 4, 5, 6}, 503 | predicate: func(i int) bool { return i%2 == 0 }, 504 | want: []int{1, 3, 5}, 505 | }, 506 | { 507 | name: "filter out nothing matches", 508 | slice: []int{2, 4, 6}, 509 | predicate: func(i int) bool { return i%2 == 0 }, 510 | want: []int{}, 511 | }, 512 | } 513 | 514 | for _, tt := range tests { 515 | t.Run(tt.name, func(t *testing.T) { 516 | s := NewSet(tt.slice) 517 | result := s.FilterNot(tt.predicate) 518 | if !assertEqualValues(result.ToSlice(), tt.want) { 519 | t.Errorf("FilterNot() = %v, want %v", result.ToSlice(), tt.want) 520 | } 521 | }) 522 | } 523 | } 524 | 525 | func TestSet_Count(t *testing.T) { 526 | tests := []struct { 527 | name string 528 | slice []int 529 | predicate func(int) bool 530 | want int 531 | }{ 532 | { 533 | name: "count even numbers", 534 | slice: []int{1, 2, 3, 4, 5, 6}, 535 | predicate: func(i int) bool { return i%2 == 0 }, 536 | want: 3, 537 | }, 538 | { 539 | name: "count nothing matches", 540 | slice: []int{1, 3, 5}, 541 | predicate: func(i int) bool { return i%2 == 0 }, 542 | want: 0, 543 | }, 544 | } 545 | 546 | for _, tt := range tests { 547 | t.Run(tt.name, func(t *testing.T) { 548 | s := NewSet(tt.slice) 549 | got := s.Count(tt.predicate) 550 | if got != tt.want { 551 | t.Errorf("Count() = %v, want %v", got, tt.want) 552 | } 553 | }) 554 | } 555 | } 556 | 557 | func TestSet_Random(t *testing.T) { 558 | s := NewSet([]int{1}) 559 | if got := s.Random(); got != 1 { 560 | t.Errorf("Random() = %v, want %v", got, 1) 561 | } 562 | } 563 | 564 | func TestSet_Remove(t *testing.T) { 565 | s := NewSet([]int{1, 2, 3}) 566 | s.Remove(2) 567 | if !assertEqualValues(s.ToSlice(), []int{1, 3}) { 568 | t.Errorf("Remove() = %v, want %v", s.ToSlice(), []int{1, 3}) 569 | } 570 | } 571 | 572 | func TestSet_Reject(t *testing.T) { 573 | tests := []struct { 574 | name string 575 | slice []int 576 | predicate func(int) bool 577 | want []int 578 | }{ 579 | { 580 | name: "filter out even numbers", 581 | slice: []int{1, 2, 3, 4, 5, 6}, 582 | predicate: func(i int) bool { return i%2 == 0 }, 583 | want: []int{1, 3, 5}, 584 | }, 585 | { 586 | name: "filter out nothing matches", 587 | slice: []int{2, 4, 6}, 588 | predicate: func(i int) bool { return i%2 == 0 }, 589 | want: []int{}, 590 | }, 591 | } 592 | 593 | for _, tt := range tests { 594 | t.Run(tt.name, func(t *testing.T) { 595 | s := NewSet(tt.slice) 596 | result := s.Reject(tt.predicate) 597 | if !assertEqualValues(result.ToSlice(), tt.want) { 598 | t.Errorf("Reject() = %v, want %v", result.ToSlice(), tt.want) 599 | } 600 | }) 601 | } 602 | } 603 | 604 | func TestDiffIterator(t *testing.T) { 605 | tests := []struct { 606 | name string 607 | a *Set[int] 608 | b *Set[int] 609 | want []int 610 | }{ 611 | { 612 | name: "diff with empty set", 613 | a: NewSet([]int{1, 2, 3}), 614 | b: NewSet([]int{}), 615 | want: []int{1, 2, 3}, 616 | }, 617 | { 618 | name: "diff with non-empty set", 619 | a: NewSet([]int{1, 2, 3, 5, 6}), 620 | b: NewSet([]int{2, 3, 4}), 621 | want: []int{1, 5, 6}, 622 | }, 623 | } 624 | 625 | for _, tt := range tests { 626 | t.Run(tt.name, func(t *testing.T) { 627 | collected := []int{} 628 | for v := range tt.a.DiffIterator(tt.b) { 629 | collected = append(collected, v) 630 | } 631 | if !assertEqualValues(collected, tt.want) { 632 | t.Errorf("DiffIterator() = %v, want %v", collected, tt.want) 633 | } 634 | }) 635 | } 636 | } 637 | 638 | func assertEqualValues[T cmp.Ordered](a []T, b []T) bool { 639 | if len(a) != len(b) { 640 | return false 641 | } 642 | slices.Sort(a) 643 | slices.Sort(b) 644 | return slices.Equal(a, b) 645 | } 646 | -------------------------------------------------------------------------------- /sequence/sequence_test.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import ( 4 | "reflect" 5 | "slices" 6 | "testing" 7 | ) 8 | 9 | func TestConcat(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | base []int 13 | toConcat [][]int 14 | want []int 15 | }{ 16 | { 17 | name: "single Sequence concat", 18 | base: []int{1, 2}, 19 | toConcat: [][]int{{3, 4}}, 20 | want: []int{1, 2, 3, 4}, 21 | }, 22 | { 23 | name: "multiple Sequences concat", 24 | base: []int{1, 2}, 25 | toConcat: [][]int{{3, 4}, {5, 6}}, 26 | want: []int{1, 2, 3, 4, 5, 6}, 27 | }, 28 | } 29 | 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | c := NewSequence(tt.base) 33 | var Sequences []Sequence[int] 34 | for _, slice := range tt.toConcat { 35 | Sequences = append(Sequences, *NewSequence(slice)) 36 | } 37 | 38 | result := c.Concat(Sequences...) 39 | if !slices.Equal(result.elements, tt.want) { 40 | t.Errorf("Concat() = %v, want %v", result.elements, tt.want) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func TestDistinct(t *testing.T) { 47 | tests := []struct { 48 | name string 49 | input []int 50 | want []int 51 | }{ 52 | { 53 | name: "no duplicates", 54 | input: []int{1, 2, 3}, 55 | want: []int{1, 2, 3}, 56 | }, 57 | { 58 | name: "with duplicates", 59 | input: []int{1, 2, 2, 3, 3, 3}, 60 | want: []int{1, 2, 3}, 61 | }, 62 | } 63 | 64 | for _, tt := range tests { 65 | t.Run(tt.name, func(t *testing.T) { 66 | c := NewSequence(tt.input) 67 | result := c.Distinct(func(a, b int) bool { 68 | return a == b 69 | }) 70 | if !slices.Equal(result.elements, tt.want) { 71 | t.Errorf("Distinct() = %v, want %v", result.elements, tt.want) 72 | } 73 | }) 74 | } 75 | } 76 | 77 | func TestSequence_At(t *testing.T) { 78 | tests := []struct { 79 | name string 80 | slice []int 81 | index int 82 | want int 83 | wantErr bool 84 | }{ 85 | { 86 | name: "valid index - first element", 87 | slice: []int{1, 2, 3}, 88 | index: 0, 89 | want: 1, 90 | wantErr: false, 91 | }, 92 | { 93 | name: "valid index - middle element", 94 | slice: []int{1, 2, 3}, 95 | index: 1, 96 | want: 2, 97 | wantErr: false, 98 | }, 99 | { 100 | name: "valid index - last element", 101 | slice: []int{1, 2, 3}, 102 | index: 2, 103 | want: 3, 104 | wantErr: false, 105 | }, 106 | { 107 | name: "invalid index - out of bounds", 108 | slice: []int{1, 2, 3}, 109 | index: 3, 110 | wantErr: true, 111 | }, 112 | { 113 | name: "invalid index - negative", 114 | slice: []int{1, 2, 3}, 115 | index: -1, 116 | wantErr: true, 117 | }, 118 | } 119 | 120 | for _, tt := range tests { 121 | t.Run(tt.name, func(t *testing.T) { 122 | defer func() { 123 | if r := recover(); r != nil { 124 | if !tt.wantErr { 125 | t.Errorf("At() panicked: %v", r) 126 | } 127 | } 128 | }() 129 | c := NewSequence(tt.slice) 130 | got := c.At(tt.index) 131 | if got != tt.want { 132 | t.Errorf("At() = %v, want %v", got, tt.want) 133 | } 134 | 135 | }) 136 | } 137 | } 138 | 139 | func TestSequence_Contains(t *testing.T) { 140 | tests := []struct { 141 | name string 142 | slice []int 143 | predicate func(int) bool 144 | want bool 145 | }{ 146 | { 147 | name: "contains element matching predicate", 148 | slice: []int{1, 2, 3}, 149 | predicate: func(i int) bool { return i == 2 }, 150 | want: true, 151 | }, 152 | { 153 | name: "does not contain element matching predicate", 154 | slice: []int{1, 2, 3}, 155 | predicate: func(i int) bool { return i == 4 }, 156 | want: false, 157 | }, 158 | } 159 | 160 | for _, tt := range tests { 161 | t.Run(tt.name, func(t *testing.T) { 162 | c := NewSequence(tt.slice) 163 | got := c.Contains(tt.predicate) 164 | if got != tt.want { 165 | t.Errorf("Contains() = %v, want %v", got, tt.want) 166 | } 167 | }) 168 | } 169 | } 170 | 171 | func TestSequence_Drop(t *testing.T) { 172 | tests := []struct { 173 | name string 174 | slice []int 175 | n int 176 | want []int 177 | }{ 178 | { 179 | name: "drop positive number", 180 | slice: []int{1, 2, 3, 4, 5}, 181 | n: 2, 182 | want: []int{3, 4, 5}, 183 | }, 184 | { 185 | name: "drop zero", 186 | slice: []int{1, 2, 3}, 187 | n: 0, 188 | want: []int{1, 2, 3}, 189 | }, 190 | { 191 | name: "drop all elements", 192 | slice: []int{1, 2, 3}, 193 | n: 3, 194 | want: nil, 195 | }, 196 | { 197 | name: "drop more than length", 198 | slice: []int{1, 2, 3}, 199 | n: 5, 200 | want: nil, 201 | }, 202 | } 203 | 204 | for _, tt := range tests { 205 | t.Run(tt.name, func(t *testing.T) { 206 | c := NewSequence(tt.slice) 207 | got := c.Drop(tt.n) 208 | if !slices.Equal(got.ToSlice(), tt.want) { 209 | t.Errorf("Drop() = %v, want %v", got.ToSlice(), tt.want) 210 | } 211 | }) 212 | } 213 | } 214 | 215 | func TestSequence_Filter(t *testing.T) { 216 | tests := []struct { 217 | name string 218 | slice []int 219 | filter func(int) bool 220 | want []int 221 | }{ 222 | { 223 | name: "filter even numbers", 224 | slice: []int{1, 2, 3, 4, 5, 6}, 225 | filter: func(i int) bool { return i%2 == 0 }, 226 | want: []int{2, 4, 6}, 227 | }, 228 | { 229 | name: "filter nothing", 230 | slice: []int{1, 2, 3}, 231 | filter: func(i int) bool { return false }, 232 | want: nil, 233 | }, 234 | { 235 | name: "filter everything", 236 | slice: []int{1, 2, 3}, 237 | filter: func(i int) bool { return true }, 238 | want: []int{1, 2, 3}, 239 | }, 240 | } 241 | 242 | for _, tt := range tests { 243 | t.Run(tt.name, func(t *testing.T) { 244 | c := NewSequence(tt.slice) 245 | got := c.Filter(tt.filter) 246 | if !slices.Equal(got.ToSlice(), tt.want) { 247 | t.Errorf("Filter() = %v, want %v", got.ToSlice(), tt.want) 248 | } 249 | }) 250 | } 251 | } 252 | 253 | func TestSequence_DropRight(t *testing.T) { 254 | tests := []struct { 255 | name string 256 | slice []int 257 | n int 258 | want []int 259 | }{ 260 | { 261 | name: "drop right positive number", 262 | slice: []int{1, 2, 3, 4, 5}, 263 | n: 2, 264 | want: []int{1, 2, 3}, 265 | }, 266 | { 267 | name: "drop right zero", 268 | slice: []int{1, 2, 3}, 269 | n: 0, 270 | want: []int{1, 2, 3}, 271 | }, 272 | { 273 | name: "drop right all elements", 274 | slice: []int{1, 2, 3}, 275 | n: 3, 276 | want: nil, 277 | }, 278 | { 279 | name: "drop right more than length", 280 | slice: []int{1, 2, 3}, 281 | n: 5, 282 | want: nil, 283 | }, 284 | } 285 | 286 | for _, tt := range tests { 287 | t.Run(tt.name, func(t *testing.T) { 288 | c := NewSequence(tt.slice) 289 | got := c.DropRight(tt.n) 290 | if !slices.Equal(got.ToSlice(), tt.want) { 291 | t.Errorf("DropRight() = %v, want %v", got.ToSlice(), tt.want) 292 | } 293 | }) 294 | } 295 | } 296 | 297 | func TestSequence_FilterNot(t *testing.T) { 298 | tests := []struct { 299 | name string 300 | slice []int 301 | filter func(int) bool 302 | want []int 303 | }{ 304 | { 305 | name: "filter not even numbers", 306 | slice: []int{1, 2, 3, 4, 5, 6}, 307 | filter: func(i int) bool { return i%2 == 0 }, 308 | want: []int{1, 3, 5}, 309 | }, 310 | { 311 | name: "filter not nothing", 312 | slice: []int{1, 2, 3}, 313 | filter: func(i int) bool { return false }, 314 | want: []int{1, 2, 3}, 315 | }, 316 | { 317 | name: "filter not everything", 318 | slice: []int{1, 2, 3}, 319 | filter: func(i int) bool { return true }, 320 | want: nil, 321 | }, 322 | } 323 | 324 | for _, tt := range tests { 325 | t.Run(tt.name, func(t *testing.T) { 326 | c := NewSequence(tt.slice) 327 | got := c.FilterNot(tt.filter) 328 | if !slices.Equal(got.ToSlice(), tt.want) { 329 | t.Errorf("FilterNot() = %v, want %v", got.ToSlice(), tt.want) 330 | } 331 | }) 332 | } 333 | } 334 | 335 | func TestSequence_Find(t *testing.T) { 336 | tests := []struct { 337 | name string 338 | slice []int 339 | predicate func(int) bool 340 | value int 341 | index int 342 | }{ 343 | { 344 | name: "find existing element", 345 | slice: []int{1, 2, 3, 4, 5}, 346 | predicate: func(i int) bool { return i > 3 }, 347 | value: 4, 348 | index: 3, 349 | }, 350 | { 351 | name: "element not found", 352 | slice: []int{1, 2, 3}, 353 | predicate: func(i int) bool { return i > 5 }, 354 | value: 0, 355 | index: -1, 356 | }, 357 | { 358 | name: "empty slice", 359 | slice: []int{}, 360 | predicate: func(i int) bool { return true }, 361 | value: 0, 362 | index: -1, 363 | }, 364 | } 365 | 366 | for _, tt := range tests { 367 | t.Run(tt.name, func(t *testing.T) { 368 | c := NewSequence(tt.slice) 369 | index, value := c.Find(tt.predicate) 370 | 371 | if index != tt.index { 372 | t.Errorf("Find() index = %v, want %v", index, tt.index) 373 | } 374 | if value != tt.value { 375 | t.Errorf("Find() value = %v, want %v", value, tt.value) 376 | } 377 | }) 378 | } 379 | } 380 | 381 | func TestSequence_Head(t *testing.T) { 382 | tests := []struct { 383 | name string 384 | slice []int 385 | want int 386 | wantErr bool 387 | }{ 388 | { 389 | name: "non-empty slice", 390 | slice: []int{1, 2, 3}, 391 | want: 1, 392 | wantErr: false, 393 | }, 394 | { 395 | name: "empty slice", 396 | slice: []int{}, 397 | want: 0, 398 | wantErr: true, 399 | }, 400 | } 401 | 402 | for _, tt := range tests { 403 | t.Run(tt.name, func(t *testing.T) { 404 | defer func() { 405 | if r := recover(); r != nil { 406 | if !tt.wantErr { 407 | t.Errorf("Head() panicked: %v", r) 408 | } 409 | } 410 | }() 411 | c := NewSequence(tt.slice) 412 | got, _ := c.Head() 413 | if got != tt.want { 414 | t.Errorf("Head() = %v, want %v", got, tt.want) 415 | } 416 | }) 417 | } 418 | } 419 | func TestSequence_Pop(t *testing.T) { 420 | tests := []struct { 421 | name string 422 | slice []int 423 | want int 424 | wantErr bool 425 | }{ 426 | { 427 | name: "non-empty slice", 428 | slice: []int{1, 2, 3}, 429 | want: 3, 430 | wantErr: false, 431 | }, 432 | { 433 | name: "empty slice", 434 | slice: []int{}, 435 | want: 0, 436 | wantErr: true, 437 | }, 438 | } 439 | 440 | for _, tt := range tests { 441 | t.Run(tt.name, func(t *testing.T) { 442 | defer func() { 443 | if r := recover(); r != nil { 444 | if !tt.wantErr { 445 | t.Errorf("Pop() panicked: %v", r) 446 | } 447 | } 448 | }() 449 | c := NewSequence(tt.slice) 450 | got, _ := c.Pop() 451 | if !tt.wantErr { 452 | if got != tt.want { 453 | t.Errorf("Pop() = %v, want %v", got, tt.want) 454 | } 455 | if c.Length() != len(tt.slice)-1 { 456 | t.Errorf("Pop() length = %v, want %v", c.Length(), len(tt.slice)-1) 457 | } 458 | } 459 | }) 460 | } 461 | } 462 | 463 | func TestSequence_Push(t *testing.T) { 464 | tests := []struct { 465 | name string 466 | slice []int 467 | toPush int 468 | expected []int 469 | }{ 470 | { 471 | name: "push to non-empty slice", 472 | slice: []int{1, 2}, 473 | toPush: 3, 474 | expected: []int{1, 2, 3}, 475 | }, 476 | { 477 | name: "push to empty slice", 478 | slice: []int{}, 479 | toPush: 1, 480 | expected: []int{1}, 481 | }, 482 | } 483 | 484 | for _, tt := range tests { 485 | t.Run(tt.name, func(t *testing.T) { 486 | c := NewSequence(tt.slice) 487 | c.Push(tt.toPush) 488 | if !slices.Equal(c.ToSlice(), tt.expected) { 489 | t.Errorf("Push() = %v, want %v", c.ToSlice(), tt.expected) 490 | } 491 | }) 492 | } 493 | } 494 | 495 | func TestSequence_Dequeue(t *testing.T) { 496 | tests := []struct { 497 | name string 498 | slice []int 499 | want int 500 | wantErr bool 501 | }{ 502 | { 503 | name: "non-empty slice", 504 | slice: []int{1, 2, 3}, 505 | want: 1, 506 | wantErr: false, 507 | }, 508 | { 509 | name: "empty slice", 510 | slice: []int{}, 511 | want: 0, 512 | wantErr: true, 513 | }, 514 | } 515 | 516 | for _, tt := range tests { 517 | t.Run(tt.name, func(t *testing.T) { 518 | defer func() { 519 | if r := recover(); r != nil { 520 | if !tt.wantErr { 521 | t.Errorf("Dequeue() panicked: %v", r) 522 | } 523 | } 524 | }() 525 | c := NewSequence(tt.slice) 526 | if !tt.wantErr { 527 | got, _ := c.Dequeue() 528 | if got != tt.want { 529 | t.Errorf("Dequeue() = %v, want %v", got, tt.want) 530 | } 531 | if c.Length() != len(tt.slice)-1 { 532 | t.Errorf("Dequeue() length = %v, want %v", c.Length(), len(tt.slice)-1) 533 | } 534 | } 535 | }) 536 | } 537 | } 538 | 539 | func TestSequence_Enqueue(t *testing.T) { 540 | tests := []struct { 541 | name string 542 | slice []int 543 | toEnqueue int 544 | expected []int 545 | }{ 546 | { 547 | name: "enqueue to non-empty slice", 548 | slice: []int{1, 2}, 549 | toEnqueue: 3, 550 | expected: []int{1, 2, 3}, 551 | }, 552 | { 553 | name: "enqueue to empty slice", 554 | slice: []int{}, 555 | toEnqueue: 1, 556 | expected: []int{1}, 557 | }, 558 | } 559 | 560 | for _, tt := range tests { 561 | t.Run(tt.name, func(t *testing.T) { 562 | c := NewSequence(tt.slice) 563 | c.Enqueue(tt.toEnqueue) 564 | if !slices.Equal(c.ToSlice(), tt.expected) { 565 | t.Errorf("Enqueue() = %v, want %v", c.ToSlice(), tt.expected) 566 | } 567 | }) 568 | } 569 | } 570 | 571 | func TestSequence_Length(t *testing.T) { 572 | tests := []struct { 573 | name string 574 | slice []int 575 | want int 576 | }{ 577 | { 578 | name: "non-empty slice", 579 | slice: []int{1, 2, 3}, 580 | want: 3, 581 | }, 582 | { 583 | name: "empty slice", 584 | slice: []int{}, 585 | want: 0, 586 | }, 587 | } 588 | 589 | for _, tt := range tests { 590 | t.Run(tt.name, func(t *testing.T) { 591 | c := NewSequence(tt.slice) 592 | got := c.Length() 593 | if got != tt.want { 594 | t.Errorf("Length() = %v, want %v", got, tt.want) 595 | } 596 | }) 597 | } 598 | } 599 | 600 | func TestSequence_Reject(t *testing.T) { 601 | tests := []struct { 602 | name string 603 | slice []int 604 | filter func(int) bool 605 | want []int 606 | }{ 607 | { 608 | name: "filter not even numbers", 609 | slice: []int{1, 2, 3, 4, 5, 6}, 610 | filter: func(i int) bool { return i%2 == 0 }, 611 | want: []int{1, 3, 5}, 612 | }, 613 | { 614 | name: "filter not nothing", 615 | slice: []int{1, 2, 3}, 616 | filter: func(i int) bool { return false }, 617 | want: []int{1, 2, 3}, 618 | }, 619 | { 620 | name: "filter not everything", 621 | slice: []int{1, 2, 3}, 622 | filter: func(i int) bool { return true }, 623 | want: nil, 624 | }, 625 | } 626 | 627 | for _, tt := range tests { 628 | t.Run(tt.name, func(t *testing.T) { 629 | c := NewSequence(tt.slice) 630 | got := c.Reject(tt.filter) 631 | if !slices.Equal(got.ToSlice(), tt.want) { 632 | t.Errorf("Reject() = %v, want %v", got.ToSlice(), tt.want) 633 | } 634 | }) 635 | } 636 | } 637 | 638 | func TestSequence_Slice(t *testing.T) { 639 | tests := []struct { 640 | name string 641 | slice []int 642 | start int 643 | end int 644 | want []int 645 | }{ 646 | { 647 | name: "valid slice", 648 | slice: []int{1, 2, 3, 4, 5}, 649 | start: 1, 650 | end: 3, 651 | want: []int{2, 3}, 652 | }, 653 | { 654 | name: "empty range", 655 | slice: []int{1, 2, 3}, 656 | start: 1, 657 | end: 1, 658 | want: []int{}, 659 | }, 660 | } 661 | 662 | for _, tt := range tests { 663 | t.Run(tt.name, func(t *testing.T) { 664 | c := NewSequence(tt.slice) 665 | got := c.Slice(tt.start, tt.end) 666 | asSlice := make([]int, 0) 667 | for _, v := range got.All() { 668 | asSlice = append(asSlice, v) 669 | } 670 | if !slices.Equal(asSlice, tt.want) { 671 | t.Errorf("Slice() = %v, want %v", asSlice, tt.want) 672 | } 673 | }) 674 | } 675 | } 676 | 677 | func TestSequence_Shuffle(t *testing.T) { 678 | tests := []struct { 679 | name string 680 | input []int 681 | }{ 682 | {name: "basic shuffle", input: []int{1, 2, 3, 4, 5}}, 683 | {name: "empty sequence", input: []int{}}, 684 | {name: "single element", input: []int{42}}, 685 | {name: "duplicate elements", input: []int{1, 1, 2, 2, 3, 3}}, 686 | } 687 | 688 | for _, tt := range tests { 689 | t.Run(tt.name, func(t *testing.T) { 690 | seq := NewSequence(tt.input) 691 | shuffled := seq.Shuffle() 692 | 693 | // Verify length 694 | if shuffled.Length() != seq.Length() { 695 | t.Errorf("Shuffle() length = %d, want %d", shuffled.Length(), seq.Length()) 696 | } 697 | 698 | // Verify element preservation 699 | originalMap := make(map[int]int) 700 | shuffledMap := make(map[int]int) 701 | for _, v := range seq.ToSlice() { 702 | originalMap[v]++ 703 | } 704 | for _, v := range shuffled.ToSlice() { 705 | shuffledMap[v]++ 706 | } 707 | if !reflect.DeepEqual(originalMap, shuffledMap) { 708 | t.Errorf("Shuffle() elements mismatch, got %v, want %v", shuffledMap, originalMap) 709 | } 710 | }) 711 | } 712 | } 713 | 714 | func TestSequence_ShuffleRandomization(t *testing.T) { 715 | input := []int{1, 2, 3, 4, 5, 6} 716 | seq := NewSequence(input) 717 | iterations := 1000 718 | 719 | sameOrderCount := 0 720 | for i := 0; i < iterations; i++ { 721 | shuffled := seq.Shuffle() 722 | if reflect.DeepEqual(input, shuffled.ToSlice()) { 723 | sameOrderCount++ 724 | } 725 | } 726 | 727 | // Expect the same order to appear <5% of the time for small sequences 728 | threshold := 0.05 * float64(iterations) 729 | if float64(sameOrderCount) > threshold { 730 | t.Errorf("Shuffle() produced the same order %d times, exceeding threshold %f", sameOrderCount, threshold) 731 | } 732 | } 733 | 734 | func TestSequence_ShuffleDistribution(t *testing.T) { 735 | input := []int{1, 2, 3, 4} 736 | seq := NewSequence(input) 737 | 738 | // Map to track the position of each value 739 | positionCounts := make([]map[int]int, len(input)) 740 | for i := range positionCounts { 741 | positionCounts[i] = make(map[int]int) 742 | } 743 | 744 | iterations := 10000 745 | for i := 0; i < iterations; i++ { 746 | shuffled := seq.Shuffle() 747 | for pos, val := range shuffled.ToSlice() { 748 | positionCounts[pos][val]++ 749 | } 750 | } 751 | 752 | // Validate uniform distribution 753 | expectedCount := iterations / len(input) 754 | tolerance := 0.1 * float64(expectedCount) 755 | for pos, counts := range positionCounts { 756 | for val, count := range counts { 757 | if float64(count) < float64(expectedCount)-tolerance || float64(count) > float64(expectedCount)+tolerance { 758 | t.Errorf("Value %d appeared at position %d %d times, which is outside the tolerance range [%f, %f]", 759 | val, pos, count, float64(expectedCount)-tolerance, float64(expectedCount)+tolerance) 760 | } 761 | } 762 | } 763 | } 764 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gophers - generic collection utils for Go 2 | [![Go Reference](https://pkg.go.dev/badge/github.com/charbz/gophers.svg)](https://pkg.go.dev/github.com/charbz/gophers) 3 | ![gophers](https://github.com/user-attachments/assets/8ae33a16-e66a-43af-9412-f6bbd192d647) 4 | 5 | Gophers is an awesome collections library for Go offering tons of utilities right out of the box. 6 | 7 | Collection Types: 8 | - **Sequence** : An ordered collection wrapping a Go slice. Great for fast random access. 9 | - **ComparableSequence** : A Sequence of comparable elements. Offers extra functionality. 10 | - **List** : An ordered collection wrapping a linked list. Great for fast insertion, removal, and implementing stacks and queues. 11 | - **ComparableList** : A List of comparable elements. Offers extra functionality. 12 | - **Set** : A hash set of unique elements. 13 | 14 | Here's a few examples of what you can do: 15 | 16 | ## Quick Start 17 | 18 | ### Installation 19 | ```bash 20 | go get github.com/charbz/gophers 21 | ``` 22 | 23 | ### Using Generic Data Types 24 | 25 | ```go 26 | import ( 27 | "github.com/charbz/gophers/list" 28 | ) 29 | 30 | type Foo struct { 31 | a int 32 | b string 33 | } 34 | 35 | foos := list.NewList([]Foo{ 36 | {a: 1, b: "one"}, 37 | {a: 2, b: "two"}, 38 | {a: 3, b: "three"}, 39 | {a: 4, b: "four"}, 40 | {a: 5, b: "five"}, 41 | }) 42 | 43 | foos.Filter(func(f Foo) bool { return f.a%2 == 0 }) 44 | // List[Foo] {{2 two} {4 four}} 45 | 46 | foos.Reject(func(f Foo) bool { return f.a%2 == 0 }) 47 | // List[Foo] {{1 one} {3 three} {5 five}} 48 | 49 | foos.Find(func(f Foo) bool { return f.a == 3 }) 50 | // {a: 3, b: "three"} 51 | 52 | foos.Partition(func(f Foo) bool { return len(f.b) == 3 }) 53 | // List[Foo] {{1 one} {2 two}} , List[Foo] {{3 three} {4 four} {5 five}} 54 | 55 | foos.SplitAt(3) 56 | // List[Foo] {{1 one} {2 two} {3 three} {4 four}} , List[Foo] {{5 five}} 57 | 58 | foos.Count(func(f Foo) bool { return f.a < 3 }) 59 | // 2 60 | 61 | bars := foos.Concat(list.NewList([]Foo{{a: 1, b: "one"}, {a: 2, b: "two"}})) 62 | // List[Foo] {{1 one} {2 two} {3 three} {4 four} {5 five} {1 one} {2 two}} 63 | 64 | bars.Distinct(func(i Foo, j Foo) bool { return i.a == j.a }) 65 | // List[Foo] {{1 one} {2 two} {3 three} {4 four} {5 five}} 66 | 67 | foos.Apply( 68 | func(f Foo) Foo { 69 | f.a *= 2 70 | f.b += " * two" 71 | return f 72 | } 73 | ) 74 | // List[Foo] {{2 one * two} {4 two * two} {6 three * two} {8 four * two} {10 five * two}} 75 | ``` 76 | 77 | ### Comparable Collections 78 | 79 | Comparable collections are collections with elements that can be compared to each other. 80 | They offer all the same functionality as an ordered collection but provide additional convenience methods. 81 | 82 | ```go 83 | import ( 84 | "github.com/charbz/gophers/list" 85 | ) 86 | 87 | nums := list.NewComparableList([]int{1, 1, 2, 2, 2, 3, 4, 5}) 88 | 89 | nums.Max() // 5 90 | 91 | nums.Min() // 1 92 | 93 | nums.Sum() // 20 94 | 95 | nums.Distinct() // List[int] {1,2,3,4,5} 96 | 97 | nums.Reverse() // List[int] {5,4,3,2,2,1,1} 98 | 99 | nums.SplitAt(3) // List[int] {1,1,2,2}, List[int] {2,3,4,5} 100 | 101 | nums.Take(3) // List[int] {1,1,2} 102 | 103 | nums.TakeRight(3) // List[int] {3,4,5} 104 | 105 | nums.Drop(3) // List[int] {2,2,3,4,5} 106 | 107 | nums.DropRight(3) // List[int] {1,1,2,2,2} 108 | 109 | nums.DropWhile( 110 | func(i int) bool { return i < 3 }, // List[int] {3,4,5} 111 | ) 112 | 113 | nums.Diff( 114 | list.NewComparableList([]int{1, 2, 3}), // List[int] {4,5} 115 | ) 116 | 117 | nums.Count( 118 | func(i int) bool { return i > 2 }, // 3 119 | ) 120 | ``` 121 | 122 | ### Sets 123 | 124 | Sets are collections of unique elements. They offer all the same functionality as an unordered collection 125 | but provide additional methods for set operations. 126 | 127 | ```go 128 | import ( 129 | "github.com/charbz/gophers/set" 130 | ) 131 | 132 | setA := set.NewSet([]string{"A", "B", "C", "A", "C", "A"}) // Set[string] {"A", "B", "C"} 133 | setB := set.NewSet([]string{"A", "B", "D"}) 134 | 135 | setA.Intersection(setB) // Set[string] {"A", "B"} 136 | 137 | setA.Union(setB) // Set[string] {"A", "B", "C", "D"} 138 | 139 | setA.Diff(setB) // Set[string] {"C"} 140 | 141 | setA.Apply( 142 | func(s string) string { 143 | s += "!" 144 | return s 145 | }, // Set[string] {"A!", "B!", "C!"} 146 | ) 147 | ``` 148 | 149 | ### Map, Reduce, GroupBy... 150 | 151 | You can use package functions such as Map, Reduce, GroupBy, and many more on any concrete collection type. 152 | 153 | ```go 154 | import ( 155 | "github.com/charbz/gophers/collection" 156 | "github.com/charbz/gophers/list" 157 | "github.com/charbz/gophers/sequence" 158 | ) 159 | 160 | foos := sequence.NewSequence([]Foo{ 161 | {a: 1, b: "one"}, 162 | {a: 2, b: "two"}, 163 | {a: 3, b: "three"}, 164 | {a: 4, b: "four"}, 165 | {a: 5, b: "five"}, 166 | }) 167 | 168 | collection.Map(foos, func(f Foo) string { return f.b }) // ["one", "two", "three", "four", "five"] 169 | 170 | collection.Reduce(foos, func(acc string, f Foo) string { return acc + f.b }, "") // "onetwothreefourfive" 171 | 172 | collection.Reduce(foos, func(acc int, f Foo) int { return acc + f.a }, 0) // 15 173 | 174 | collection.GroupBy(foos, func(f Foo) int { return f.a % 2 }) // Map[int][]Foo { 0: [{2 two}, {4 four}], 1: [{1 one}, {3 three}, {5 five}]} 175 | ``` 176 | 177 | **Note:** Given that methods cannot define new type parameters in Go, any function that produces a new type, for example `Map(List[T], func(T) K) -> List[K]`, 178 | must be called as a function similar to the examples above and cannot be made into a method of the collection type i.e. `List[T].Map(func(T) K) -> List[K]`. 179 | This is a minor inconvenience as it breaks the consistency of the API but is a limitation of the language. 180 | 181 | ### Iterator Methods 182 | 183 | All collections implement methods that return iterators over the result as opposed to returning the result itself. 184 | This is useful when combined with the `for ... range` syntax to iterate over the result. 185 | 186 | ```go 187 | import ( 188 | "github.com/charbz/gophers/list" 189 | ) 190 | 191 | A := list.NewComparableList([]int{1, 2, 2, 3}) 192 | B := list.NewComparableList([]int{4, 5, 2}) 193 | 194 | for i := range A.Filtered(func(v int) bool { return v%2 == 0 }) { 195 | fmt.Printf("filtered %v\n", i) 196 | } 197 | // filtered 2 198 | // filtered 2 199 | 200 | for i := range A.Rejected(func(v int) bool { return v%2 == 0 }) { 201 | fmt.Printf("rejected %v\n", i) 202 | } 203 | // rejected 1 204 | // rejected 3 205 | 206 | for i := range A.Distincted() { 207 | fmt.Printf("distincted %v\n", i) 208 | } 209 | // distincted 1 210 | // distincted 2 211 | // distincted 3 212 | 213 | for i := range A.Concatenated(B) { 214 | fmt.Printf("concatenated %v\n", i) 215 | } 216 | // concatenated 1 217 | // concatenated 2 218 | // concatenated 2 219 | // concatenated 3 220 | // concatenated 4 221 | // concatenated 5 222 | // concatenated 2 223 | 224 | for i := range A.Diffed(B) { 225 | fmt.Printf("diffed %v\n", i) 226 | } 227 | // diffed 1 228 | // diffed 3 229 | 230 | for i := range A.Intersected(B) { 231 | fmt.Printf("intersected %v\n", i) 232 | } 233 | // intersected 2 234 | // intersected 2 // (A is not a set) 235 | ``` 236 | 237 | ### Sequence Operations 238 | 239 | - `Add(element)` - Append element to sequence 240 | - `All()` - Get iterator over all elements 241 | - `At(index)` - Get element at index 242 | - `Apply(function)` - Apply function to each element (mutates the original collection) 243 | - `Backward()` - Get reverse iterator over elements 244 | - `Clone()` - Create shallow copy of sequence 245 | - `Concat(sequences...)` - Concatenates any passed sequences 246 | - `Concatenated(sequence)` - Get iterator over concatenated sequence 247 | - `Contains(predicate)` - Test if any element matches predicate 248 | - `Corresponds(sequence, function)` - Test element-wise correspondence 249 | - `Count(predicate)` - Count elements matching predicate 250 | - `Dequeue()` - Remove and return first element 251 | - `Diff(sequence, function)` - Get elements in first sequence but not in second 252 | - `Diffed(sequence, function)` - Get iterator over elements in first sequence but not in second 253 | - `Distinct(function)` - Get unique elements using equality function 254 | - `Distincted()` - Get unique elements using equality comparison 255 | - `Drop(n)` - Drop first n elements 256 | - `DropRight(n)` - Drop last n elements 257 | - `DropWhile(predicate)` - Drop elements while predicate is true 258 | - `Enqueue(element)` - Add element to end 259 | - `Equals(sequence, function)` - Test sequence equality using function 260 | - `Exists(predicate)` - Test if any element matches predicate 261 | - `Filter(predicate)` - Filter elements based on predicate 262 | - `FilterNot(predicate)` - Inverse filter operation 263 | - `Find(predicate)` - Find first matching element 264 | - `FindLast(predicate)` - Find last matching element 265 | - `ForAll(predicate)` - Test if predicate holds for all elements 266 | - `Head()` - Get first element 267 | - `Init()` - Get all elements except last 268 | - `Intersect(sequence, function)` - Get elements present in both sequences 269 | - `Intersected(sequence, function)` - Get iterator over elements present in both sequences 270 | - `IsEmpty()` - Test if sequence is empty 271 | - `Last()` - Get last element 272 | - `Length()` - Get number of elements 273 | - `New(slices...)` - Create new sequence 274 | - `NewOrdered(slices...)` - Create new ordered sequence 275 | - `NonEmpty()` - Test if sequence is not empty 276 | - `Partition(predicate)` - Split sequence based on predicate 277 | - `Pop()` - Remove and return last element 278 | - `Push(element)` - Add element to end 279 | - `Random()` - Get random element 280 | - `Reverse()` - Reverse order of elements 281 | - `Reject(predicate)` - Inverse filter operation 282 | - `Rejected(predicate)` - Get iterator over elements rejected by predicate 283 | - `Slice(start, end)` - Get subsequence from start to end 284 | - `SplitAt(n)` - Split sequence at index n 285 | - `String()` - Get string representation 286 | - `Take(n)` - Get first n elements 287 | - `TakeRight(n)` - Get last n elements 288 | - `Tail()` - Get all elements except first 289 | - `ToSlice()` - Convert to Go slice 290 | - `Values()` - Get iterator over values 291 | 292 | ### ComparableSequence Operations 293 | 294 | Inherits all operations from Sequence, but with the following additional operations: 295 | 296 | - `Contains(element)` - Test if sequence contains element 297 | - `Distinct()` - Get unique elements using equality comparison 298 | - `Diff(sequence)` - Get elements in first sequence but not in second 299 | - `Equals(sequence)` - Test sequence equality using equality comparison 300 | - `Exists(element)` - Test if sequence contains element 301 | - `IndexOf(element)` - Get index of first occurrence of element 302 | - `LastIndexOf(element)` - Get index of last occurrence of element 303 | - `Max()` - Get maximum element 304 | - `Min()` - Get minimum element 305 | - `Sum()` - Get sum of all elements 306 | 307 | ### List Operations 308 | 309 | - `Add(element)` - Add element to end 310 | - `All()` - Get iterator over index/value pairs 311 | - `Apply(function)` - Apply function to each element 312 | - `At(index)` - Get element at index 313 | - `Backward()` - Get reverse iterator over index/value pairs 314 | - `Clone()` - Create shallow copy 315 | - `Concat(lists...)` - Concatenate multiple lists 316 | - `Concatenated(list)` - Get iterator over concatenated list 317 | - `Contains(predicate)` - Test if any element matches predicate 318 | - `Corresponds(list, function)` - Test element-wise correspondence 319 | - `Count(predicate)` - Count elements matching predicate 320 | - `Dequeue()` - Remove and return first element 321 | - `Diff(list, function)` - Get elements in first list but not in second 322 | - `Diffed(list, function)` - Get iterator over elements in first list but not in second 323 | - `Distinct(function)` - Get unique elements using equality function 324 | - `Distincted()` - Get unique elements using equality comparison 325 | - `Drop(n)` - Drop first n elements 326 | - `DropRight(n)` - Drop last n elements 327 | - `DropWhile(predicate)` - Drop elements while predicate is true 328 | - `Enqueue(element)` - Add element to end 329 | - `Equals(list, function)` - Test list equality using function 330 | - `Exists(predicate)` - Test if any element matches predicate 331 | - `Filter(predicate)` - Filter elements based on predicate 332 | - `FilterNot(predicate)` - Inverse filter operation 333 | - `Find(predicate)` - Find first matching element 334 | - `FindLast(predicate)` - Find last matching element 335 | - `ForAll(predicate)` - Test if predicate holds for all elements 336 | - `Head()` - Get first element 337 | - `Init()` - Get all elements except last 338 | - `Intersect(list, function)` - Get elements present in both lists 339 | - `Intersected(list, function)` - Get iterator over elements present in both lists 340 | - `IsEmpty()` - Test if list is empty 341 | - `Last()` - Get last element 342 | - `Length()` - Get number of elements 343 | - `New(slices...)` - Create new list 344 | - `NewOrdered(slices...)` - Create new ordered list 345 | - `NonEmpty()` - Test if list is not empty 346 | - `Partition(predicate)` - Split list based on predicate 347 | - `Pop()` - Remove and return last element 348 | - `Push(element)` - Add element to end 349 | - `Random()` - Get random element 350 | - `Reverse()` - Reverse order of elements 351 | - `Reject(predicate)` - Inverse filter operation 352 | - `Rejected(predicate)` - Get iterator over elements rejected by predicate 353 | - `Slice(start, end)` - Get sublist from start to end 354 | - `SplitAt(n)` - Split list at index n 355 | - `String()` - Get string representation 356 | - `Take(n)` - Get first n elements 357 | - `TakeRight(n)` - Get last n elements 358 | - `Tail()` - Get all elements except first 359 | - `ToSlice()` - Convert to Go slice 360 | - `Values()` - Get iterator over values 361 | 362 | ### ComparableList Operations 363 | 364 | Inherits all operations from List, but with the following additional operations: 365 | 366 | - `Contains(value)` - Test if list contains value 367 | - `Distinct()` - Get unique elements 368 | - `Diff(list)` - Get elements in first list but not in second 369 | - `Exists(value)` - Test if list contains value (alias for Contains) 370 | - `Equals(list)` - Test list equality 371 | - `IndexOf(value)` - Get index of first occurrence of value 372 | - `LastIndexOf(value)` - Get index of last occurrence of value 373 | - `Max()` - Get maximum element 374 | - `Min()` - Get minimum element 375 | - `Sum()` - Get sum of all elements 376 | 377 | 378 | ### Set Operations 379 | 380 | - `Add(element)` - Add element to set 381 | - `Apply(function)` - Apply function to each element 382 | - `Clone()` - Create shallow copy of set 383 | - `Contains(value)` - Test if set contains value 384 | - `ContainsFunc(predicate)` - Test if set contains element matching predicate 385 | - `Count(predicate)` - Count elements matching predicate 386 | - `Diff(set)` - Get elements in first set but not in second 387 | - `Diffed(set)` - Get iterator over elements in first set but not in second 388 | - `Equals(set)` - Test set equality 389 | - `Filter(predicate)` - Filter elements based on predicate 390 | - `FilterNot(predicate)` - Inverse filter operation 391 | - `ForAll(predicate)` - Test if predicate holds for all elements 392 | - `Intersection(set)` - Get elements present in both sets 393 | - `Intersected(set)` - Get iterator over elements present in both sets 394 | - `IsEmpty()` - Test if set is empty 395 | - `Length()` - Get number of elements 396 | - `New(slices...)` - Create new set 397 | - `NonEmpty()` - Test if set is not empty 398 | - `Partition(predicate)` - Split set based on predicate 399 | - `Random()` - Get random element 400 | - `Remove(element)` - Remove element from set 401 | - `Reject(predicate)` - Inverse filter operation 402 | - `Rejected(predicate)` - Get iterator over elements rejected by predicate 403 | - `String()` - Get string representation 404 | - `ToSlice()` - Convert to Go slice 405 | - `Union(set)` - Get elements present in either set 406 | - `Unioned(set)` - Get iterator over elements present in either set 407 | - `Values()` - Get iterator over values 408 | 409 | 410 | ### Collection Functions 411 | 412 | The following package functions can be called on any collection, including Sequence, ComparableSequence, List, ComparableList, and Set. 413 | - `Count(collection, predicate)` - Count elements matching predicate 414 | - `Diff(collection)` - Get elements in first collection but not in second 415 | - `Distinct(collection, function)` - Get unique elements 416 | - `Filter(collection, predicate)` - Filter elements based on predicate 417 | - `FilterNot(collection, predicate)` - Inverse filter operation 418 | - `ForAll(collection, predicate)` - Test if predicate holds for all elements 419 | - `GroupBy(collection, function)` - Group elements by key function 420 | - `Intersect(collection1, collection2)` - Get elements present in both collections 421 | - `Map(collection, function)` - Transform elements using function 422 | - `MaxBy(collection, function)` - Get maximum element by comparison function 423 | - `MinBy(collection, function)` - Get minimum element by comparison function 424 | - `Partition(collection, predicate)` - Split collection based on predicate 425 | - `Reduce(collection, function, initial)` - Reduce collection to single value 426 | 427 | The package functions below can be called on ordered collections (Sequence, ComparableSequence, List, and ComparableList): 428 | - `Corresponds(collection1, collection2, function)` - test whether values in collection1 map into values in collection2 by the given function 429 | - `Drop(collection, n)` - Drop first n elements 430 | - `DropRight(collection, n)` - Drop last n elements 431 | - `DropWhile(collection, predicate)` - Drop elements while predicate is true 432 | - `Find(collection, predicate)` - returns the index and value of the first element matching predicate 433 | - `FindLast(collection, predicate)` - returns the index and value of the last element matching predicate 434 | - `Head(collection)` - returns the first element in a collection 435 | - `Init(collection)` - returns all elements excluding the last one 436 | - `Last(collection)` - Get last element 437 | - `ReduceRight(collection, function, initial)` - Right-to-left reduction 438 | - `Reverse(collection)` - Reverse order of elements 439 | - `ReverseMap(collection, function)` - Map elements in reverse order 440 | - `SplitAt(collection, n)` - Split collection at index n 441 | - `Tail(collection)` - Get all elements except first 442 | - `Take(collection, n)` - Get first n elements 443 | - `TakeRight(collection, n)` - Get last n elements 444 | 445 | The following package functions return an iterator for the result: 446 | - `Concatenated(collection1, collection2)` - Get iterator over concatenated collection 447 | - `Diffed(collection1, collection2, function)` - Get iterator over elements in first collection but not in second 448 | - `Intersected(collection1, collection2, function)` - Get iterator over elements present in both collections 449 | - `Mapped(collection, function)` - Get iterator over elements transformed by function 450 | - `Rejected(collection, predicate)` - Get iterator over elements rejected by predicate 451 | 452 | 453 | ## Contributing 454 | 455 | Contributions are welcome! Feel free to submit a Pull Request. 456 | 457 | If you have any ideas for new features or improvements, or would like to chat, 458 | 459 | Feel free to reach out on [Discord: Gophers Project](https://discord.gg/vQ2dqQU6ve) or on [Reddit: r/gopherslib](https://www.reddit.com/r/gopherslib) 460 | -------------------------------------------------------------------------------- /collection/ordered_functions_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "slices" 7 | "testing" 8 | ) 9 | 10 | func TestCorresponds(t *testing.T) { 11 | isInverse := func(i, j int) bool { return i == -j } 12 | tests := []struct { 13 | name string 14 | A []int 15 | B []int 16 | correspond bool 17 | }{ 18 | {name: "correspond", A: []int{1, 2, 3}, B: []int{-1, -2, -3}, correspond: true}, 19 | {name: "not correspond", A: []int{1, 2, 3}, B: []int{-1, -2}, correspond: false}, 20 | {name: "not correspond", A: []int{1, 2, 3}, B: []int{-1, -2, -3, -4}, correspond: false}, 21 | {name: "not correspond", A: []int{1, 2, -3}, B: []int{-1, -2, -3}, correspond: false}, 22 | } 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | got := Corresponds(NewMockOrderedCollection(tt.A), NewMockOrderedCollection(tt.B), isInverse) 26 | if got != tt.correspond { 27 | t.Errorf("Corresponds() = %v, want %v", got, tt.correspond) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | func TestDrop(t *testing.T) { 34 | tests := []struct { 35 | name string 36 | slice []int 37 | n int 38 | want []int 39 | }{ 40 | { 41 | name: "drop first 2 elements", 42 | slice: []int{1, 2, 3, 4, 5, 6}, 43 | n: 2, 44 | want: []int{3, 4, 5, 6}, 45 | }, 46 | { 47 | name: "drop 0 elements", 48 | slice: []int{1, 2, 3}, 49 | n: 0, 50 | want: []int{1, 2, 3}, 51 | }, 52 | { 53 | name: "drop all elements", 54 | slice: []int{1, 2, 3}, 55 | n: 3, 56 | want: nil, 57 | }, 58 | { 59 | name: "drop more than length", 60 | slice: []int{1, 2, 3}, 61 | n: 5, 62 | want: nil, 63 | }, 64 | { 65 | name: "drop from empty slice", 66 | slice: []int{}, 67 | n: 2, 68 | want: nil, 69 | }, 70 | } 71 | 72 | for _, tt := range tests { 73 | t.Run(tt.name, func(t *testing.T) { 74 | c := NewMockOrderedCollection(tt.slice) 75 | got := Drop(c, tt.n) 76 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 77 | t.Errorf("Drop() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 78 | } 79 | }) 80 | } 81 | 82 | } 83 | 84 | func TestFind(t *testing.T) { 85 | isThree := func(n int) bool { return n == 3 } 86 | 87 | tests := []struct { 88 | name string 89 | input []int 90 | finder func(int) bool 91 | expectedIndex int 92 | expectedValue int 93 | }{ 94 | { 95 | name: "empty slice", 96 | input: []int{}, 97 | finder: isThree, 98 | expectedIndex: -1, 99 | expectedValue: 0, 100 | }, 101 | { 102 | name: "value found", 103 | input: []int{1, 2, 3, 4, 5}, 104 | finder: isThree, 105 | expectedIndex: 2, 106 | expectedValue: 3, 107 | }, 108 | { 109 | name: "value not found", 110 | input: []int{1, 2, 4, 5}, 111 | finder: isThree, 112 | expectedIndex: -1, 113 | expectedValue: 0, 114 | }, 115 | } 116 | 117 | for _, tt := range tests { 118 | t.Run(tt.name, func(t *testing.T) { 119 | index, value := Find(NewMockOrderedCollection(tt.input), tt.finder) 120 | if index != tt.expectedIndex || value != tt.expectedValue { 121 | t.Errorf("Find() = %v, want %v", index, tt.expectedIndex) 122 | t.Errorf("Find() = %v, want %v", value, tt.expectedValue) 123 | } 124 | }) 125 | } 126 | } 127 | 128 | func TestDropRight(t *testing.T) { 129 | tests := []struct { 130 | name string 131 | slice []int 132 | n int 133 | want []int 134 | }{ 135 | { 136 | name: "drop last 2 elements", 137 | slice: []int{1, 2, 3, 4, 5, 6}, 138 | n: 2, 139 | want: []int{1, 2, 3, 4}, 140 | }, 141 | { 142 | name: "drop 0 elements", 143 | slice: []int{1, 2, 3}, 144 | n: 0, 145 | want: []int{1, 2, 3}, 146 | }, 147 | { 148 | name: "drop all elements", 149 | slice: []int{1, 2, 3}, 150 | n: 3, 151 | want: nil, 152 | }, 153 | { 154 | name: "drop more than length", 155 | slice: []int{1, 2, 3}, 156 | n: 5, 157 | want: nil, 158 | }, 159 | { 160 | name: "drop from empty slice", 161 | slice: []int{}, 162 | n: 2, 163 | want: nil, 164 | }, 165 | } 166 | 167 | for _, tt := range tests { 168 | t.Run(tt.name, func(t *testing.T) { 169 | c := NewMockOrderedCollection(tt.slice) 170 | got := DropRight(c, tt.n) 171 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 172 | t.Errorf("DropRight() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 173 | } 174 | }) 175 | } 176 | } 177 | 178 | func TestDropWhile(t *testing.T) { 179 | isLessThan4 := func(n int) bool { return n < 4 } 180 | tests := []struct { 181 | name string 182 | input []int 183 | want []int 184 | }{ 185 | { 186 | name: "drop while less than 4", 187 | input: []int{1, 2, 3, 4, 5, 6}, 188 | want: []int{4, 5, 6}, 189 | }, 190 | { 191 | name: "drop none", 192 | input: []int{4, 5, 6}, 193 | want: []int{4, 5, 6}, 194 | }, 195 | { 196 | name: "drop all", 197 | input: []int{1, 2, 3}, 198 | want: []int{}, 199 | }, 200 | { 201 | name: "empty slice", 202 | input: []int{}, 203 | want: []int{}, 204 | }, 205 | } 206 | 207 | for _, tt := range tests { 208 | t.Run(tt.name, func(t *testing.T) { 209 | got := DropWhile(NewMockOrderedCollection(tt.input), isLessThan4) 210 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 211 | t.Errorf("DropWhile() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 212 | } 213 | }) 214 | } 215 | } 216 | 217 | func TestFindLast(t *testing.T) { 218 | isLessThan6 := func(n int) bool { return n < 6 } 219 | tests := []struct { 220 | name string 221 | input []int 222 | expectedIndex int 223 | expectedValue int 224 | }{ 225 | { 226 | name: "find last less than 6", 227 | input: []int{1, 2, 3, 4, 5, 6}, 228 | expectedIndex: 4, 229 | expectedValue: 5, 230 | }, 231 | { 232 | name: "no matches", 233 | input: []int{6, 7, 8}, 234 | expectedIndex: -1, 235 | expectedValue: 0, 236 | }, 237 | { 238 | name: "empty slice", 239 | input: []int{}, 240 | expectedIndex: -1, 241 | expectedValue: 0, 242 | }, 243 | } 244 | 245 | for _, tt := range tests { 246 | t.Run(tt.name, func(t *testing.T) { 247 | index, value := FindLast(NewMockOrderedCollection(tt.input), isLessThan6) 248 | if index != tt.expectedIndex || value != tt.expectedValue { 249 | t.Errorf("FindLast() = %v, want %v", index, tt.expectedIndex) 250 | t.Errorf("FindLast() = %v, want %v", value, tt.expectedValue) 251 | } 252 | }) 253 | } 254 | } 255 | 256 | func TestHead(t *testing.T) { 257 | tests := []struct { 258 | name string 259 | input []int 260 | expectedValue int 261 | expectedErr error 262 | }{ 263 | { 264 | name: "get head", 265 | input: []int{1, 2, 3}, 266 | expectedValue: 1, 267 | expectedErr: nil, 268 | }, 269 | { 270 | name: "empty slice", 271 | input: []int{}, 272 | expectedValue: 0, 273 | expectedErr: EmptyCollectionError, 274 | }, 275 | } 276 | 277 | for _, tt := range tests { 278 | t.Run(tt.name, func(t *testing.T) { 279 | value, err := Head(NewMockOrderedCollection(tt.input)) 280 | if value != tt.expectedValue || err != tt.expectedErr { 281 | t.Errorf("Head() = %v, want %v", value, tt.expectedValue) 282 | t.Errorf("Head() = %v, want %v", err, tt.expectedErr) 283 | } 284 | }) 285 | } 286 | } 287 | 288 | func TestInit(t *testing.T) { 289 | tests := []struct { 290 | name string 291 | input []int 292 | want []int 293 | }{ 294 | { 295 | name: "get init", 296 | input: []int{1, 2, 3, 4, 5}, 297 | want: []int{1, 2, 3, 4}, 298 | }, 299 | { 300 | name: "single element", 301 | input: []int{1}, 302 | want: []int{}, 303 | }, 304 | { 305 | name: "empty slice", 306 | input: []int{}, 307 | want: []int{}, 308 | }, 309 | } 310 | 311 | for _, tt := range tests { 312 | t.Run(tt.name, func(t *testing.T) { 313 | got := Init(NewMockOrderedCollection(tt.input)) 314 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 315 | t.Errorf("Init() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 316 | } 317 | }) 318 | } 319 | } 320 | 321 | func TestLast(t *testing.T) { 322 | tests := []struct { 323 | name string 324 | input []int 325 | expectedValue int 326 | expectedErr error 327 | }{ 328 | { 329 | name: "get last", 330 | input: []int{1, 2, 3}, 331 | expectedValue: 3, 332 | expectedErr: nil, 333 | }, 334 | { 335 | name: "empty slice", 336 | input: []int{}, 337 | expectedValue: 0, 338 | expectedErr: EmptyCollectionError, 339 | }, 340 | } 341 | 342 | for _, tt := range tests { 343 | t.Run(tt.name, func(t *testing.T) { 344 | value, err := Last(NewMockOrderedCollection(tt.input)) 345 | if value != tt.expectedValue || err != tt.expectedErr { 346 | t.Errorf("Last() = %v, want %v", value, tt.expectedValue) 347 | t.Errorf("Last() = %v, want %v", err, tt.expectedErr) 348 | } 349 | }) 350 | } 351 | } 352 | 353 | func TestReverse(t *testing.T) { 354 | tests := []struct { 355 | name string 356 | input []int 357 | want []int 358 | }{ 359 | { 360 | name: "reverse slice", 361 | input: []int{1, 2, 3, 4, 5}, 362 | want: []int{5, 4, 3, 2, 1}, 363 | }, 364 | { 365 | name: "single element", 366 | input: []int{1}, 367 | want: []int{1}, 368 | }, 369 | { 370 | name: "empty slice", 371 | input: []int{}, 372 | want: nil, 373 | }, 374 | } 375 | 376 | for _, tt := range tests { 377 | t.Run(tt.name, func(t *testing.T) { 378 | got := Reverse(NewMockOrderedCollection(tt.input)) 379 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 380 | t.Errorf("Reverse() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 381 | } 382 | }) 383 | } 384 | } 385 | 386 | func TestReduceRight(t *testing.T) { 387 | concat := func(acc string, curr int) string { return acc + fmt.Sprint(curr) } 388 | 389 | tests := []struct { 390 | name string 391 | input []int 392 | init string 393 | expected string 394 | }{ 395 | { 396 | name: "concat numbers", 397 | input: []int{1, 2, 3}, 398 | init: "", 399 | expected: "321", 400 | }, 401 | { 402 | name: "empty slice", 403 | input: []int{}, 404 | init: "", 405 | expected: "", 406 | }, 407 | } 408 | 409 | for _, tt := range tests { 410 | t.Run(tt.name, func(t *testing.T) { 411 | result := ReduceRight(NewMockOrderedCollection(tt.input), concat, tt.init) 412 | if result != tt.expected { 413 | t.Errorf("ReduceRight() = %v, want %v", result, tt.expected) 414 | } 415 | }) 416 | } 417 | } 418 | 419 | func TestReverseMap(t *testing.T) { 420 | double := func(n int) int { return n * 2 } 421 | tests := []struct { 422 | name string 423 | input []int 424 | want []int 425 | }{ 426 | { 427 | name: "double numbers in reverse", 428 | input: []int{1, 2, 3}, 429 | want: []int{6, 4, 2}, 430 | }, 431 | { 432 | name: "empty slice", 433 | input: []int{}, 434 | want: nil, 435 | }, 436 | } 437 | 438 | for _, tt := range tests { 439 | t.Run(tt.name, func(t *testing.T) { 440 | got := ReverseMap(NewMockOrderedCollection(tt.input), double) 441 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 442 | t.Errorf("ReverseMap() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 443 | } 444 | }) 445 | } 446 | } 447 | 448 | func TestTail(t *testing.T) { 449 | tests := []struct { 450 | name string 451 | input []int 452 | want []int 453 | }{ 454 | { 455 | name: "get tail", 456 | input: []int{1, 2, 3, 4, 5}, 457 | want: []int{2, 3, 4, 5}, 458 | }, 459 | { 460 | name: "single element", 461 | input: []int{1}, 462 | want: []int{}, 463 | }, 464 | { 465 | name: "empty slice", 466 | input: []int{}, 467 | want: []int{}, 468 | }, 469 | } 470 | 471 | for _, tt := range tests { 472 | t.Run(tt.name, func(t *testing.T) { 473 | got := Tail(NewMockOrderedCollection(tt.input)) 474 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 475 | t.Errorf("Tail() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 476 | } 477 | }) 478 | } 479 | } 480 | 481 | func TestTake(t *testing.T) { 482 | tests := []struct { 483 | name string 484 | input []int 485 | n int 486 | want []int 487 | }{ 488 | { 489 | name: "take first 3", 490 | input: []int{1, 2, 3, 4, 5}, 491 | n: 3, 492 | want: []int{1, 2, 3}, 493 | }, 494 | { 495 | name: "take more than length", 496 | input: []int{1, 2, 3}, 497 | n: 5, 498 | want: []int{1, 2, 3}, 499 | }, 500 | { 501 | name: "take 0", 502 | input: []int{1, 2, 3}, 503 | n: 0, 504 | want: nil, 505 | }, 506 | { 507 | name: "empty slice", 508 | input: []int{}, 509 | n: 3, 510 | want: []int{}, 511 | }, 512 | } 513 | 514 | for _, tt := range tests { 515 | t.Run(tt.name, func(t *testing.T) { 516 | got := Take(NewMockOrderedCollection(tt.input), tt.n) 517 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 518 | t.Errorf("Take() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 519 | } 520 | }) 521 | } 522 | } 523 | 524 | func TestTakeRight(t *testing.T) { 525 | tests := []struct { 526 | name string 527 | input []int 528 | n int 529 | want []int 530 | }{ 531 | { 532 | name: "take last 3", 533 | input: []int{1, 2, 3, 4, 5}, 534 | n: 3, 535 | want: []int{3, 4, 5}, 536 | }, 537 | { 538 | name: "take more than length", 539 | input: []int{1, 2, 3}, 540 | n: 5, 541 | want: []int{1, 2, 3}, 542 | }, 543 | { 544 | name: "take 0", 545 | input: []int{1, 2, 3}, 546 | n: 0, 547 | want: nil, 548 | }, 549 | { 550 | name: "empty slice", 551 | input: []int{}, 552 | n: 3, 553 | want: []int{}, 554 | }, 555 | } 556 | 557 | for _, tt := range tests { 558 | t.Run(tt.name, func(t *testing.T) { 559 | got := TakeRight(NewMockOrderedCollection(tt.input), tt.n) 560 | if !slices.Equal(got.(*MockOrderedCollection[int]).items, tt.want) { 561 | t.Errorf("TakeRight() = %v, want %v", got, NewMockOrderedCollection(tt.want)) 562 | } 563 | }) 564 | } 565 | } 566 | 567 | func TestStartsWith(t *testing.T) { 568 | tests := []struct { 569 | name string 570 | A []int 571 | B []int 572 | startsWith bool 573 | }{ 574 | // Core test cases 575 | {name: "exact match", A: []int{1, 2, 3}, B: []int{1, 2, 3}, startsWith: true}, 576 | {name: "prefix match", A: []int{1, 2, 3, 4}, B: []int{1, 2}, startsWith: true}, 577 | {name: "no match", A: []int{1, 2, 3}, B: []int{2, 3}, startsWith: false}, 578 | {name: "B longer than A", A: []int{1, 2}, B: []int{1, 2, 3}, startsWith: false}, 579 | 580 | // Edge cases 581 | {name: "B is empty", A: []int{1, 2, 3}, B: []int{}, startsWith: true}, 582 | {name: "A is empty", A: []int{}, B: []int{1}, startsWith: false}, 583 | {name: "both empty", A: []int{}, B: []int{}, startsWith: true}, 584 | } 585 | for _, tt := range tests { 586 | t.Run(tt.name, func(t *testing.T) { 587 | got := StartsWith(NewMockOrderedCollection(tt.A), NewMockOrderedCollection(tt.B)) 588 | if got != tt.startsWith { 589 | t.Errorf("StartsWith() = %v, want %v", got, tt.startsWith) 590 | } 591 | }) 592 | } 593 | } 594 | 595 | func TestEndsWith(t *testing.T) { 596 | tests := []struct { 597 | name string 598 | A []int 599 | B []int 600 | endsWith bool 601 | }{ 602 | // Core test cases 603 | {name: "exact match", A: []int{1, 2, 3}, B: []int{1, 2, 3}, endsWith: true}, 604 | {name: "postfix match", A: []int{1, 2, 3, 4}, B: []int{3, 4}, endsWith: true}, 605 | {name: "no match", A: []int{1, 2, 3}, B: []int{1, 2}, endsWith: false}, 606 | {name: "B longer than A", A: []int{1, 2}, B: []int{1, 2, 3}, endsWith: false}, 607 | 608 | // Edge cases 609 | {name: "B is empty", A: []int{1, 2, 3}, B: []int{}, endsWith: true}, 610 | {name: "A is empty", A: []int{}, B: []int{1}, endsWith: false}, 611 | {name: "both empty", A: []int{}, B: []int{}, endsWith: true}, 612 | } 613 | for _, tt := range tests { 614 | t.Run(tt.name, func(t *testing.T) { 615 | got := EndsWith(NewMockOrderedCollection(tt.A), NewMockOrderedCollection(tt.B)) 616 | if got != tt.endsWith { 617 | t.Errorf("EndsWith() = %v, want %v", got, tt.endsWith) 618 | } 619 | }) 620 | } 621 | } 622 | 623 | func TestShuffle(t *testing.T) { 624 | tests := []struct { 625 | name string 626 | input []int 627 | }{ 628 | {name: "basic shuffle", input: []int{1, 2, 3, 4, 5}}, 629 | {name: "empty collection", input: []int{}}, 630 | {name: "single element", input: []int{42}}, 631 | {name: "duplicate elements", input: []int{1, 1, 2, 2, 3, 3}}, 632 | } 633 | 634 | for _, tt := range tests { 635 | t.Run(tt.name, func(t *testing.T) { 636 | c := NewMockOrderedCollection(tt.input) 637 | shuffled := Shuffle(c) 638 | 639 | if shuffled.Length() != c.Length() { 640 | t.Errorf("Shuffle() length = %d, want %d", shuffled.Length(), c.Length()) 641 | } 642 | 643 | originalMap := make(map[int]int) 644 | shuffledMap := make(map[int]int) 645 | for _, v := range c.All() { 646 | originalMap[v]++ 647 | } 648 | for _, v := range shuffled.All() { 649 | shuffledMap[v]++ 650 | } 651 | if !reflect.DeepEqual(originalMap, shuffledMap) { 652 | t.Errorf("Shuffle() elements mismatch, got %v, want %v", shuffledMap, originalMap) 653 | } 654 | }) 655 | } 656 | } 657 | 658 | func TestShuffleRandomization(t *testing.T) { 659 | input := []int{1, 2, 3, 4, 5, 6} 660 | c := NewMockOrderedCollection(input) 661 | iterations := 10000 662 | 663 | sameOrderCount := 0 664 | for i := 0; i < iterations; i++ { 665 | shuffled := Shuffle(c) 666 | if reflect.DeepEqual(input, shuffled.(*MockOrderedCollection[int]).items) { 667 | sameOrderCount++ 668 | } 669 | } 670 | 671 | // Expect the same order to appear <5% of the time for small collections 672 | threshold := 0.05 * float64(iterations) 673 | if float64(sameOrderCount) > threshold { 674 | t.Errorf("Shuffle() produced the same order %d times, exceeding threshold %f", sameOrderCount, threshold) 675 | } 676 | } 677 | 678 | // This test function ensures that Shuffle() returns randomized 679 | // items in a uniform distribution 680 | func TestShuffleDistribution(t *testing.T) { 681 | input := []int{1, 2, 3, 4} 682 | c := NewMockOrderedCollection(input) 683 | 684 | // Map to track the position of each value 685 | positionCounts := make([]map[int]int, len(input)) 686 | for i := range positionCounts { 687 | positionCounts[i] = make(map[int]int) 688 | } 689 | 690 | iterations := 10000 691 | for i := 0; i < iterations; i++ { 692 | shuffled := Shuffle(c) 693 | for pos, val := range shuffled.All() { 694 | positionCounts[pos][val]++ 695 | } 696 | } 697 | 698 | // Validate uniform distribution 699 | expectedCount := iterations / len(input) 700 | tolerance := 0.1 * float64(expectedCount) 701 | for pos, counts := range positionCounts { 702 | for val, count := range counts { 703 | if float64(count) < float64(expectedCount)-tolerance || float64(count) > float64(expectedCount)+tolerance { 704 | t.Errorf("Value %d appeared at position %d %d times, which is outside the tolerance range [%f, %f]", 705 | val, pos, count, float64(expectedCount)-tolerance, float64(expectedCount)+tolerance) 706 | } 707 | } 708 | } 709 | } 710 | -------------------------------------------------------------------------------- /list/list_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "reflect" 5 | "slices" 6 | "testing" 7 | ) 8 | 9 | func TestList_Head(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | slice []int 13 | want int 14 | wantErr bool 15 | }{ 16 | { 17 | name: "non-empty list", 18 | slice: []int{1, 2, 3}, 19 | want: 1, 20 | wantErr: false, 21 | }, 22 | { 23 | name: "empty list", 24 | slice: []int{}, 25 | want: 0, 26 | wantErr: true, 27 | }, 28 | } 29 | 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | l := NewList(tt.slice) 33 | got, err := l.Head() 34 | if tt.wantErr { 35 | if err == nil { 36 | t.Errorf("Head() = %v, want error", got) 37 | } 38 | } else { 39 | if err != nil { 40 | t.Errorf("Head() = %v, want no error", got) 41 | } 42 | if got != tt.want { 43 | t.Errorf("Head() = %v, want %v", got, tt.want) 44 | } 45 | } 46 | }) 47 | } 48 | } 49 | 50 | func TestList_Drop(t *testing.T) { 51 | tests := []struct { 52 | name string 53 | slice []int 54 | n int 55 | want []int 56 | }{ 57 | { 58 | name: "drop positive number", 59 | slice: []int{1, 2, 3, 4, 5}, 60 | n: 2, 61 | want: []int{3, 4, 5}, 62 | }, 63 | { 64 | name: "drop zero", 65 | slice: []int{1, 2, 3}, 66 | n: 0, 67 | want: []int{1, 2, 3}, 68 | }, 69 | { 70 | name: "drop all elements", 71 | slice: []int{1, 2, 3}, 72 | n: 3, 73 | want: []int{}, 74 | }, 75 | { 76 | name: "drop more than length", 77 | slice: []int{1, 2, 3}, 78 | n: 5, 79 | want: []int{}, 80 | }, 81 | } 82 | 83 | for _, tt := range tests { 84 | t.Run(tt.name, func(t *testing.T) { 85 | l := NewList(tt.slice) 86 | got := l.Drop(tt.n) 87 | if !slices.Equal(got.ToSlice(), tt.want) { 88 | t.Errorf("Drop() = %v, want %v", got.ToSlice(), tt.want) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | func TestList_DropRight(t *testing.T) { 95 | tests := []struct { 96 | name string 97 | slice []int 98 | n int 99 | want []int 100 | }{ 101 | { 102 | name: "drop right positive number", 103 | slice: []int{1, 2, 3, 4, 5}, 104 | n: 2, 105 | want: []int{1, 2, 3}, 106 | }, 107 | { 108 | name: "drop right zero", 109 | slice: []int{1, 2, 3}, 110 | n: 0, 111 | want: []int{1, 2, 3}, 112 | }, 113 | { 114 | name: "drop right all elements", 115 | slice: []int{1, 2, 3}, 116 | n: 3, 117 | want: []int{}, 118 | }, 119 | { 120 | name: "drop right more than length", 121 | slice: []int{1, 2, 3}, 122 | n: 5, 123 | want: []int{}, 124 | }, 125 | } 126 | 127 | for _, tt := range tests { 128 | t.Run(tt.name, func(t *testing.T) { 129 | l := NewList(tt.slice) 130 | got := l.DropRight(tt.n) 131 | if !slices.Equal(got.ToSlice(), tt.want) { 132 | t.Errorf("DropRight() = %v, want %v", got.ToSlice(), tt.want) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | func TestList_Contains(t *testing.T) { 139 | tests := []struct { 140 | name string 141 | slice []int 142 | predicate func(int) bool 143 | want bool 144 | }{ 145 | { 146 | name: "contains element matching predicate", 147 | slice: []int{1, 2, 3}, 148 | predicate: func(i int) bool { return i == 2 }, 149 | want: true, 150 | }, 151 | { 152 | name: "does not contain element matching predicate", 153 | slice: []int{1, 2, 3}, 154 | predicate: func(i int) bool { return i == 4 }, 155 | want: false, 156 | }, 157 | } 158 | 159 | for _, tt := range tests { 160 | t.Run(tt.name, func(t *testing.T) { 161 | l := NewList(tt.slice) 162 | got := l.Contains(tt.predicate) 163 | if got != tt.want { 164 | t.Errorf("Contains() = %v, want %v", got, tt.want) 165 | } 166 | }) 167 | } 168 | } 169 | 170 | func TestList_Find(t *testing.T) { 171 | tests := []struct { 172 | name string 173 | slice []int 174 | predicate func(int) bool 175 | value int 176 | index int 177 | }{ 178 | { 179 | name: "find existing element", 180 | slice: []int{1, 2, 3, 4, 5}, 181 | predicate: func(i int) bool { return i > 3 }, 182 | value: 4, 183 | index: 3, 184 | }, 185 | { 186 | name: "element not found", 187 | slice: []int{1, 2, 3}, 188 | predicate: func(i int) bool { return i > 5 }, 189 | value: 0, 190 | index: -1, 191 | }, 192 | { 193 | name: "empty list", 194 | slice: []int{}, 195 | predicate: func(i int) bool { return true }, 196 | value: 0, 197 | index: -1, 198 | }, 199 | } 200 | 201 | for _, tt := range tests { 202 | t.Run(tt.name, func(t *testing.T) { 203 | l := NewList(tt.slice) 204 | index, value := l.Find(tt.predicate) 205 | 206 | if index != tt.index { 207 | t.Errorf("Find() index = %v, want %v", index, tt.index) 208 | } 209 | if value != tt.value { 210 | t.Errorf("Find() value = %v, want %v", value, tt.value) 211 | } 212 | }) 213 | } 214 | } 215 | 216 | func TestList_Length(t *testing.T) { 217 | tests := []struct { 218 | name string 219 | slice []int 220 | want int 221 | }{ 222 | { 223 | name: "non-empty list", 224 | slice: []int{1, 2, 3}, 225 | want: 3, 226 | }, 227 | { 228 | name: "empty list", 229 | slice: []int{}, 230 | want: 0, 231 | }, 232 | } 233 | 234 | for _, tt := range tests { 235 | t.Run(tt.name, func(t *testing.T) { 236 | l := NewList(tt.slice) 237 | got := l.Length() 238 | if got != tt.want { 239 | t.Errorf("Length() = %v, want %v", got, tt.want) 240 | } 241 | }) 242 | } 243 | } 244 | 245 | func TestConcat(t *testing.T) { 246 | tests := []struct { 247 | name string 248 | base []int 249 | toConcat [][]int 250 | want []int 251 | }{ 252 | { 253 | name: "single list concat", 254 | base: []int{1, 2}, 255 | toConcat: [][]int{{3, 4}}, 256 | want: []int{1, 2, 3, 4}, 257 | }, 258 | { 259 | name: "multiple lists concat", 260 | base: []int{1, 2}, 261 | toConcat: [][]int{{3, 4}, {5, 6}}, 262 | want: []int{1, 2, 3, 4, 5, 6}, 263 | }, 264 | } 265 | 266 | for _, tt := range tests { 267 | t.Run(tt.name, func(t *testing.T) { 268 | l := NewList(tt.base) 269 | var lists []*List[int] 270 | for _, slice := range tt.toConcat { 271 | lists = append(lists, NewList(slice)) 272 | } 273 | 274 | result := l.Concat(lists...) 275 | if !slices.Equal(result.ToSlice(), tt.want) { 276 | t.Errorf("Concat() = %v, want %v", result.ToSlice(), tt.want) 277 | } 278 | }) 279 | } 280 | } 281 | 282 | func TestList_Distinct(t *testing.T) { 283 | tests := []struct { 284 | name string 285 | slice []int 286 | want []int 287 | }{ 288 | { 289 | name: "distinct elements", 290 | slice: []int{1, 2, 2, 3, 3, 4}, 291 | want: []int{1, 2, 3, 4}, 292 | }, 293 | { 294 | name: "all unique elements", 295 | slice: []int{1, 2, 3}, 296 | want: []int{1, 2, 3}, 297 | }, 298 | { 299 | name: "empty list", 300 | slice: []int{}, 301 | want: []int{}, 302 | }, 303 | } 304 | 305 | for _, tt := range tests { 306 | t.Run(tt.name, func(t *testing.T) { 307 | l := NewList(tt.slice) 308 | got := l.Distinct(func(a, b int) bool { return a == b }) 309 | if !slices.Equal(got.ToSlice(), tt.want) { 310 | t.Errorf("Distinct() = %v, want %v", got.ToSlice(), tt.want) 311 | } 312 | }) 313 | } 314 | } 315 | 316 | func TestList_DropWhile(t *testing.T) { 317 | tests := []struct { 318 | name string 319 | slice []int 320 | predicate func(int) bool 321 | want []int 322 | }{ 323 | { 324 | name: "drop while less than 3", 325 | slice: []int{1, 2, 3, 4, 5}, 326 | predicate: func(i int) bool { return i < 3 }, 327 | want: []int{3, 4, 5}, 328 | }, 329 | { 330 | name: "drop while false", 331 | slice: []int{1, 2, 3}, 332 | predicate: func(i int) bool { return false }, 333 | want: []int{1, 2, 3}, 334 | }, 335 | { 336 | name: "drop all elements", 337 | slice: []int{1, 2, 3}, 338 | predicate: func(i int) bool { return true }, 339 | want: []int{}, 340 | }, 341 | } 342 | 343 | for _, tt := range tests { 344 | t.Run(tt.name, func(t *testing.T) { 345 | l := NewList(tt.slice) 346 | got := l.DropWhile(tt.predicate) 347 | if !slices.Equal(got.ToSlice(), tt.want) { 348 | t.Errorf("DropWhile() = %v, want %v", got.ToSlice(), tt.want) 349 | } 350 | }) 351 | } 352 | } 353 | 354 | func TestList_Equals(t *testing.T) { 355 | tests := []struct { 356 | name string 357 | list1 []int 358 | list2 []int 359 | equals func(int, int) bool 360 | want bool 361 | }{ 362 | { 363 | name: "equal lists", 364 | list1: []int{1, 2, 3}, 365 | list2: []int{1, 2, 3}, 366 | equals: func(a, b int) bool { return a == b }, 367 | want: true, 368 | }, 369 | { 370 | name: "different lengths", 371 | list1: []int{1, 2}, 372 | list2: []int{1, 2, 3}, 373 | equals: func(a, b int) bool { return a == b }, 374 | want: false, 375 | }, 376 | { 377 | name: "different elements", 378 | list1: []int{1, 2, 3}, 379 | list2: []int{1, 2, 4}, 380 | equals: func(a, b int) bool { return a == b }, 381 | want: false, 382 | }, 383 | } 384 | 385 | for _, tt := range tests { 386 | t.Run(tt.name, func(t *testing.T) { 387 | l1 := NewList(tt.list1) 388 | l2 := NewList(tt.list2) 389 | got := l1.Equals(l2, tt.equals) 390 | if got != tt.want { 391 | t.Errorf("Equals() = %v, want %v", got, tt.want) 392 | } 393 | }) 394 | } 395 | } 396 | 397 | func TestList_Filter(t *testing.T) { 398 | tests := []struct { 399 | name string 400 | slice []int 401 | predicate func(int) bool 402 | want []int 403 | }{ 404 | { 405 | name: "filter even numbers", 406 | slice: []int{1, 2, 3, 4, 5}, 407 | predicate: func(i int) bool { return i%2 == 0 }, 408 | want: []int{2, 4}, 409 | }, 410 | { 411 | name: "filter none", 412 | slice: []int{1, 2, 3}, 413 | predicate: func(i int) bool { return false }, 414 | want: []int{}, 415 | }, 416 | { 417 | name: "filter all", 418 | slice: []int{1, 2, 3}, 419 | predicate: func(i int) bool { return true }, 420 | want: []int{1, 2, 3}, 421 | }, 422 | } 423 | 424 | for _, tt := range tests { 425 | t.Run(tt.name, func(t *testing.T) { 426 | l := NewList(tt.slice) 427 | got := l.Filter(tt.predicate) 428 | if !slices.Equal(got.ToSlice(), tt.want) { 429 | t.Errorf("Filter() = %v, want %v", got.ToSlice(), tt.want) 430 | } 431 | }) 432 | } 433 | } 434 | 435 | func TestList_FilterNot(t *testing.T) { 436 | tests := []struct { 437 | name string 438 | slice []int 439 | predicate func(int) bool 440 | want []int 441 | }{ 442 | { 443 | name: "filter not even numbers", 444 | slice: []int{1, 2, 3, 4, 5}, 445 | predicate: func(i int) bool { return i%2 == 0 }, 446 | want: []int{1, 3, 5}, 447 | }, 448 | { 449 | name: "filter not none", 450 | slice: []int{1, 2, 3}, 451 | predicate: func(i int) bool { return false }, 452 | want: []int{1, 2, 3}, 453 | }, 454 | { 455 | name: "filter not all", 456 | slice: []int{1, 2, 3}, 457 | predicate: func(i int) bool { return true }, 458 | want: []int{}, 459 | }, 460 | } 461 | 462 | for _, tt := range tests { 463 | t.Run(tt.name, func(t *testing.T) { 464 | l := NewList(tt.slice) 465 | got := l.FilterNot(tt.predicate) 466 | if !slices.Equal(got.ToSlice(), tt.want) { 467 | t.Errorf("FilterNot() = %v, want %v", got.ToSlice(), tt.want) 468 | } 469 | }) 470 | } 471 | } 472 | 473 | func TestList_FindLast(t *testing.T) { 474 | tests := []struct { 475 | name string 476 | slice []int 477 | predicate func(int) bool 478 | wantIndex int 479 | wantValue int 480 | }{ 481 | { 482 | name: "find last even number", 483 | slice: []int{1, 2, 3, 4, 5}, 484 | predicate: func(i int) bool { return i%2 == 0 }, 485 | wantIndex: 3, 486 | wantValue: 4, 487 | }, 488 | { 489 | name: "no match", 490 | slice: []int{1, 3, 5}, 491 | predicate: func(i int) bool { return i%2 == 0 }, 492 | wantIndex: -1, 493 | wantValue: 0, 494 | }, 495 | { 496 | name: "empty list", 497 | slice: []int{}, 498 | predicate: func(i int) bool { return true }, 499 | wantIndex: -1, 500 | wantValue: 0, 501 | }, 502 | } 503 | 504 | for _, tt := range tests { 505 | t.Run(tt.name, func(t *testing.T) { 506 | l := NewList(tt.slice) 507 | gotIndex, gotValue := l.FindLast(tt.predicate) 508 | if gotIndex != tt.wantIndex { 509 | t.Errorf("FindLast() index = %v, want %v", gotIndex, tt.wantIndex) 510 | } 511 | if gotValue != tt.wantValue { 512 | t.Errorf("FindLast() value = %v, want %v", gotValue, tt.wantValue) 513 | } 514 | }) 515 | } 516 | } 517 | 518 | func TestList_Init(t *testing.T) { 519 | tests := []struct { 520 | name string 521 | slice []int 522 | want []int 523 | }{ 524 | { 525 | name: "non-empty list", 526 | slice: []int{1, 2, 3, 4}, 527 | want: []int{1, 2, 3}, 528 | }, 529 | { 530 | name: "single element", 531 | slice: []int{1}, 532 | want: []int{}, 533 | }, 534 | { 535 | name: "empty list", 536 | slice: []int{}, 537 | want: []int{}, 538 | }, 539 | } 540 | 541 | for _, tt := range tests { 542 | t.Run(tt.name, func(t *testing.T) { 543 | l := NewList(tt.slice) 544 | got := l.Init() 545 | if !slices.Equal(got.ToSlice(), tt.want) { 546 | t.Errorf("Init() = %v, want %v", got.ToSlice(), tt.want) 547 | } 548 | }) 549 | } 550 | } 551 | 552 | func TestList_Last(t *testing.T) { 553 | tests := []struct { 554 | name string 555 | slice []int 556 | want int 557 | wantErr bool 558 | }{ 559 | { 560 | name: "non-empty list", 561 | slice: []int{1, 2, 3}, 562 | want: 3, 563 | wantErr: false, 564 | }, 565 | { 566 | name: "empty list", 567 | slice: []int{}, 568 | want: 0, 569 | wantErr: true, 570 | }, 571 | } 572 | 573 | for _, tt := range tests { 574 | t.Run(tt.name, func(t *testing.T) { 575 | l := NewList(tt.slice) 576 | got, err := l.Last() 577 | if tt.wantErr { 578 | if err == nil { 579 | t.Errorf("Last() error = nil, want error") 580 | } 581 | } else { 582 | if err != nil { 583 | t.Errorf("Last() error = %v, want no error", err) 584 | } 585 | if got != tt.want { 586 | t.Errorf("Last() = %v, want %v", got, tt.want) 587 | } 588 | } 589 | }) 590 | } 591 | } 592 | 593 | func TestList_Partition(t *testing.T) { 594 | tests := []struct { 595 | name string 596 | slice []int 597 | predicate func(int) bool 598 | wantLeft []int 599 | wantRight []int 600 | }{ 601 | { 602 | name: "partition even and odd", 603 | slice: []int{1, 2, 3, 4, 5}, 604 | predicate: func(i int) bool { return i%2 == 0 }, 605 | wantLeft: []int{2, 4}, 606 | wantRight: []int{1, 3, 5}, 607 | }, 608 | { 609 | name: "all elements satisfy predicate", 610 | slice: []int{2, 4, 6}, 611 | predicate: func(i int) bool { return i%2 == 0 }, 612 | wantLeft: []int{2, 4, 6}, 613 | wantRight: []int{}, 614 | }, 615 | { 616 | name: "no elements satisfy predicate", 617 | slice: []int{1, 3, 5}, 618 | predicate: func(i int) bool { return i%2 == 0 }, 619 | wantLeft: []int{}, 620 | wantRight: []int{1, 3, 5}, 621 | }, 622 | } 623 | 624 | for _, tt := range tests { 625 | t.Run(tt.name, func(t *testing.T) { 626 | l := NewList(tt.slice) 627 | left, right := l.Partition(tt.predicate) 628 | if !slices.Equal(left.ToSlice(), tt.wantLeft) { 629 | t.Errorf("Partition() left = %v, want %v", left.ToSlice(), tt.wantLeft) 630 | } 631 | if !slices.Equal(right.ToSlice(), tt.wantRight) { 632 | t.Errorf("Partition() right = %v, want %v", right.ToSlice(), tt.wantRight) 633 | } 634 | }) 635 | } 636 | } 637 | 638 | func TestList_Reverse(t *testing.T) { 639 | tests := []struct { 640 | name string 641 | slice []int 642 | want []int 643 | }{ 644 | { 645 | name: "non-empty list", 646 | slice: []int{1, 2, 3, 4}, 647 | want: []int{4, 3, 2, 1}, 648 | }, 649 | { 650 | name: "single element", 651 | slice: []int{1}, 652 | want: []int{1}, 653 | }, 654 | { 655 | name: "empty list", 656 | slice: []int{}, 657 | want: []int{}, 658 | }, 659 | } 660 | 661 | for _, tt := range tests { 662 | t.Run(tt.name, func(t *testing.T) { 663 | l := NewList(tt.slice) 664 | got := l.Reverse() 665 | if !slices.Equal(got.ToSlice(), tt.want) { 666 | t.Errorf("Reverse() = %v, want %v", got.ToSlice(), tt.want) 667 | } 668 | }) 669 | } 670 | } 671 | 672 | func TestList_Reject(t *testing.T) { 673 | tests := []struct { 674 | name string 675 | slice []int 676 | predicate func(int) bool 677 | want []int 678 | }{ 679 | { 680 | name: "filter not even numbers", 681 | slice: []int{1, 2, 3, 4, 5}, 682 | predicate: func(i int) bool { return i%2 == 0 }, 683 | want: []int{1, 3, 5}, 684 | }, 685 | { 686 | name: "filter not none", 687 | slice: []int{1, 2, 3}, 688 | predicate: func(i int) bool { return false }, 689 | want: []int{1, 2, 3}, 690 | }, 691 | { 692 | name: "filter not all", 693 | slice: []int{1, 2, 3}, 694 | predicate: func(i int) bool { return true }, 695 | want: []int{}, 696 | }, 697 | } 698 | 699 | for _, tt := range tests { 700 | t.Run(tt.name, func(t *testing.T) { 701 | l := NewList(tt.slice) 702 | got := l.Reject(tt.predicate) 703 | if !slices.Equal(got.ToSlice(), tt.want) { 704 | t.Errorf("Reject() = %v, want %v", got.ToSlice(), tt.want) 705 | } 706 | }) 707 | } 708 | } 709 | 710 | func TestList_SplitAt(t *testing.T) { 711 | tests := []struct { 712 | name string 713 | slice []int 714 | n int 715 | wantLeft []int 716 | wantRight []int 717 | }{ 718 | { 719 | name: "split in middle", 720 | slice: []int{1, 2, 3, 4, 5}, 721 | n: 2, 722 | wantLeft: []int{1, 2, 3}, 723 | wantRight: []int{4, 5}, 724 | }, 725 | { 726 | name: "split at start", 727 | slice: []int{1, 2, 3}, 728 | n: 0, 729 | wantLeft: []int{1}, 730 | wantRight: []int{2, 3}, 731 | }, 732 | { 733 | name: "split at end", 734 | slice: []int{1, 2, 3}, 735 | n: 3, 736 | wantLeft: []int{1, 2, 3}, 737 | wantRight: []int{}, 738 | }, 739 | } 740 | 741 | for _, tt := range tests { 742 | t.Run(tt.name, func(t *testing.T) { 743 | l := NewList(tt.slice) 744 | left, right := l.SplitAt(tt.n) 745 | if !slices.Equal(left.ToSlice(), tt.wantLeft) { 746 | t.Errorf("SplitAt() left = %v, want %v", left.ToSlice(), tt.wantLeft) 747 | } 748 | if !slices.Equal(right.ToSlice(), tt.wantRight) { 749 | t.Errorf("SplitAt() right = %v, want %v", right.ToSlice(), tt.wantRight) 750 | } 751 | }) 752 | } 753 | } 754 | 755 | func TestList_Take(t *testing.T) { 756 | tests := []struct { 757 | name string 758 | slice []int 759 | n int 760 | want []int 761 | }{ 762 | { 763 | name: "take positive number", 764 | slice: []int{1, 2, 3, 4, 5}, 765 | n: 3, 766 | want: []int{1, 2, 3}, 767 | }, 768 | { 769 | name: "take zero", 770 | slice: []int{1, 2, 3}, 771 | n: 0, 772 | want: []int{}, 773 | }, 774 | { 775 | name: "take more than length", 776 | slice: []int{1, 2, 3}, 777 | n: 5, 778 | want: []int{1, 2, 3}, 779 | }, 780 | } 781 | 782 | for _, tt := range tests { 783 | t.Run(tt.name, func(t *testing.T) { 784 | l := NewList(tt.slice) 785 | got := l.Take(tt.n) 786 | if !slices.Equal(got.ToSlice(), tt.want) { 787 | t.Errorf("Take() = %v, want %v", got.ToSlice(), tt.want) 788 | } 789 | }) 790 | } 791 | } 792 | 793 | func TestList_TakeRight(t *testing.T) { 794 | tests := []struct { 795 | name string 796 | slice []int 797 | n int 798 | want []int 799 | }{ 800 | { 801 | name: "take right positive number", 802 | slice: []int{1, 2, 3, 4, 5}, 803 | n: 3, 804 | want: []int{3, 4, 5}, 805 | }, 806 | { 807 | name: "take right zero", 808 | slice: []int{1, 2, 3}, 809 | n: 0, 810 | want: []int{}, 811 | }, 812 | { 813 | name: "take right more than length", 814 | slice: []int{1, 2, 3}, 815 | n: 5, 816 | want: []int{1, 2, 3}, 817 | }, 818 | } 819 | 820 | for _, tt := range tests { 821 | t.Run(tt.name, func(t *testing.T) { 822 | l := NewList(tt.slice) 823 | got := l.TakeRight(tt.n) 824 | if !slices.Equal(got.ToSlice(), tt.want) { 825 | t.Errorf("TakeRight() = %v, want %v", got.ToSlice(), tt.want) 826 | } 827 | }) 828 | } 829 | } 830 | 831 | func TestList_Tail(t *testing.T) { 832 | tests := []struct { 833 | name string 834 | slice []int 835 | want []int 836 | }{ 837 | { 838 | name: "non-empty list", 839 | slice: []int{1, 2, 3, 4}, 840 | want: []int{2, 3, 4}, 841 | }, 842 | { 843 | name: "single element", 844 | slice: []int{1}, 845 | want: []int{}, 846 | }, 847 | { 848 | name: "empty list", 849 | slice: []int{}, 850 | want: []int{}, 851 | }, 852 | } 853 | 854 | for _, tt := range tests { 855 | t.Run(tt.name, func(t *testing.T) { 856 | l := NewList(tt.slice) 857 | got := l.Tail() 858 | if !slices.Equal(got.ToSlice(), tt.want) { 859 | t.Errorf("Tail() = %v, want %v", got.ToSlice(), tt.want) 860 | } 861 | }) 862 | } 863 | } 864 | 865 | func TestList_Shuffle(t *testing.T) { 866 | tests := []struct { 867 | name string 868 | input []int 869 | }{ 870 | {name: "basic shuffle", input: []int{1, 2, 3, 4, 5}}, 871 | {name: "empty list", input: []int{}}, 872 | {name: "single element", input: []int{22}}, 873 | {name: "duplicate elements", input: []int{1, 1, 2, 2, 3, 3}}, 874 | } 875 | 876 | for _, tt := range tests { 877 | t.Run(tt.name, func(t *testing.T) { 878 | list := NewList(tt.input) 879 | shuffled := list.Shuffle() 880 | 881 | // Verify length 882 | if shuffled.Length() != list.Length() { 883 | t.Errorf("Shuffle() length = %d, want %d", shuffled.Length(), list.Length()) 884 | } 885 | 886 | // Verify element preservation 887 | originalMap := make(map[int]int) 888 | shuffledMap := make(map[int]int) 889 | for _, v := range list.ToSlice() { 890 | originalMap[v]++ 891 | } 892 | for _, v := range shuffled.ToSlice() { 893 | shuffledMap[v]++ 894 | } 895 | if !reflect.DeepEqual(originalMap, shuffledMap) { 896 | t.Errorf("Shuffle() elements mismatch, got %v, want %v", shuffledMap, originalMap) 897 | } 898 | }) 899 | } 900 | } 901 | 902 | func TestList_ShuffleRandomization(t *testing.T) { 903 | input := []int{1, 2, 3, 4, 5, 6} 904 | list := NewList(input) 905 | iterations := 10000 906 | 907 | sameOrderCount := 0 908 | for i := 0; i < iterations; i++ { 909 | shuffled := list.Shuffle() 910 | if reflect.DeepEqual(input, shuffled.ToSlice()) { 911 | sameOrderCount++ 912 | } 913 | } 914 | 915 | // Expect the same order to appear <5% of the time for small lists 916 | threshold := 0.05 * float64(iterations) 917 | if float64(sameOrderCount) > threshold { 918 | t.Errorf("Shuffle() produced the same order %d times, exceeding threshold %f", sameOrderCount, threshold) 919 | } 920 | } 921 | 922 | func TestList_ShuffleDistribution(t *testing.T) { 923 | input := []int{1, 2, 3, 4} 924 | list := NewList(input) 925 | 926 | // Map to track the position of each value 927 | positionCounts := make([]map[int]int, len(input)) 928 | for i := range positionCounts { 929 | positionCounts[i] = make(map[int]int) 930 | } 931 | 932 | iterations := 10000 933 | for i := 0; i < iterations; i++ { 934 | shuffled := list.Shuffle() 935 | for pos, val := range shuffled.ToSlice() { 936 | positionCounts[pos][val]++ 937 | } 938 | } 939 | 940 | // Validate uniform distribution 941 | expectedCount := iterations / len(input) 942 | tolerance := 0.1 * float64(expectedCount) 943 | for pos, counts := range positionCounts { 944 | for val, count := range counts { 945 | if float64(count) < float64(expectedCount)-tolerance || float64(count) > float64(expectedCount)+tolerance { 946 | t.Errorf("Value %d appeared at position %d %d times, which is outside the tolerance range [%f, %f]", 947 | val, pos, count, float64(expectedCount)-tolerance, float64(expectedCount)+tolerance) 948 | } 949 | } 950 | } 951 | } 952 | --------------------------------------------------------------------------------