diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dbf20f2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 HotPotatoC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..18577b8 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Sture + +A collection of data structures based on Go 1.18+ Generics. + +## Installation + +```bash +go get github.com/HotPotatoC/sture +``` + +## Usage + +Creating a priority queue using: + +```go +import "github.com/HotPotatoC/sture/queue" + +func main() { + pq := queue.NewPriorityQueue[string]() + + pq.Enqueue("Adam", 1) + pq.Enqueue("John", 3) + pq.Enqueue("Bob", 2) + + top := pq.Peek() + fmt.Println(top) // John +} +``` + +## Support + +If this project is helpful to you, please consider supporting me by donating or just give this project a 🌟 + +<a href="https://www.buymeacoffee.com/hotpotato" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a> diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..878a6c7 --- /dev/null +++ b/example/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + + "github.com/HotPotatoC/sture/queue" +) + +func main() { + pq := queue.NewPriorityQueue[string]() + + pq.Enqueue("Adam", 1) + pq.Enqueue("John", 3) + pq.Enqueue("Bob", 2) + + top := pq.Peek() + fmt.Println(top) // John +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8e65bae --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/HotPotatoC/sture + +go 1.18 + +require golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0507dff --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 h1:s/+U+w0teGzcoH2mdIlFQ6KfVKGaYpgyGdUefZrn9TU= +golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= diff --git a/linkedlist/circular_linked_list.go b/linkedlist/circular_linked_list.go new file mode 100644 index 0000000..dd157bd --- /dev/null +++ b/linkedlist/circular_linked_list.go @@ -0,0 +1,221 @@ +package linkedlist + +import ( + "errors" + "fmt" + + "golang.org/x/exp/constraints" +) + +// CircularLinkedList is a circular linked list. +type CircularLinkedList[V constraints.Ordered] struct { + head *Node[V] + tail *Node[V] + nSize uint +} + +// NewCircularLinkedList returns a new circular linked list. +func NewCircularLinkedList[V constraints.Ordered]() *CircularLinkedList[V] { + return &CircularLinkedList[V]{} +} + +// Append adds a new node to the end of the list. +func (ll *CircularLinkedList[V]) Append(value V) { + newNode := NewNode(value) + ll.nSize++ + + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + return + } + + newNode.prev = ll.tail + newNode.next = ll.head + ll.tail.next = newNode + ll.tail = ll.tail.next + ll.head.prev = ll.tail +} + +// PushHead adds a new node to the head of the list. +func (ll *CircularLinkedList[V]) PushHead(value V) { + newNode := NewNode(value) + ll.nSize++ + + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + return + } + + newNode.next = ll.head + ll.head.prev = newNode + ll.head = newNode + ll.tail.next = ll.head +} + +// InsertAt adds a new node to the given position +func (ll *CircularLinkedList[V]) InsertAt(pos int, value V) error { + newNode := NewNode(value) + + if pos < 1 || uint(pos) > ll.nSize { + return errors.New("invalid position") + } + + currPos := 1 + currNode := ll.head + + if pos == 1 { + ll.PushHead(value) + return nil + } + + for currPos < pos { + currNode = currNode.next + currPos++ + } + + newNode.prev = currNode.prev + newNode.next = currNode + currNode.prev.next = newNode + currNode.prev = newNode + + ll.nSize++ + + return nil +} + +// PushMid adds a new node to the middle of the list. +func (ll *CircularLinkedList[V]) PushMid(value V) { + newNode := NewNode(value) + ll.nSize++ + + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + return + } + + if ll.head.value > value { + ll.PushHead(value) + return + } + + if ll.tail.value < value { + ll.Append(value) + return + } + + curr := ll.head + + for curr.value < value { + curr = curr.next + } + + newNode.prev = curr.prev + newNode.next = curr + curr.prev.next = newNode + curr.prev = newNode +} + +// Pop removes the last node from the list. +func (ll *CircularLinkedList[V]) Pop() { + if ll.head == nil { + return + } + + ll.nSize-- + + if ll.head == ll.tail { + ll.head = nil + ll.tail = nil + return + } + + prev := ll.tail.prev + prev.next = ll.head + ll.tail = prev + ll.head.prev = ll.tail +} + +// PopHead removes the first node from the list. +func (ll *CircularLinkedList[V]) PopHead() { + if ll.head == nil { + return + } + + ll.nSize-- + + if ll.head == ll.tail { + ll.head = nil + ll.tail = nil + return + } + + next := ll.head.next + next.prev = ll.tail + ll.head = next + ll.tail.next = ll.head +} + +// Find returns the node with the given value. +func (ll *CircularLinkedList[V]) Find(value V) *Node[V] { + curr := ll.head + + for curr != nil { + if curr.value == value { + return curr + } + + curr = curr.next + } + + return nil +} + +// String returns a string representation of the list. +func (ll *CircularLinkedList[V]) String() string { + var s string + + curr := ll.head + + for curr != nil { + s += fmt.Sprintf("[%v]", curr.value) + if curr.next != ll.head { + s += `-` + } + + if curr.next == ll.head { + return s + } + + curr = curr.next + } + + return s +} + +// Head returns the head node of the list. +func (ll *CircularLinkedList[V]) Head() *Node[V] { + return ll.head +} + +// Tail returns the tail node of the list. +func (ll *CircularLinkedList[V]) Tail() *Node[V] { + return ll.tail +} + +// Size returns the size of the list. +func (ll *CircularLinkedList[V]) Size() uint { + return ll.nSize +} + +// IsCircular returns true if the list is circular. +func (ll *CircularLinkedList[V]) IsCircular() bool { + return ll.head != nil && ll.head == ll.tail.next +} + +// IsEmpty returns true if the list is empty. +func (ll *CircularLinkedList[V]) IsEmpty() bool { + return ll.head == nil +} diff --git a/linkedlist/circular_linked_list_test.go b/linkedlist/circular_linked_list_test.go new file mode 100644 index 0000000..b143b17 --- /dev/null +++ b/linkedlist/circular_linked_list_test.go @@ -0,0 +1,139 @@ +package linkedlist_test + +import ( + "testing" + + "github.com/HotPotatoC/sture/linkedlist" +) + +func TestCircularLinkedList_Append(t *testing.T) { + ll := linkedlist.NewCircularLinkedList[int]() + exp := "[1]-[2]-[3]-[4]-[5]" + + ll.Append(1) + ll.Append(2) + ll.Append(3) + ll.Append(4) + ll.Append(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestCircularLinkedList_PushHead(t *testing.T) { + ll := linkedlist.NewCircularLinkedList[int]() + exp := "[5]-[4]-[3]-[2]-[1]" + + ll.PushHead(1) + ll.PushHead(2) + ll.PushHead(3) + ll.PushHead(4) + ll.PushHead(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestCircularLinkedList_Pop(t *testing.T) { + ll := linkedlist.NewCircularLinkedList[int]() + exp := "[1]-[2]-[5]" + + ll.Append(1) + ll.Append(2) + ll.Append(3) + ll.Pop() + ll.Append(4) + ll.Pop() + ll.Append(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "[1]" + + ll.Pop() + ll.Pop() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "" + + ll.Pop() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestCircularLinkedList_PopHead(t *testing.T) { + ll := linkedlist.NewCircularLinkedList[int]() + exp := "[5]-[2]-[1]" + + ll.PushHead(1) + ll.PushHead(2) + ll.PushHead(3) + ll.PopHead() + ll.PushHead(4) + ll.PopHead() + ll.PushHead(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "[1]" + + ll.PopHead() + ll.PopHead() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "" + + ll.PopHead() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestCircularLinkedList_Find(t *testing.T) { + ll := linkedlist.NewCircularLinkedList[int]() + + ll.Append(1) + ll.Append(2) + ll.Append(3) + + node := ll.Find(2) + + if node.Next().Value() != 3 || node.Prev().Value() != 1 { + t.Errorf("\nExpected: next=3 prev=1\nGot: next=%d prev=%d", node.Next().Value(), node.Prev().Value()) + } +} + +func TestCircularLinkedList_IsCircular(t *testing.T) { + ll := linkedlist.NewCircularLinkedList[int]() + + ll.Append(1) + ll.Append(2) + ll.Append(3) + + if ll.Head().Prev() != ll.Tail() { + t.Errorf("\nExpected: prev=tail\nGot: prev=%d", ll.Head().Prev().Value()) + } + + if ll.Tail().Next() != ll.Head() { + t.Errorf("\nExpected: next=head\nGot: next=%d", ll.Tail().Next().Value()) + } + + if !ll.IsCircular() { + t.Errorf("\nExpected: circular") + } +} diff --git a/linkedlist/linked_list.go b/linkedlist/linked_list.go new file mode 100644 index 0000000..41a6d20 --- /dev/null +++ b/linkedlist/linked_list.go @@ -0,0 +1,207 @@ +package linkedlist + +import ( + "errors" + "fmt" + + "golang.org/x/exp/constraints" +) + +// LinkedList is a linked list. +type LinkedList[V constraints.Ordered] struct { + head *Node[V] + tail *Node[V] + nSize uint +} + +// NewLinkedList returns a new linked list. +func NewLinkedList[V constraints.Ordered]() *LinkedList[V] { + return &LinkedList[V]{} +} + +// Append adds a new node to the end of the list. +func (ll *LinkedList[V]) Append(value V) { + newNode := NewNode(value) + ll.nSize++ + + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + return + } + + newNode.prev = ll.tail + ll.tail.next = newNode + ll.tail = ll.tail.next +} + +// PushHead adds a new node to the head of the list. +func (ll *LinkedList[V]) PushHead(value V) { + newNode := NewNode(value) + ll.nSize++ + + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + return + } + + newNode.next = ll.head + ll.head.prev = newNode + ll.head = newNode +} + +// InsertAt adds a new node to the given position +func (ll *LinkedList[V]) InsertAt(pos int, value V) error { + newNode := NewNode(value) + + if pos < 1 || uint(pos) > ll.nSize { + return errors.New("invalid position") + } + + currPos := 1 + currNode := ll.head + + if pos == 1 { + ll.PushHead(value) + return nil + } + + for currPos < pos { + currNode = currNode.next + currPos++ + } + + newNode.prev = currNode.prev + newNode.next = currNode + currNode.prev.next = newNode + currNode.prev = newNode + + ll.nSize++ + + return nil +} + +// PushMid adds a new node to the middle of the list. +func (ll *LinkedList[V]) PushMid(value V) { + newNode := NewNode(value) + ll.nSize++ + + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + return + } + + if ll.head.value > value { + ll.PushHead(value) + return + } + + if ll.tail.value < value { + ll.Append(value) + return + } + + curr := ll.head + + for curr.value < value { + curr = curr.next + } + + newNode.prev = curr.prev + newNode.next = curr + curr.prev.next = newNode + curr.prev = newNode +} + +// Pop removes the last node from the list. +func (ll *LinkedList[V]) Pop() { + if ll.head == nil { + return + } + + ll.nSize-- + + if ll.head == ll.tail { + ll.head = nil + ll.tail = nil + return + } + + prev := ll.tail.prev + prev.next = nil + ll.tail = prev +} + +// PopHead removes the first node from the list. +func (ll *LinkedList[V]) PopHead() { + if ll.head == nil { + return + } + + ll.nSize-- + + if ll.head == ll.tail { + ll.head = nil + ll.tail = nil + return + } + + next := ll.head.next + next.prev = nil + ll.head = next +} + +// Find returns the node with the given value. +func (ll *LinkedList[V]) Find(value V) *Node[V] { + curr := ll.head + + for curr != nil { + if curr.value == value { + return curr + } + + curr = curr.next + } + + return nil +} + +// String returns a string representation of the list. +func (ll *LinkedList[V]) String() string { + var s string + + curr := ll.head + + for curr != nil { + s += fmt.Sprintf("[%v]", curr.value) + if curr.next != nil { + s += `-` + } + + curr = curr.next + } + + return s +} + +// Head returns the head node of the list. +func (ll *LinkedList[V]) Head() *Node[V] { + return ll.head +} + +// Tail returns the tail node of the list. +func (ll *LinkedList[V]) Tail() *Node[V] { + return ll.tail +} + +// Size returns the size of the list. +func (ll *LinkedList[V]) Size() uint { + return ll.nSize +} + +// IsEmpty returns true if the list is empty. +func (ll *LinkedList[V]) IsEmpty() bool { + return ll.head == nil +} \ No newline at end of file diff --git a/linkedlist/linked_list_test.go b/linkedlist/linked_list_test.go new file mode 100644 index 0000000..dcac8bd --- /dev/null +++ b/linkedlist/linked_list_test.go @@ -0,0 +1,189 @@ +package linkedlist_test + +import ( + "testing" + + "github.com/HotPotatoC/sture/linkedlist" +) + +func TestLinkedList_Append(t *testing.T) { + ll := linkedlist.NewLinkedList[int]() + exp := "[1]-[2]-[3]-[4]-[5]" + + ll.Append(1) + ll.Append(2) + ll.Append(3) + ll.Append(4) + ll.Append(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestLinkedList_PushHead(t *testing.T) { + ll := linkedlist.NewLinkedList[int]() + exp := "[5]-[4]-[3]-[2]-[1]" + + ll.PushHead(1) + ll.PushHead(2) + ll.PushHead(3) + ll.PushHead(4) + ll.PushHead(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestLinkedList_InsertAt(t *testing.T) { + ll := linkedlist.NewLinkedList[int]() + + ll.Append(1) + ll.Append(2) + ll.Append(3) + + ll.InsertAt(2, 4) + + exp := "[1]-[4]-[2]-[3]" + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + ll.InsertAt(1, 5) + + exp = "[5]-[1]-[4]-[2]-[3]" + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + ll.InsertAt(5, 6) + + exp = "[5]-[1]-[4]-[2]-[6]-[3]" + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + err := ll.InsertAt(7, 7) + if err == nil { + t.Errorf("\nExpected: error\nGot: nil") + } +} + +func TestLinkedList_PushMid(t *testing.T) { + ll := linkedlist.NewLinkedList[int]() + exp := "[1]-[2]-[3]-[4]-[5]" + + ll.Append(1) + ll.Append(2) + ll.Append(3) + ll.Append(4) + ll.PushMid(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + ll.PushMid(-4) + ll.PushMid(-2) + + exp = "[-4]-[-2]-[1]-[2]-[3]-[4]-[5]" + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + ll.PushMid(3) + + exp = "[-4]-[-2]-[1]-[2]-[3]-[3]-[4]-[5]" + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestLinkedList_Pop(t *testing.T) { + ll := linkedlist.NewLinkedList[int]() + exp := "[1]-[2]-[5]" + + ll.Append(1) + ll.Append(2) + ll.Append(3) + ll.Pop() + ll.Append(4) + ll.Pop() + ll.Append(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "[1]" + + ll.Pop() + ll.Pop() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "" + + ll.Pop() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestLinkedList_PopHead(t *testing.T) { + ll := linkedlist.NewLinkedList[int]() + exp := "[5]-[2]-[1]" + + ll.PushHead(1) + ll.PushHead(2) + ll.PushHead(3) + ll.PopHead() + ll.PushHead(4) + ll.PopHead() + ll.PushHead(5) + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "[1]" + + ll.PopHead() + ll.PopHead() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } + + exp = "" + + ll.PopHead() + + if ll.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String()) + } +} + +func TestLinkedList_Find(t *testing.T) { + ll := linkedlist.NewLinkedList[int]() + + ll.Append(1) + ll.Append(2) + ll.Append(3) + + node := ll.Find(2) + + if node.Next().Value() != 3 || node.Prev().Value() != 1 { + t.Errorf("\nExpected: next=3 prev=1\nGot: next=%d prev=%d", + node.Next().Value(), node.Prev().Value()) + } +} diff --git a/linkedlist/node.go b/linkedlist/node.go new file mode 100644 index 0000000..30c065e --- /dev/null +++ b/linkedlist/node.go @@ -0,0 +1,30 @@ +package linkedlist + +import "golang.org/x/exp/constraints" + +// Node is a node in a linked list. +type Node[V constraints.Ordered] struct { + value V + next *Node[V] + prev *Node[V] +} + +// NewNode returns a new linked list node. +func NewNode[V constraints.Ordered](value V) *Node[V] { + return &Node[V]{value: value} +} + +// Value returns the value of the node. +func (node *Node[V]) Value() V { + return node.value +} + +// Next returns the next node in the list. +func (node *Node[V]) Next() *Node[V] { + return node.next +} + +// Prev returns the previous node in the list. +func (node *Node[V]) Prev() *Node[V] { + return node.prev +} diff --git a/queue/priority_queue.go b/queue/priority_queue.go new file mode 100644 index 0000000..c71d8bb --- /dev/null +++ b/queue/priority_queue.go @@ -0,0 +1,84 @@ +package queue + +import ( + "fmt" + + "golang.org/x/exp/constraints" +) + +// PriorityQueue is a queue that supports priority-based insertion. +type PriorityQueue[V constraints.Ordered] struct { + list []PriorityQueueNode[V] + nSize int +} + +// NewPriorityQueue creates a new priority queue. +func NewPriorityQueue[V constraints.Ordered]() *PriorityQueue[V] { + return &PriorityQueue[V]{ + list: make([]PriorityQueueNode[V], 0), + } +} + +// Enqueue adds a new node at a certain position in the queue based on the priority. +func (q *PriorityQueue[V]) Enqueue(value V, priority int) { + newNode := NewPriorityQueueNode(value, priority) + + pos := q.search(priority) // search for the position to insert the new node + + q.list = append(q.list, PriorityQueueNode[V]{}) // add empty node + + copy(q.list[pos+1:], q.list[pos:]) // shift elements to the right + + q.list[pos] = newNode // insert new node at pos + q.nSize++ +} + +// Dequeue removes the last node from the queue. +func (q *PriorityQueue[V]) Dequeue() { + if q.nSize == 0 { + return + } + + q.list = q.list[:q.nSize-1] + q.nSize-- +} + +// Peek returns the value of the node with the highest priority in the queue. +func (q *PriorityQueue[V]) Peek() V { + return q.list[q.nSize-1].Value() +} + +// IsEmpty returns true if the queue is empty. +func (q *PriorityQueue[V]) IsEmpty() bool { + return q.nSize == 0 +} + +// String returns a string representation of the queue. +func (q *PriorityQueue[V]) String() string { + var s string + + for idx, node := range q.list { + s += fmt.Sprintf("[%v]", node.value) + if idx != q.nSize-1 { + s += "-" + } + } + + return s +} + +// search returns the position of the node that is the closest to the given priority. +func (q *PriorityQueue[V]) search(priority int) int { + low, high := 0, q.nSize + for low < high { + mid := int(uint(low+high) >> 1) // avoid overflow when computing mid + + if q.list[mid].Priority() <= priority { + low = mid + 1 + } else { + high = mid + } + } + + return low +} diff --git a/queue/priority_queue_node.go b/queue/priority_queue_node.go new file mode 100644 index 0000000..17b97fe --- /dev/null +++ b/queue/priority_queue_node.go @@ -0,0 +1,31 @@ +package queue + +import ( + "golang.org/x/exp/constraints" +) + +// PriorityQueueNode is a node in a priority queue. +type PriorityQueueNode[V constraints.Ordered] struct { + value V + priority int +} + +// NewPriorityQueueNode returns a new priority queue node. +func NewPriorityQueueNode[V constraints.Ordered](value V, priority int) PriorityQueueNode[V] { + return PriorityQueueNode[V]{value: value, priority: priority} +} + +// Value returns the value of the node. +func (node PriorityQueueNode[V]) Value() V { + return node.value +} + +// Priority returns the priority of the node. +func (node PriorityQueueNode[V]) Priority() int { + return node.priority +} + +// SetPriority sets the priority of the node. +func (node *PriorityQueueNode[V]) SetPriority(priority int) { + node.priority = priority +} diff --git a/queue/priority_queue_test.go b/queue/priority_queue_test.go new file mode 100644 index 0000000..a315e6d --- /dev/null +++ b/queue/priority_queue_test.go @@ -0,0 +1,52 @@ +package queue_test + +import ( + "testing" + + "github.com/HotPotatoC/sture/queue" +) + +func TestPriorityQueue_Enqueue(t *testing.T) { + q := queue.NewPriorityQueue[string]() + exp := "[John]-[Adam]-[Bob]" + + q.Enqueue("Adam", 3) + q.Enqueue("John", 2) + q.Enqueue("Bob", 5) + + if q.IsEmpty() { + t.Error("Queue should not be empty") + } + + if q.Peek() != "Bob" { + t.Errorf("Expected: Bob\nGot: %s", q.Peek()) + } + + if q.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, q.String()) + } +} + +func TestPriorityQueue_Dequeue(t *testing.T) { + q := queue.NewPriorityQueue[string]() + + q.Enqueue("Adam", 3) + q.Enqueue("John", 2) + q.Enqueue("Bob", 5) + + q.Dequeue() + + exp := "[John]-[Adam]" + + if q.IsEmpty() { + t.Error("Queue should not be empty") + } + + if q.Peek() != "Adam" { + t.Errorf("Expected: Adam\nGot: %s", q.Peek()) + } + + if q.String() != exp { + t.Errorf("\nExpected: %s\nGot: %s", exp, q.String()) + } +} diff --git a/queue/queue.go b/queue/queue.go new file mode 100644 index 0000000..3eed6aa --- /dev/null +++ b/queue/queue.go @@ -0,0 +1,44 @@ +package queue + +import ( + "golang.org/x/exp/constraints" +) + +// Queue is a queue. +type Queue[V constraints.Ordered] struct { + list []V + nSize int +} + +// NewQueue returns a new queue. +func NewQueue[V constraints.Ordered]() *Queue[V] { + return &Queue[V]{ + list: make([]V, 0), + } +} + +// Enqueue adds a new node to the end of the queue. +func (q *Queue[V]) Enqueue(value V) { + q.list = append(q.list, value) + q.nSize++ +} + +// Dequeue removes the first node from the queue. +func (q *Queue[V]) Dequeue() { + if q.nSize == 0 { + return + } + + q.list = q.list[1:] + q.nSize-- +} + +// Peek returns the value of the first node in the queue. +func (q *Queue[V]) Peek() V { + return q.list[0] +} + +// IsEmpty returns true if the queue is empty. +func (q *Queue[V]) IsEmpty() bool { + return q.nSize == 0 +} diff --git a/queue/queue_test.go b/queue/queue_test.go new file mode 100644 index 0000000..1c6a642 --- /dev/null +++ b/queue/queue_test.go @@ -0,0 +1,41 @@ +package queue_test + +import ( + "testing" + + "github.com/HotPotatoC/sture/queue" +) + +func TestQueue_Enqueue(t *testing.T) { + q := queue.NewQueue[string]() + + q.Enqueue("Adam") + q.Enqueue("John") + q.Enqueue("Bob") + + if q.IsEmpty() { + t.Error("Queue should not be empty") + } + + if q.Peek() != "Adam" { + t.Errorf("Expected: Adam\nGot: %s", q.Peek()) + } +} + +func TestQueue_Dequeue(t *testing.T) { + q := queue.NewQueue[string]() + + q.Enqueue("Adam") + q.Enqueue("John") + q.Enqueue("Bob") + + q.Dequeue() + + if q.IsEmpty() { + t.Error("Queue should not be empty") + } + + if q.Peek() != "John" { + t.Errorf("Expected: John\nGot: %s", q.Peek()) + } +} \ No newline at end of file diff --git a/stack/stack.go b/stack/stack.go new file mode 100644 index 0000000..a0f037f --- /dev/null +++ b/stack/stack.go @@ -0,0 +1,38 @@ +package stack + +import ( + "github.com/HotPotatoC/sture/linkedlist" + "golang.org/x/exp/constraints" +) + +// Stack is a stack. +type Stack[V constraints.Ordered] struct { + list *linkedlist.LinkedList[V] +} + +// NewStack returns a new stack. +func NewStack[V constraints.Ordered]() *Stack[V] { + return &Stack[V]{ + list: linkedlist.NewLinkedList[V](), + } +} + +// Add adds a new node to the top of the stack. +func (s *Stack[V]) Add(value V) { + s.list.Append(value) +} + +// Pop removes the top node from the stack. +func (s *Stack[V]) Pop() { + s.list.Pop() +} + +// Peek returns the value of the top node in the stack. +func (s *Stack[V]) Peek() V { + return s.list.Head().Value() +} + +// IsEmpty returns true if the stack is empty. +func (s *Stack[V]) IsEmpty() bool { + return s.list.IsEmpty() +}