From abe445f5e6bb3652ac161a41832e607579c40215 Mon Sep 17 00:00:00 2001 From: charlie <3140647@qq.com> Date: Wed, 16 Nov 2022 17:31:28 +0800 Subject: [PATCH] list --- collections/list/array_list.go | 180 +++++++++++++++++++++++++++ collections/list/array_list_test.go | 40 ++++++ collections/list/linked_list.go | 141 +++++++++++++++++++++ collections/list/linked_list_test.go | 87 +++++++++++++ collections/list/list.go | 35 ++++++ mathx/int.go | 8 +- 6 files changed, 486 insertions(+), 5 deletions(-) create mode 100644 collections/list/array_list.go create mode 100644 collections/list/array_list_test.go create mode 100644 collections/list/linked_list.go create mode 100644 collections/list/linked_list_test.go create mode 100644 collections/list/list.go diff --git a/collections/list/array_list.go b/collections/list/array_list.go new file mode 100644 index 0000000..37c2daa --- /dev/null +++ b/collections/list/array_list.go @@ -0,0 +1,180 @@ +package list + +import ( + "github.com/charlienet/go-mixed/locker" +) + +const minCapacity = 16 + +type ArrayList[T any] struct { + buf []T + head int + tail int + minCap int + list[T] +} + +func NewArrayList[T any](elems ...T) *ArrayList[T] { + minCap := minCapacity + var buf []T + + return &ArrayList[T]{ + buf: buf, + minCap: minCap, + list: list[T]{locker: locker.EmptyLocker}, + } +} + +func (l *ArrayList[T]) PushFront(v T) { + l.locker.Lock() + defer l.locker.Unlock() + + l.grow() + + l.head = l.prev(l.head) + l.buf[l.head] = v + l.size++ +} + +func (l *ArrayList[T]) PushBack(v T) { + l.locker.Lock() + defer l.locker.Unlock() + + l.grow() + + l.buf[l.tail] = v + + l.tail = l.next(l.tail) + l.size++ +} + +func (l *ArrayList[T]) PopFront() T { + l.locker.Lock() + defer l.locker.Unlock() + + if l.size <= 0 { + panic("list: PopFront() called on empty list") + } + ret := l.buf[l.head] + var zero T + l.buf[l.head] = zero + + l.head = l.next(l.head) + l.size-- + + l.shrink() + return ret +} + +func (l *ArrayList[T]) PopBack() T { + l.locker.Lock() + defer l.locker.Unlock() + + l.tail = l.prev(l.tail) + + ret := l.buf[l.tail] + var zero T + l.buf[l.tail] = zero + l.size-- + + l.shrink() + return ret +} + +func (l *ArrayList[T]) RemoveAt(at int) T { + if at < 0 || at >= l.Size() { + panic(ErrorOutOffRange) + } + + l.locker.Lock() + defer l.locker.Unlock() + + rm := (l.head + at) & (len(l.buf) - 1) + if at*2 < l.size { + for i := 0; i < at; i++ { + prev := l.prev(rm) + l.buf[prev], l.buf[rm] = l.buf[rm], l.buf[prev] + rm = prev + } + return l.PopFront() + } + swaps := l.size - at - 1 + for i := 0; i < swaps; i++ { + next := l.next(rm) + l.buf[rm], l.buf[next] = l.buf[next], l.buf[rm] + rm = next + } + return l.PopBack() +} + +func (l *ArrayList[T]) Front() T { + l.locker.RLock() + defer l.locker.RUnlock() + + return l.buf[l.head] +} + +func (l *ArrayList[T]) Back() T { + l.locker.RLock() + defer l.locker.RUnlock() + + return l.buf[l.tail] +} + +func (l *ArrayList[T]) ForEach(fn func(T)) { + l.locker.RLock() + defer l.locker.RUnlock() + + n := l.head + for i := 0; i < l.size; i++ { + fn(l.buf[n]) + + n = l.next(n) + } +} + +func (q *ArrayList[T]) prev(i int) int { + return (i - 1) & (len(q.buf) - 1) +} + +func (l *ArrayList[T]) next(i int) int { + return (i + 1) & (len(l.buf) - 1) +} + +func (l *ArrayList[T]) grow() { + if l.size != len(l.buf) { + return + } + if len(l.buf) == 0 { + if l.minCap == 0 { + l.minCap = minCapacity + } + l.buf = make([]T, l.minCap) + return + } + + l.resize() +} + +func (l *ArrayList[T]) shrink() { + if len(l.buf) > l.minCap && (l.size<<2) == len(l.buf) { + l.resize() + } +} + +// resize resizes the list to fit exactly twice its current contents. This is +// used to grow the list when it is full, and also to shrink it when it is +// only a quarter full. +func (l *ArrayList[T]) resize() { + newBuf := make([]T, l.size<<1) + if l.tail > l.head { + copy(newBuf, l.buf[l.head:l.tail]) + } else { + n := copy(newBuf, l.buf[l.head:]) + copy(newBuf[n:], l.buf[:l.tail]) + } + + l.head = 0 + l.tail = l.size + l.buf = newBuf +} diff --git a/collections/list/array_list_test.go b/collections/list/array_list_test.go new file mode 100644 index 0000000..acdc097 --- /dev/null +++ b/collections/list/array_list_test.go @@ -0,0 +1,40 @@ +package list_test + +import ( + "testing" + + "github.com/charlienet/go-mixed/collections/list" +) + +func TestNewArrayList(t *testing.T) { + l := list.NewArrayList[int]() + + _ = l +} + +func TestArrayPushBack(t *testing.T) { + l := list.NewArrayList[int]() + + l.PushBack(1) + l.PushBack(2) + l.PushBack(3) + + l.ForEach(func(i int) { + t.Log(i) + }) +} + +func TestArrayPushFront(t *testing.T) { + l := list.NewArrayList[int]() + + l.PushFront(1) + l.PushFront(2) + l.PushFront(3) + + l.PushBack(99) + l.PushBack(88) + + l.ForEach(func(i int) { + t.Log(i) + }) +} diff --git a/collections/list/linked_list.go b/collections/list/linked_list.go new file mode 100644 index 0000000..2882eae --- /dev/null +++ b/collections/list/linked_list.go @@ -0,0 +1,141 @@ +package list + +import ( + "github.com/charlienet/go-mixed/locker" +) + +type LinkedList[T any] struct { + list[T] + front, tail *LinkedNode[T] +} + +type LinkedNode[T any] struct { + Value T + Prev, Next *LinkedNode[T] +} + +func NewLinkedList[T any](elems ...T) *LinkedList[T] { + return &LinkedList[T]{ + list: list[T]{locker: locker.EmptyLocker}, + } +} + +func (l *LinkedList[T]) PushBack(v T) *LinkedList[T] { + l.pushBackNode(&LinkedNode[T]{Value: v}) + + return l +} + +func (l *LinkedList[T]) PushFront(v T) *LinkedList[T] { + l.pushFrontNode(&LinkedNode[T]{Value: v}) + + return l +} + +func (l *LinkedList[T]) FrontNode() *LinkedNode[T] { + return l.front +} + +func (l *LinkedList[T]) Front() T { + return l.FrontNode().Value +} + +func (l *LinkedList[T]) BackNode() *LinkedNode[T] { + return l.tail +} + +func (l *LinkedList[T]) Back() T { + if l.size == 0 { + panic(ErrorOutOffRange) + } + return l.tail.Value +} + +func (l *LinkedList[T]) ForEach(fn func(T) bool) { + l.locker.RLock() + defer l.locker.RUnlock() + + for current := l.front; current != nil; current = current.Next { + if fn(current.Value) { + break + } + } +} + +func (l *LinkedList[T]) Remove(n *LinkedNode[T]) { + l.locker.Lock() + defer l.locker.Unlock() + + if n.Next != nil { + n.Next.Prev = n.Prev + } else { + l.tail = n.Prev + } + + if n.Prev != nil { + n.Prev.Next = n.Next + } else { + l.front = n.Next + } + + l.size-- +} + +func (l *LinkedList[T]) RemoveAt(index int) { + l.locker.Lock() + defer l.locker.Unlock() + + var i int + var prev *LinkedNode[T] + + prev = l.front + for current := l.front; current != nil; { + + if i == index { + prev.Next = current.Next + current.Next = nil + + l.size-- + return + } + + prev = current + current = current.Next + i++ + } + +} + +func (l *LinkedList[T]) pushBackNode(n *LinkedNode[T]) { + l.locker.Lock() + defer l.locker.Unlock() + + n.Next = nil + n.Prev = l.tail + + if l.tail != nil { + l.tail.Next = n + } else { + l.front = n + } + + l.tail = n + + l.size++ +} + +func (l *LinkedList[T]) pushFrontNode(n *LinkedNode[T]) { + l.locker.Lock() + defer l.locker.Unlock() + + n.Next = l.front + n.Prev = nil + if l.front != nil { + l.front.Prev = n + } else { + l.tail = n + } + l.front = n + + l.size++ +} diff --git a/collections/list/linked_list_test.go b/collections/list/linked_list_test.go new file mode 100644 index 0000000..da430bd --- /dev/null +++ b/collections/list/linked_list_test.go @@ -0,0 +1,87 @@ +package list_test + +import ( + "testing" + + "github.com/charlienet/go-mixed/collections/list" + "github.com/stretchr/testify/assert" +) + +func TestPushBack(t *testing.T) { + l := list.NewLinkedList[int]() + l.PushBack(1) + l.PushBack(2) + l.PushBack(3) + + l.ForEach(func(i int) bool { + t.Log(i) + return false + }) +} + +func TestPushFront(t *testing.T) { + l := list.NewLinkedList[int]() + l.PushFront(1) + l.PushFront(2) + l.PushFront(3) + + l.ForEach(func(i int) bool { + t.Log(i) + return false + }) +} + +func TestRemoveAt(t *testing.T) { + + l := list.NewLinkedList[int]() + l.PushBack(1) + l.PushBack(2) + l.PushBack(3) + + l.RemoveAt(1) + + l.ForEach(func(i int) bool { + t.Log(i) + return false + }) + + t.Log() + + l.RemoveAt(0) + l.ForEach(func(i int) bool { + t.Log(i) + return false + }) + +} + +func TestSize(t *testing.T) { + l := list.NewLinkedList[int]() + l.PushBack(1) + l.PushBack(2) + l.PushBack(3) + + assert.Equal(t, 3, l.Size()) + + l.RemoveAt(0) + assert.Equal(t, 2, l.Size()) +} + +func TestLinkedListToSlice(t *testing.T) { + l := list.NewLinkedList[int]() + l.PushBack(1) + l.PushBack(2) + l.PushBack(3) + + s := l.ToSlice() + t.Log(s) +} + +func BenchmarkLinkedList(b *testing.B) { + l := list.NewLinkedList[int]() + l.Synchronize() + + for i := 0; i < b.N; i++ { + l.PushBack(i) + } +} diff --git a/collections/list/list.go b/collections/list/list.go new file mode 100644 index 0000000..150bd58 --- /dev/null +++ b/collections/list/list.go @@ -0,0 +1,35 @@ +package list + +import ( + "errors" + + "github.com/charlienet/go-mixed/locker" +) + +var ErrorOutOffRange = errors.New("out of range") + +type List[T any] interface { +} + +type list[T any] struct { + size int + locker locker.RWLocker +} + +func (l *list[T]) Synchronize() { + l.locker = locker.NewRWLocker() +} + +func (l *list[T]) ForEach(fn func(T) bool) { panic("Not Implemented") } + +func (l *LinkedList[T]) ToSlice() []T { + s := make([]T, 0, l.Size()) + l.ForEach(func(t T) bool { + s = append(s, t) + return false + }) + + return s +} + +func (l *list[T]) Size() int { return l.size } diff --git a/mathx/int.go b/mathx/int.go index 8766791..98bac70 100644 --- a/mathx/int.go +++ b/mathx/int.go @@ -7,17 +7,15 @@ import ( // MaxInt returns the larger one of v1 and v2. func Max[T constraints.Ordered](v1, v2 T) T { - return expr.If(v1 > v2, v1, v2) + return expr.Ternary(v1 > v2, v1, v2) } // MinInt returns the smaller one of v1 and v2. func Min[T constraints.Ordered](v1, v2 T) T { - return expr.If(v1 < v2, v1, v2) + return expr.Ternary(v1 < v2, v1, v2) } - - -func abs(n int64) int64 { +func Abs(n int64) int64 { y := n >> 63 return (n ^ y) - y }