From c8d2295a058c64233f88b71b57d51cac0b0024ee Mon Sep 17 00:00:00 2001 From: miniLCT <920861971@qq.com> Date: Sun, 7 Apr 2024 17:10:12 +0800 Subject: [PATCH] gcontainers: gheap,glist,gmap,gslice etc.. --- gcontainers/gheap/heap.go | 96 ++++++++++ gcontainers/gheap/heap_test.go | 81 +++++++++ gcontainers/glist/list.go | 87 +++++++++ gcontainers/glist/list_test.go | 42 +++++ gcontainers/gmap/map.go | 105 +++++++++++ gcontainers/gmap/map_test.go | 225 ++++++++++++++++++++++++ gcontainers/gqueue/const.go | 7 + gcontainers/gqueue/fifo.go | 99 +++++++++++ gcontainers/gqueue/fifo_test.go | 54 ++++++ gcontainers/gring/ring.go | 135 ++++++++++++++ gcontainers/gring/ring_test.go | 224 +++++++++++++++++++++++ gcontainers/gset/set.go | 60 +++++++ gcontainers/gset/set_test.go | 32 ++++ gcontainers/gslice/silice_test.go | 283 ++++++++++++++++++++++++++++++ gcontainers/gslice/slice.go | 184 +++++++++++++++++++ 15 files changed, 1714 insertions(+) create mode 100644 gcontainers/gheap/heap.go create mode 100644 gcontainers/gheap/heap_test.go create mode 100644 gcontainers/glist/list.go create mode 100644 gcontainers/glist/list_test.go create mode 100644 gcontainers/gmap/map.go create mode 100644 gcontainers/gmap/map_test.go create mode 100644 gcontainers/gqueue/const.go create mode 100644 gcontainers/gqueue/fifo.go create mode 100644 gcontainers/gqueue/fifo_test.go create mode 100644 gcontainers/gring/ring.go create mode 100644 gcontainers/gring/ring_test.go create mode 100644 gcontainers/gset/set.go create mode 100644 gcontainers/gset/set_test.go create mode 100644 gcontainers/gslice/silice_test.go create mode 100644 gcontainers/gslice/slice.go diff --git a/gcontainers/gheap/heap.go b/gcontainers/gheap/heap.go new file mode 100644 index 0000000..2b7f890 --- /dev/null +++ b/gcontainers/gheap/heap.go @@ -0,0 +1,96 @@ +package gheap + +import "github.com/miniLCT/gosb/gogenerics/constraints" + +// Heap is the generics implementation of heap + +type Heap[T any] struct { + data []T + less constraints.Less[T] +} + +// New constructs a new heap +func New[T any](less constraints.Less[T]) *Heap[T] { + return &Heap[T]{ + data: make([]T, 0), + less: less, + } +} + +// NewWithData build a heap tree with data +func NewWithData[T any](data []T, less constraints.Less[T]) *Heap[T] { + h := New(less) + h.data = data + heapSort(h.data, less) + return h +} + +// siftDown implements the heap property on v[lo:hi]. +func siftDown[T any](x []T, index int, less constraints.Less[T]) { + for { + left := (index * 2) + 1 + right := left + 1 + if left >= len(x) { + break + } + c := left + if len(x) > right && less(x[right], x[left]) { + c = right + } + if less(x[index], x[c]) { + break + } + x[c], x[index] = x[index], x[c] + index = c + } +} + +func siftUp[T any](x []T, index int, less constraints.Less[T]) { + for index > 0 { + p := (index - 1) / 2 + if less(x[p], x[index]) { + break + } + x[p], x[index] = x[index], x[p] + index = p + } +} + +// heapSort is min-heap sort +func heapSort[T any](v []T, less constraints.Less[T]) { + n := len(v) + for i := n/2 - 1; i >= 0; i-- { + siftDown(v, i, less) + } + + // Build heap with greatest element at top. + // for i := (len(v) - 1) / 2; i >= 0; i-- { + // siftDown(v, i, len(v), less) + // } + + // // Pop elements into end of v. + // for i := len(v) - 1; i >= 1; i-- { + // v[0], v[i] = v[i], v[0] + // siftDown(v[:i], 0, len(v), less) // BUG + // } +} + +// Push pushes the element v onto the heap. +func Push[T any](h *Heap[T], v T) { + x := &h.data + (*x) = append((*x), v) + siftUp(*x, len(*x)-1, h.less) + h.data = (*x) +} + +// Pop removes the minimum element from the heap and returns it. +func Pop[T any](h *Heap[T]) T { + x := &h.data + ret := (*x)[0] + (*x)[0], *x = (*x)[len(*x)-1], (*x)[:len(*x)-1] + if len(*x) > 0 { + siftDown((*x), 0, h.less) + } + h.data = (*x) + return ret +} diff --git a/gcontainers/gheap/heap_test.go b/gcontainers/gheap/heap_test.go new file mode 100644 index 0000000..434fd84 --- /dev/null +++ b/gcontainers/gheap/heap_test.go @@ -0,0 +1,81 @@ +package gheap + +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/miniLCT/gosb/gcontainers/gslice" +) + +var charSet = []string{ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", +} + +func genString() string { + l := 4 + var str string + for i := 0; i < l; i++ { + str += charSet[rand.Intn(len(charSet))] + } + return str +} + +type Node struct { + Name string + Count int +} + +func TestHeap(t *testing.T) { + assert := assert.New(t) + t.Parallel() + + rand.Seed(time.Now().Unix()) + nds := []*Node{} + for i := 0; i < 10; i++ { + nds = append(nds, &Node{ + Name: genString(), + Count: rand.Intn(4), + }) + } + less := func(a, b *Node) bool { + if a.Count != b.Count { + return a.Count > b.Count + } + return a.Name < b.Name + } + hp := NewWithData(nds, less) + + tmp := []*Node{} + for i := 0; i <= 100; i++ { + Push(hp, &Node{ + Name: genString(), + Count: rand.Intn(4), + }) + for len(hp.data) > 0 { + v := Pop(hp) + tmp = append(tmp, v) + } + assert.True(gslice.IsSortedFunc(tmp, less)) + for _, v := range tmp { + Push(hp, v) + } + tmp = make([]*Node, 0) + } + + for i := 0; i <= 100; i++ { + tmpSlice := gslice.Copy(hp.data) + gslice.Shuffle(tmpSlice) + h := NewWithData(tmpSlice[:rand.Int()%len(tmpSlice)], less) + tpchecks := make([]*Node, 0) + for len(h.data) > 0 { + v := Pop(h) + tpchecks = append(tpchecks, v) + } + assert.True(gslice.IsSortedFunc(tpchecks, less)) + } +} diff --git a/gcontainers/glist/list.go b/gcontainers/glist/list.go new file mode 100644 index 0000000..ea2794e --- /dev/null +++ b/gcontainers/glist/list.go @@ -0,0 +1,87 @@ +package glist + +// Node is a node in the linked list +type Node[T any] struct { + Value T + Prev, Next *Node[T] +} + +// List is a doubly-linked list +type List[T any] struct { + Front *Node[T] + Back *Node[T] +} + +// New returns an empty linked list +func New[T any]() *List[T] { + return &List[T]{} +} + +// PushBack adds v to the end of the list +func PushBack[T any](l *List[T], v T) { + PushBackNode(l, &Node[T]{ + Value: v, + }) +} + +// PushBackNode adds the node nd to the back of the list +func PushBackNode[T any](l *List[T], nd *Node[T]) { + nd.Next = nil + nd.Prev = l.Back + if l.Back != nil { + l.Back.Next = nd + } else { + l.Front = nd + } + l.Back = nd +} + +// PushFront adds v to the beginning of the list +func PushFront[T any](l *List[T], v T) { + PushFrontNode(l, &Node[T]{ + Value: v, + }) +} + +// PushFrontNode adds the node nd to the beginning of the list +func PushFrontNode[T any](l *List[T], nd *Node[T]) { + nd.Next = l.Front + nd.Prev = nil + if l.Front != nil { + l.Front.Prev = nd + } else { + l.Back = nd + } + l.Front = nd +} + +// Remove removes the node nd from the list +func Remove[T any](l *List[T], nd *Node[T]) { + if nd.Next != nil { + nd.Next.Prev = nd.Prev + } else { + l.Back = nd.Prev + } + + if nd.Prev != nil { + nd.Prev.Next = nd.Next + } else { + l.Front = nd.Next + } +} + +// Range iterates over the list and calls f for each element +func Range[T any](nd *Node[T], f func(T)) { + for nd != nil { + f(nd.Value) + nd = nd.Next + } +} + +// RangeReverse iterates over the list in reverse order and calls f for each element +func RangeReverse[T any](nd *Node[T], f func(T)) { + for nd != nil { + f(nd.Value) + nd = nd.Prev + } +} diff --git a/gcontainers/glist/list_test.go b/gcontainers/glist/list_test.go new file mode 100644 index 0000000..6eb6b1b --- /dev/null +++ b/gcontainers/glist/list_test.go @@ -0,0 +1,42 @@ +package glist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestList(t *testing.T) { + l := New[int]() + for i := 0; i < 5; i++ { + PushFront(l, i) + } + for i := 0; i < 5; i++ { + PushBack(l, i) + } + s1 := make([]int, 0, 10) + Range(l.Front, func(i int) { + s1 = append(s1, i) + }) + s2 := make([]int, 0, 10) + RangeReverse(l.Back, func(i int) { + s2 = append(s2, i) + }) + t.Logf("s1=%v\n", s1) + t.Logf("s2=%v\n", s2) + + assert := assert.New(t) + assert.Equal(s2, s1) + + Remove(l, l.Back) + Remove(l, l.Front) + + lenL := 0 + Range(l.Front, func(i int) { + lenL++ + }) + assert.Equal(8, lenL) + + assert.Equal(3, l.Front.Value) + assert.Equal(3, l.Back.Value) +} diff --git a/gcontainers/gmap/map.go b/gcontainers/gmap/map.go new file mode 100644 index 0000000..97051ad --- /dev/null +++ b/gcontainers/gmap/map.go @@ -0,0 +1,105 @@ +package gmap + +import "github.com/miniLCT/gosb/gogenerics/constraints" + +// Keys returns a slice of keys from the map. Note that the keys will be an indeterminate order +func Keys[K comparable, V any](m map[K]V) []K { + keys := make([]K, 0, len(m)) + + for k := range m { + keys = append(keys, k) + } + return keys +} + +// Values returns a slice of values from the map. Note that the values will be an indeterminate order +func Values[K comparable, V any](m map[K]V) []V { + values := make([]V, 0, len(m)) + + for _, v := range m { + values = append(values, v) + } + return values +} + +// Copy returns a shallow copy of this map +func Copy[K comparable, V any](m map[K]V) map[K]V { + res := make(map[K]V, len(m)) + + for k, v := range m { + res[k] = v + } + return res +} + +// Len returns the number of elements of this map +func Len[K comparable, V any](m map[K]V) int { + return len(m) +} + +// Contains returns whether this map contains the specified key. +// Note that if the key is a pointer, the result may be unexpected +func Contains[K comparable, V any](m map[K]V, e K) bool { + _, ok := m[e] + return ok +} + +// Clear removes all the elements from this map +func Clear[K comparable, V any](m map[K]V) { + for k := range m { + delete(m, k) + } +} + +// Map2Entries transforms a map into slice of key-value pairs +func Map2Entries[K comparable, V any](m map[K]V) []constraints.Entry[K, V] { + entries := make([]constraints.Entry[K, V], 0, len(m)) + + for k, v := range m { + entries = append(entries, constraints.Entry[K, V]{ + Key: k, + Value: v, + }) + } + return entries +} + +// Entries2Map transforms a slice of key-value pairs into a map +func Entries2Map[K comparable, V any](entries []constraints.Entry[K, V]) map[K]V { + m := make(map[K]V, len(entries)) + + for _, e := range entries { + m[e.Key] = e.Value + } + return m +} + +// Equal returns whether two maps contain the same key-value pairs +func Equal[K, V comparable](m1, m2 map[K]V) bool { + if Len(m1) != Len(m2) { + return false + } + + for k, v1 := range m1 { + v2, ok := m1[k] + if !ok || v2 != v1 { + return false + } + } + return true +} + +// EqualWithFunc returns whether two maps contain the same key-value pairs with the given equal function +func EqualWithFunc[K comparable, V1, V2 any](m1 map[K]V1, m2 map[K]V2, eqFunc func(V1, V2) bool) bool { + if Len(m1) != Len(m2) { + return false + } + + for k, v1 := range m1 { + v2, ok := m2[k] + if !ok || !eqFunc(v1, v2) { + return false + } + } + return true +} diff --git a/gcontainers/gmap/map_test.go b/gcontainers/gmap/map_test.go new file mode 100644 index 0000000..b492a81 --- /dev/null +++ b/gcontainers/gmap/map_test.go @@ -0,0 +1,225 @@ +package gmap + +import ( + "fmt" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/miniLCT/gosb/gogenerics/constraints" +) + +func TestKeys(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + mp1 := map[string]any{ + "a": 1, + "b": "b", + "c": []string{"c"}, + "100": map[string]any{}, + "11": true, + } + + mp2 := map[float64]any{ + 1.1: "1", + 100.0: 114, + -514: 191.8810, + -0: true, + } + + keys1 := Keys(mp1) + keys2 := Keys(mp2) + // TODO: maybe can use this package func to sort + sort.Strings(keys1) + stdKeys1 := []string{"100", "11", "a", "b", "c"} + sort.Float64s(keys2) + stdKeys2 := []float64{-514, -0, 1.1, 100.00000} + + assert.Equal(stdKeys1, keys1) + assert.Equal(stdKeys2, keys2) +} + +func TestValues(t *testing.T) { + t.Parallel() + // assert := assert.New(t) + + mp1 := map[string]any{ + "a": 1, + "b": "b", + "c": []string{"c"}, + "100": map[string]any{}, + "11": true, + } + + mp2 := map[float64]any{ + 1.1: "1", + 100.0: 114, + -514: 191.8810, + -0: true, + } + + values1 := Values(mp1) + values2 := Values(mp2) + // TODO: fix it + t.Logf("%v\n", values1) + t.Logf("%v\n", values2) + // fmt.Println(values1...) + // fmt.Println(values2) + // assert.Equal([]any{1, "b", []string{"c"}, map[string]any{}, true}, values1) + // assert.Equal([]any{"1", 114, 191.881, true}, values2) +} + +func TestCopy(t *testing.T) { + assert := assert.New(t) + + mp1 := map[string]any{ + "a": 1, + "b": "b", + "c": []string{"c"}, + "100": map[string]any{}, + "11": true, + } + mp2 := Copy(mp1) + + assert.Equal(mp1, mp2) + assert.NotEqual(fmt.Sprintf("%p", &mp1), fmt.Sprintf("%p", &mp2), "copy should not be the same pointer") +} + +func TestMap2Entries(t *testing.T) { + assert := assert.New(t) + + mp := map[int64]any{ + -1: "-1", + 0: 0, + 1: true, + 2: []float64{2.0}, + 3: map[string]any{"3": 3}, + } + entries := Map2Entries(mp) + sort.Slice(entries, func(i, j int) bool { + return entries[i].Key < entries[j].Key + }) + stdEntries := []constraints.Entry[int64, any]{ + {Key: -1, Value: "-1"}, + {Key: 0, Value: 0}, + {Key: 1, Value: true}, + {Key: 2, Value: []float64{2.0}}, + {Key: 3, Value: map[string]any{"3": 3}}, + } + + assert.Equal(stdEntries, entries) +} + +func TestEntries2Map(t *testing.T) { + assert := assert.New(t) + + entries := []constraints.Entry[int64, any]{ + {Key: -1, Value: "-1"}, + {Key: 0, Value: 0}, + {Key: 1, Value: true}, + {Key: 2, Value: []float64{2.0}}, + {Key: 3, Value: map[string]any{"3": 3}}, + } + mp := Entries2Map(entries) + stdMp := map[int64]any{ + -1: "-1", + 0: 0, + 1: true, + 2: []float64{2.0}, + 3: map[string]any{"3": 3}, + } + + assert.Equal(stdMp, mp) +} + +func TestEqual(t *testing.T) { + assert := assert.New(t) + + mp1 := map[string]string{ + "a": "1", + "b": "b", + } + mp2 := map[string]string{ + "a": "1", + } + mp3 := map[string]string{ + "a": "1", + "b": "b", + } + + assert.False(Equal(mp1, mp2)) + assert.False(Equal(mp2, mp3)) + assert.True(Equal(mp1, mp3)) +} + +func TestLen(t *testing.T) { + assert := assert.New(t) + + mp := map[string]string{ + "a": "***", + "b": "b", + } + assert.Equal(2, Len(mp)) + mp["c"] = "c" + assert.Equal(3, Len(mp)) + delete(mp, "ddd") + assert.Equal(3, Len(mp)) + delete(mp, "c") + assert.Equal(2, Len(mp)) +} + +func TestEqualWithFunc(t *testing.T) { + assert := assert.New(t) + + mp1 := map[string]string{ + "a": "1", + "b": "b", + } + mp2 := map[string]string{ + "a": "1", + "b": "bbb", + } + mp3 := Copy(mp1) + + assert.Equal(false, EqualWithFunc(mp1, mp2, func(a, b string) bool { + return a == b + })) + assert.Equal(true, EqualWithFunc(mp1, mp3, func(a, b string) bool { + return a == b + })) +} + +func TestContains(t *testing.T) { + assert := assert.New(t) + + type Node struct { + Name string + } + mp := map[Node]any{ + {Name: "a"}: "1", + } + assert.Equal(true, Contains(mp, Node{Name: "a"})) + assert.Equal(false, Contains(mp, Node{Name: "b"})) + Clear(mp) + assert.Equal(false, Contains(mp, Node{Name: "a"})) +} + +func TestClear(t *testing.T) { + assert := assert.New(t) + + mp := map[string]any{ + "a": "1", + "b": 123, + } + t.Logf("now len: %d\n", len(mp)) + t.Logf("mp[%v]===%v", "a", mp["a"]) + t.Logf("mp[%v]===%v", "b", mp["b"]) + t.Logf("mp[%v]===%v", "c", mp["c"]) + Clear(mp) + t.Logf("after clear len: %d\n", len(mp)) + t.Logf("mp[%v]===%v", "a", mp["a"]) + assert.Equal(0, len(mp)) + assert.Equal(map[string]any{}, mp) +} diff --git a/gcontainers/gqueue/const.go b/gcontainers/gqueue/const.go new file mode 100644 index 0000000..0562b2b --- /dev/null +++ b/gcontainers/gqueue/const.go @@ -0,0 +1,7 @@ +package gqueue + +import "errors" + +var ( + ErrorEmptyQueue = errors.New("empty queue") +) diff --git a/gcontainers/gqueue/fifo.go b/gcontainers/gqueue/fifo.go new file mode 100644 index 0000000..9a61a25 --- /dev/null +++ b/gcontainers/gqueue/fifo.go @@ -0,0 +1,99 @@ +package gqueue + +import ( + "github.com/miniLCT/gosb/gcontainers/glist" + "github.com/miniLCT/gosb/gogenerics/constraints" +) + +// Queue is a simple FIFO queue, not thread-safe + +type Queue[T any] struct { + list *glist.List[T] + length int +} + +// New returns an empty FIFO queue +func New[T any]() *Queue[T] { + return &Queue[T]{ + list: glist.New[T](), + length: 0, + } +} + +// Len returns the number of items currently in the queue +func Len[T any](q *Queue[T]) int { + return q.length +} + +// Push adds an element to the tail of the queue, reserves the return type for future extension +func Push[T any](q *Queue[T], v T) error { + glist.PushBack(q.list, v) + q.length++ + return nil +} + +// Pop removes an element from the head of the queue +func Pop[T any](q *Queue[T]) (T, error) { + if IsEmpty(q) { + return constraints.Empty[T](), ErrorEmptyQueue + } + val := q.list.Front.Value + glist.Remove(q.list, q.list.Front) + q.length-- + return val, nil +} + +// Peek retrieves but does not remove the head of the queue +func Peek[T any](q *Queue[T]) (T, error) { + if IsEmpty(q) { + // todo:return panic or error? + return constraints.Empty[T](), ErrorEmptyQueue + } + return q.list.Front.Value, nil +} + +// PeekAll returns all elements in the queue without removing them +func PeekAll[T any](q *Queue[T]) []T { + res := make([]T, q.length) + var idx int + glist.Range(q.list.Front, func(v T) { + res[idx] = v + idx++ + }) + return res +} + +// IsEmpty returns whether the queue is empty +func IsEmpty[T any](q *Queue[T]) bool { + return q.length == 0 +} + +// Clear empties the queue +func Clear[T any](q *Queue[T]) { + q.list = glist.New[T]() + q.length = 0 +} + +// Iterator returns a channel that will be filled with the elements +func Iterator[T any](q *Queue[T]) <-chan T { + ch := make(chan T, q.length) + defer close(ch) + for { + val, err := Pop(q) + if err != nil { + break + } + ch <- val + } + return ch +} + +func Gen[T any, S ~[]T](s S) *Queue[T] { + q := New[T]() + for _, v := range s { + _ = Push(q, v) + } + return q +} + +// todo: wrap queue with interface diff --git a/gcontainers/gqueue/fifo_test.go b/gcontainers/gqueue/fifo_test.go new file mode 100644 index 0000000..0fc9a0c --- /dev/null +++ b/gcontainers/gqueue/fifo_test.go @@ -0,0 +1,54 @@ +package gqueue + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFifo(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + sli := []int{1, 2, 3} + fifo := Gen(sli) + assert.False(IsEmpty(fifo)) + assert.Equal(3, Len(fifo)) + + e, err := Pop(fifo) + assert.Equal(1, e) + assert.Nil(err) + assert.Equal(2, Len(fifo)) + + _ = Push(fifo, 4) + assert.Equal(3, Len(fifo)) + + e, err = Peek(fifo) + assert.Equal(2, e) + assert.Nil(err) + + es := PeekAll(fifo) + assert.Equal([]int{2, 3, 4}, es) + assert.Nil(err) + + ch := Iterator(fifo) + ss := make([]int, 0, len(ch)) + assert.Equal(3, len(ch)) + for e := range ch { + ss = append(ss, e) + } + assert.Equal([]int{2, 3, 4}, ss) + + Clear(fifo) + assert.Equal(0, Len(fifo)) + assert.True(IsEmpty(fifo)) + + emptyQueue := New[string]() + assert.True(IsEmpty(emptyQueue)) + e2, err := Pop(emptyQueue) + assert.Equal("", e2) + assert.NotNil(err) + e2, err = Peek(emptyQueue) + assert.Equal("", e2) + assert.NotNil(err) +} diff --git a/gcontainers/gring/ring.go b/gcontainers/gring/ring.go new file mode 100644 index 0000000..aeab539 --- /dev/null +++ b/gcontainers/gring/ring.go @@ -0,0 +1,135 @@ +// Package gring implements operations on circular lists support generics. Note not safety in concurrent operation. +package gring + +import "github.com/miniLCT/gosb/gogenerics/constraints" + +// A Ring is an element of a circular list, or ring. +// Rings do not have a beginning or end; a pointer to any ring element +// serves as reference to the entire ring. Empty rings are represented +// as nil Ring pointers. The zero value for a Ring is a one-element +// ring with a nil Value. + +type Ring[V any] struct { + next, prev *Ring[V] + Value V // for use by client; untouched by this library +} + +func (r *Ring[V]) init() *Ring[V] { + r.next = r + r.prev = r + return r +} + +// Next returns the next ring element. r must not be empty. +func (r *Ring[V]) Next() *Ring[V] { + if r.next == nil { + return r.init() + } + return r.next +} + +// Prev returns the previous ring element. r must not be empty. +func (r *Ring[V]) Prev() *Ring[V] { + if r.next == nil { + return r.init() + } + return r.prev +} + +// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0) +// in the ring and returns that ring element. r must not be empty. +func (r *Ring[V]) Move(n int) *Ring[V] { + if r.next == nil { + return r.init() + } + switch { + case n < 0: + for ; n < 0; n++ { + r = r.prev + } + case n > 0: + for ; n > 0; n-- { + r = r.next + } + } + return r +} + +// New creates a ring of n elements. +func New[V any](n int) *Ring[V] { + if n <= 0 { + return nil + } + r := new(Ring[V]) + p := r + for i := 1; i < n; i++ { + p.next = &Ring[V]{prev: p} + p = p.next + } + p.next = r + r.prev = p + return r +} + +// Link connects ring r with ring s such that r.Next() +// becomes s and returns the original value for r.Next(). +// r must not be empty. +// +// If r and s point to the same ring, linking +// them removes the elements between r and s from the ring. +// The removed elements form a subring and the result is a +// reference to that subring (if no elements were removed, +// the result is still the original value for r.Next(), +// and not nil). +// +// If r and s point to different rings, linking +// them creates a single ring with the elements of s inserted +// after r. The result points to the element following the +// last element of s after insertion. +func (r *Ring[V]) Link(s *Ring[V]) *Ring[V] { + n := r.Next() + if s != nil { + p := s.Prev() + // Note: Cannot use multiple assignment because + // evaluation order of LHS is not specified. + r.next = s + s.prev = r + n.prev = p + p.next = n + } + return n +} + +// Unlink removes n % r.Len() elements from the ring r, starting +// at r.Next(). If n % r.Len() == 0, r remains unchanged. +// The result is the removed subring. r must not be empty. +func (r *Ring[V]) Unlink(n int) *Ring[V] { + if n <= 0 { + return nil + } + return r.Link(r.Move(n + 1)) +} + +// Len computes the number of elements in ring r. +// It executes in time proportional to the number of elements. +func (r *Ring[V]) Len() int { + n := 0 + if r != nil { + n = 1 + for p := r.Next(); p != r; p = p.next { + n++ + } + } + return n +} + +// Do calls function f on each element of the ring, in forward order. +// The behavior of Do is undefined if f changes *r. +func (r *Ring[V]) Do(f constraints.Consumer[any]) { + if r != nil { + f(r.Value) + for p := r.Next(); p != r; p = p.next { + f(p.Value) + } + } +} diff --git a/gcontainers/gring/ring_test.go b/gcontainers/gring/ring_test.go new file mode 100644 index 0000000..a433f55 --- /dev/null +++ b/gcontainers/gring/ring_test.go @@ -0,0 +1,224 @@ +package gring + +import ( + "fmt" + "testing" +) + +// For debugging - keep around. +func dump[V any](r *Ring[V]) { + if r == nil { + fmt.Println("empty") + return + } + i, n := 0, r.Len() + for p := r; i < n; p = p.next { + fmt.Printf("%4d: %p = {<- %p | %p ->}\n", i, p, p.prev, p.next) + i++ + } + fmt.Println() +} + +func verify[V any](t *testing.T, r *Ring[V], N int, sum int) { + // Len + n := r.Len() + if n != N { + t.Errorf("r.Len() == %d; expected %d", n, N) + } + + // iteration + n = 0 + s := 0 + r.Do(func(p any) { + n++ + if p != nil { + s += p.(int) + } + }) + if n != N { + t.Errorf("number of forward iterations == %d; expected %d", n, N) + } + if sum >= 0 && s != sum { + t.Errorf("forward ring sum = %d; expected %d", s, sum) + } + + if r == nil { + return + } + + // connections + if r.next != nil { + var p *Ring[V] // previous element + for q := r; p == nil || q != r; q = q.next { + if p != nil && p != q.prev { + t.Errorf("prev = %p, expected q.prev = %p\n", p, q.prev) + } + p = q + } + if p != r.prev { + t.Errorf("prev = %p, expected r.prev = %p\n", p, r.prev) + } + } + + // Next, Prev + if r.Next() != r.next { + t.Errorf("r.Next() != r.next") + } + if r.Prev() != r.prev { + t.Errorf("r.Prev() != r.prev") + } + + // Move + if r.Move(0) != r { + t.Errorf("r.Move(0) != r") + } + if r.Move(N) != r { + t.Errorf("r.Move(%d) != r", N) + } + if r.Move(-N) != r { + t.Errorf("r.Move(%d) != r", -N) + } + for i := 0; i < 10; i++ { + ni := N + i + mi := ni % N + if r.Move(ni) != r.Move(mi) { + t.Errorf("r.Move(%d) != r.Move(%d)", ni, mi) + } + if r.Move(-ni) != r.Move(-mi) { + t.Errorf("r.Move(%d) != r.Move(%d)", -ni, -mi) + } + } +} + +func TestCornerCases(t *testing.T) { + var ( + r0 *Ring[int] + r1 Ring[int] + ) + // Basics + verify(t, r0, 0, 0) + verify(t, &r1, 1, 0) + // Insert + r1.Link(r0) + verify(t, r0, 0, 0) + verify(t, &r1, 1, 0) + // Insert + r1.Link(r0) + verify(t, r0, 0, 0) + verify(t, &r1, 1, 0) + // Unlink + r1.Unlink(0) + verify(t, &r1, 1, 0) +} + +func makeN(n int) *Ring[int] { + r := New[int](n) + for i := 1; i <= n; i++ { + r.Value = i + r = r.Next() + } + return r +} + +func sumN(n int) int { return (n*n + n) / 2 } + +func TestNew(t *testing.T) { + for i := 0; i < 10; i++ { + r := New[int](i) + verify(t, r, i, -1) + } + for i := 0; i < 10; i++ { + r := makeN(i) + verify(t, r, i, sumN(i)) + } +} + +func TestLink1(t *testing.T) { + r1a := makeN(1) + var r1b Ring[int] + r2a := r1a.Link(&r1b) + verify(t, r2a, 2, 1) + if r2a != r1a { + t.Errorf("a) 2-element link failed") + } + + r2b := r2a.Link(r2a.Next()) + verify(t, r2b, 2, 1) + if r2b != r2a.Next() { + t.Errorf("b) 2-element link failed") + } + + r1c := r2b.Link(r2b) + verify(t, r1c, 1, 1) + verify(t, r2b, 1, 0) +} + +func TestLink2(t *testing.T) { + var r0 *Ring[int] + r1a := &Ring[int]{Value: 42} + r1b := &Ring[int]{Value: 77} + r10 := makeN(10) + + r1a.Link(r0) + verify(t, r1a, 1, 42) + + r1a.Link(r1b) + verify(t, r1a, 2, 42+77) + + r10.Link(r0) + verify(t, r10, 10, sumN(10)) + + r10.Link(r1a) + verify(t, r10, 12, sumN(10)+42+77) +} + +func TestLink3(t *testing.T) { + var r Ring[int] + n := 1 + for i := 1; i < 10; i++ { + n += i + verify(t, r.Link(New[int](i)), n, -1) + } +} + +func TestUnlink(t *testing.T) { + r10 := makeN(10) + s10 := r10.Move(6) + + sum10 := sumN(10) + + verify(t, r10, 10, sum10) + verify(t, s10, 10, sum10) + + r0 := r10.Unlink(0) + verify(t, r0, 0, 0) + + r1 := r10.Unlink(1) + verify(t, r1, 1, 2) + verify(t, r10, 9, sum10-2) + + r9 := r10.Unlink(9) + verify(t, r9, 9, sum10-2) + verify(t, r10, 9, sum10-2) +} + +func TestLinkUnlink(t *testing.T) { + for i := 1; i < 4; i++ { + ri := New[int](i) + for j := 0; j < i; j++ { + rj := ri.Unlink(j) + verify(t, rj, j, -1) + verify(t, ri, i-j, -1) + ri.Link(rj) + verify(t, ri, i, -1) + } + } +} + +// Test that calling Move() on an empty Ring initializes it. +func TestMoveEmptyRing(t *testing.T) { + var r Ring[int] + + r.Move(1) + verify(t, &r, 1, 0) +} diff --git a/gcontainers/gset/set.go b/gcontainers/gset/set.go new file mode 100644 index 0000000..7d9a0bf --- /dev/null +++ b/gcontainers/gset/set.go @@ -0,0 +1,60 @@ +package gset + +// Set is the hashset datastructure +type Set[T comparable] map[T]struct{} + +// NewSet returns an empty set +func NewSet[T comparable]() Set[T] { + s := make(Set[T]) + return s +} + +// NewSetWithSize returns an empty set initialized with specific size +func NewSetWithSize[T comparable](size int) Set[T] { + s := make(Set[T], size) + return s +} + +// Len returns the number of elements of this set +func Len[T comparable](s Set[T]) int { + return len(s) +} + +// Contains returns true if this set contains the specified element +func Contains[T comparable](s Set[T], e T) bool { + _, ok := s[e] + return ok +} + +// Add adds the specified element to this set +// Always returns true due to the build-in map doesn't indicate caller whether the given element already exists +// Reserves the return type for future extension +func Add[T comparable](s Set[T], e T) bool { + s[e] = struct{}{} + return true +} + +// Remove removes the specified element from this set +// Always returns true due to the build-in map doesn't indicate caller whether the given element already exists +// Reserves the return type for future extension +func Remove[T comparable](s Set[T], e T) bool { + delete(s, e) + return true +} + +// Items returns a slice of elements from the set. Note that the elements will be an indeterminate order +func Items[T comparable](s Set[T]) []T { + items := make([]T, 0, len(s)) + + for k := range s { + items = append(items, k) + } + return items +} + +// Clear removes all the elements from this set +func Clear[T comparable](s Set[T]) { + for k := range s { + delete(s, k) + } +} diff --git a/gcontainers/gset/set_test.go b/gcontainers/gset/set_test.go new file mode 100644 index 0000000..e867a26 --- /dev/null +++ b/gcontainers/gset/set_test.go @@ -0,0 +1,32 @@ +package gset + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSet(t *testing.T) { + strSet := NewSet[string]() + + for _, v := range []string{"hello", "the", "curel", "world"} { + Add(strSet, v) + } + + t.Logf("set contains hello:%v\n", Contains(strSet, "hello")) + t.Logf("set contains helloo:%v\n", Contains(strSet, "helloo")) + + assert := assert.New(t) + assert.Equal(4, Len(strSet)) + + Remove(strSet, "hello") + t.Logf("after remove hello, set contains hello:%v\n", Contains(strSet, "hello")) + assert.Equal(3, Len(strSet)) + + items := Items(strSet) + t.Logf("items:%v\n", items) + + Clear(strSet) + t.Logf("now set is empty: %v\n", strSet) + assert.Equal(0, Len(strSet)) +} diff --git a/gcontainers/gslice/silice_test.go b/gcontainers/gslice/silice_test.go new file mode 100644 index 0000000..d24734d --- /dev/null +++ b/gcontainers/gslice/silice_test.go @@ -0,0 +1,283 @@ +package gslice + +import ( + "math" + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +type Node struct { + Val int +} + +func TestCopy(t *testing.T) { + t.Parallel() + t.Helper() + assert := assert.New(t) + + nilFunc := func() []int64 { + return nil + } + assert.Nil(Copy(nilFunc())) + assert.Equal([]string{}, Copy([]string{})) + t.Logf("%p, %p", []string{""}, Copy([]string{""})) + assert.Equal([]float64{-1}, Copy([]float64{-1})) + t.Logf("%p, %p", []float64{-1}, Copy([]float64{-1})) + assert.Equal([]*Node{{1}, {2}}, Copy([]*Node{{1}, {2}})) + t.Logf("%p, %p", []*Node{{1}, {2}}, Copy([]*Node{{1}, {2}})) +} + +func TestEqual(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + nilFunc := func() []int64 { + return nil + } + assert.True(Equal([]int32{1, 2}, []int32{1, 2})) + assert.False(Equal([]int32{1, 2}, []int32{1, 3})) + assert.True(Equal([]int64{}, nilFunc())) // nil and empty are equal + assert.False(Equal([]string{"a", "a", "a"}, []string{"a"})) + assert.False(Equal([]float64{math.NaN()}, []float64{math.NaN()})) +} + +func TestEqualWithFunc(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + lenEqFunc := func(a, b string) bool { + return len(a) == len(b) + } + lesFunc := func(a, b int) bool { + return a < b + } + + assert.True(EqualWithFunc([]string{"abc", "", "dd"}, []string{"zza", "", "pp"}, lenEqFunc)) + assert.False(EqualWithFunc([]string{"abc"}, []string{"zzadd"}, lenEqFunc)) + + assert.True(EqualWithFunc([]int{1, 2, 3}, []int{4, 5, 6}, lesFunc)) + assert.False(EqualWithFunc([]int{1, 2, 3}, []int{1, 2, 3}, lesFunc)) +} + +func TestIndex(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + assert.Equal(0, Index([]int32{1, 2}, 1)) + assert.Equal(-1, Index([]int32{100, 200}, 1)) + assert.Equal(-1, Index([]string{}, "")) +} + +func TestIndexWithFunc(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + existFunc := func(a string) bool { + return len(a) > 0 + } + more100Func := func(a int) bool { + return a > 100 + } + + assert.Equal(2, IndexWithFunc([]string{"", "", "dd"}, existFunc)) + assert.Equal(-1, IndexWithFunc([]string{"", "", ""}, existFunc)) + assert.Equal(1, IndexWithFunc([]int{1, 200, 3}, more100Func)) + assert.Equal(-1, IndexWithFunc([]int{1, 2, 3}, more100Func)) +} + +func TestContains(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + assert.True(Contains([]int32{1, 2}, 1)) + assert.False(Contains([]int32{100, 200}, 1)) + assert.False(Contains([]string{}, "")) +} + +func TestContainsWithFunc(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + existFunc := func(a string) bool { + return len(a) > 0 + } + more100Func := func(a int) bool { + return a > 100 + } + + assert.True(ContainsWithFunc([]string{"", "", "dd"}, existFunc)) + assert.False(ContainsWithFunc([]string{"", "", ""}, existFunc)) + assert.True(ContainsWithFunc([]int{1, 200, 3}, more100Func)) + assert.False(ContainsWithFunc([]int{1, 2, 3}, more100Func)) +} + +func TestLen(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + nilFunc := func() []int64 { + return nil + } + assert.Equal(0, Len(nilFunc())) + assert.Equal(0, Len([]string{})) + assert.Equal(1, Len([]float64{-1})) + assert.Equal(2, Len([]*Node{{1}, {2}})) +} + +func TestUnique(t *testing.T) { + t.Parallel() + t.Helper() + assert := assert.New(t) + + nilFunc := func() []int64 { + return nil + } + type utWrap[T comparable] struct { + Slice []T + Count int + } + + res1, cnt1 := Unique(nilFunc()) + assert.Equal(res1, []int64{}) + assert.Equal(cnt1, 0) + res2, cnt2 := Unique([]string{}) + assert.Equal(res2, []string{}) + assert.Equal(cnt2, 0) + res3, cnt3 := Unique([]float64{-1}) + assert.Equal(res3, []float64{-1}) + assert.Equal(cnt3, 1) + res4, cnt4 := Unique([]string{"a", "a", "b", "b", "a"}) + assert.Equal(res4, []string{"a", "b"}) + assert.Equal(cnt4, 2) + + // TODO:why???? + // assert.Equal( + // utWrap[string]{Slice: []string{}, Count: 0}, + // utWrapFunc([]string{}), + // ) + // assert.Equal( + // utWrap[float64]{Slice: []float64{-1}, Count: 1}, + // utWrapFunc([]float64{-1}), + // ) + // assert.Equal( + // utWrap[string]{Slice: []string{"a", "b"}, Count: 2}, + // utWrapFunc([]string{"a", "a", "b", "b", "a"}), + // ) +} + +// so ugly 🤮 TODO: find a better way to do this + +type utWrap[T comparable] struct { + Slice []T + Count int +} + +// so ugly 🤮 TODO: find a better way to do this +func utWrapFunc[T comparable](s []T) utWrap[T] { + res, cnt := Unique(s) + return utWrap[T]{Slice: res, Count: cnt} +} + +func TestUniqueWithFunc(t *testing.T) { + t.Parallel() + t.Helper() + assert := assert.New(t) + + s1, count1 := UniqueWithFunc([]int{0, 1, 2, 3, 4, 5, 6}, func(i int) int { + return i % 3 + }) + s2, count2 := UniqueWithFunc([]int{0, 1, 2, 3, 4, 5, 6, 1000}, func(i int) int { + if i > 10 { + return i + } + return -1 + }) + assert.Equal([]int{0, 1, 2}, s1) + assert.Equal(3, count1) + assert.Equal([]int{0, 1000}, s2) + assert.Equal(2, count2) +} + +func TestReverse(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + assert.Equal([]int{3, 2, 1}, Reverse([]int{1, 2, 3})) + assert.Equal([]string{"a", "b", "c", "d"}, Reverse([]string{"d", "c", "b", "a"})) + assert.Equal([]int64{}, Reverse([]int64{})) +} + +func TestMerge(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + assert.Equal([]int{1, 2, 3, 4, 5, 6}, Merge([]int{1, 2, 3}, []int{4, 5, 6})) + assert.Equal([]string{"a", "b", "c", "d"}, Merge([]string{"a", "b"}, []string{"c", "d"})) + assert.Equal([]int64{}, Merge([]int64{}, []int64{})) + + nilf := func() []int64 { + return nil + } + assert.Equal([]int64{}, Merge([]int64{}, nilf())) + assert.Equal([]int64{}, Merge(nilf(), []int64{})) + assert.Equal([]int64{}, Merge(nilf(), nilf())) +} + +func TestIsSorted(t *testing.T) { + assert := assert.New(t) + t.Parallel() + + var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586} + var float64s = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8, 74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3} // nolint: lll + + assert.False(IsSorted(ints[:])) + assert.False(IsSorted(float64s[:])) + + sort.Slice(ints[:], func(i, j int) bool { + return ints[i] < ints[j] + }) + sort.Slice(float64s[:], func(i, j int) bool { + return float64s[i] < float64s[j] + }) + t.Logf("after sort: %v", ints) + t.Logf("after sort: %v", float64s) + assert.True(IsSorted(ints[:])) + assert.True(IsSorted(float64s[:])) +} + +func TestIsSortedWithFunc(t *testing.T) { + assert := assert.New(t) + t.Parallel() + + var ints = []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586} + + assert.False(IsSortedFunc(ints, func(a, b int) bool { + return a < b + })) + assert.False(IsSortedFunc(ints, func(a, b int) bool { + return a > b + })) + + sort.Slice(ints, func(i, j int) bool { + return ints[i] > ints[j] + }) + assert.False(IsSortedFunc(ints, func(a, b int) bool { + return a < b + })) + assert.True(IsSortedFunc(ints, func(a, b int) bool { + return a > b + })) +} + +func TestShuffle(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := Shuffle([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + result2 := Shuffle([]int{}) + + is.NotEqual(result1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + is.Equal(result2, []int{}) +} diff --git a/gcontainers/gslice/slice.go b/gcontainers/gslice/slice.go new file mode 100644 index 0000000..b095a4c --- /dev/null +++ b/gcontainers/gslice/slice.go @@ -0,0 +1,184 @@ +package gslice + +import ( + "github.com/miniLCT/gosb/gogenerics/constraints" + "github.com/miniLCT/gosb/hack/fastrand" +) + +// Copy returns a shallow copy of the given slice +func Copy[T any](s []T) []T { + // Preserve nil in case it matters + if s == nil { + return nil + } + return append([]T{}, s...) +} + +// Equal returns whether two slices are equal: the same length and all +// elements equal. Note that size=0 and nil are considered equal; floating +// point NaNs are not considered equal +func Equal[T comparable](a, b []T) bool { + if len(a) != len(b) { + return false + } + + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// EqualWithFunc returns whether two slices are equal using a comparison function on each pair of elements +func EqualWithFunc[T1, T2 any](a []T1, b []T2, eq func(T1, T2) bool) bool { + if len(a) != len(b) { + return false + } + + for i, v1 := range a { + v2 := b[i] + if !eq(v1, v2) { + return false + } + } + return true +} + +// Index returns the index of the first occurrence of target in s, or -1 if not present +func Index[T comparable](s []T, target T) int { + for i := range s { + if s[i] == target { + return i + } + } + return -1 +} + +// IndexWithFunc returns the first index i satisfying eq(s[i]), or -1 if none do +func IndexWithFunc[T any](s []T, eq func(T) bool) int { + for i := range s { + if eq(s[i]) { + return i + } + } + return -1 +} + +// Contains returns whether target is present in s. +func Contains[T comparable](s []T, target T) bool { + return Index(s, target) >= 0 +} + +// ContainsWithFunc return whether at least one element e of s satisfies eq(e). +func ContainsWithFunc[T any](s []T, eq func(T) bool) bool { + return IndexWithFunc(s, eq) >= 0 +} + +// Len returns the length of slice +func Len[T any](s []T) int { + return len(s) +} + +// Unique returns a new slice containing only the unique elements of s, in the order they first appear. +func Unique[T comparable](s []T) ([]T, int) { + l := Len(s) + if l == 0 { + return make([]T, 0), 0 + } + + idx := 0 + seem := make(map[T]struct{}, len(s)) // comparable instead of any + uniqS := make([]T, len(s)) + for _, v := range s { + if _, ok := seem[v]; ok { + continue + } + uniqS[idx] = v + idx++ + seem[v] = struct{}{} + } + return uniqS[:idx], idx +} + +// UniqueWithFunc returns a new slice containing only the unique elements +// satisfying func f of s, in the order they first appear. +func UniqueWithFunc[T any, U comparable](s []T, f func(T) U) ([]T, int) { + l := Len(s) + if l == 0 { + return make([]T, 0), 0 + } + + idx := 0 + seem := make(map[U]struct{}, len(s)) + uniqS := make([]T, len(s)) + for _, v := range s { + key := f(v) + + if _, ok := seem[key]; ok { + continue + } + + uniqS[idx] = v + idx++ + seem[key] = struct{}{} + } + return uniqS[:idx], idx +} + +// Reverse means the first becomes the last, the second becomes the second to last, and so on +func Reverse[T any](s []T) []T { + l := len(s) + mid := l >> 1 + + for i := 0; i < mid; i++ { + s[i], s[l-1-i] = s[l-1-i], s[i] + } + return s +} + +// Merge returns a new slice containing all the elements of s1 +// followed by all the elements of s2. If s1 and s2 are both nil, +// returns the empty slice. If either is nil, returns a copy of +// the other. +func Merge[T any](s1, s2 []T) []T { + if s1 == nil && s2 == nil { + return []T{} + } + if s1 == nil { + return Copy(s2) + } + if s2 == nil { + return Copy(s1) + } + return append(s1, s2...) +} + +// IsSorted reports whether x is sorted in ascending order +func IsSorted[T constraints.Ordered](x []T) bool { + for i := len(x) - 1; i > 0; i-- { + if x[i] < x[i-1] { + return false + } + } + return true +} + +// IsSortedFunc reports whether x is sorted in ascending order, with less as the +// comparison function +func IsSortedFunc[T any](x []T, less constraints.Less[T]) bool { + for i := len(x) - 1; i > 0; i-- { + if less(x[i], x[i-1]) { + return false + } + } + return true +} + +// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm +func Shuffle[T any](collection []T) []T { + fastrand.Shuffle(len(collection), func(i, j int) { + collection[i], collection[j] = collection[j], collection[i] + }) + return collection +}