Add new feature in collection framework:
- Implemented slice data structure - Defined new interfaces Collection, List and Set
This commit is contained in:
36
pkg/collection/collection.go
Normal file
36
pkg/collection/collection.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package collection
|
||||||
|
|
||||||
|
type Iterator[E any] interface {
|
||||||
|
HasNext() bool
|
||||||
|
Next() E
|
||||||
|
NextWithIndex() (int, E)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Iterable[E any] interface {
|
||||||
|
Iterator() Iterator[E]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Collection[E any] interface {
|
||||||
|
Iterable[E]
|
||||||
|
Empty() bool
|
||||||
|
Size() int
|
||||||
|
Push(item E)
|
||||||
|
Contains(item E) bool
|
||||||
|
Delete(item E) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type List[E any] interface {
|
||||||
|
Collection[E]
|
||||||
|
Back() (E, error)
|
||||||
|
Front() (E, error)
|
||||||
|
PushBack(item E)
|
||||||
|
PushFront(item E)
|
||||||
|
Index(item E) (int, error)
|
||||||
|
GetAt(pos int) (E, error)
|
||||||
|
PushAt(item E, pos int)
|
||||||
|
DeleteAt(pos int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Set[E comparable] interface {
|
||||||
|
Collection[E]
|
||||||
|
}
|
||||||
26
pkg/collection/errors.go
Normal file
26
pkg/collection/errors.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package collection
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrPositionNegative = fmt.Errorf("position can not be negative")
|
||||||
|
ErrEmptyCollection = fmt.Errorf("this collection is empty")
|
||||||
|
ErrNodeNotFound = fmt.Errorf("node not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrIndexOutOfBound struct {
|
||||||
|
index int
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrIndexOutOfBound) Error() string {
|
||||||
|
return fmt.Sprintf("index %d out of bound from range %d and %d", e.index, 0, e.size-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrItemNotFound struct {
|
||||||
|
item any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrItemNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("item %v not found", e.item)
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package collection
|
|
||||||
|
|
||||||
type Iterator[E any] interface {
|
|
||||||
HasNext() bool
|
|
||||||
Next() E
|
|
||||||
NextWithIndex() (int, E)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Iterable[E any] interface {
|
|
||||||
Iterator() Iterator[E]
|
|
||||||
}
|
|
||||||
@@ -2,25 +2,10 @@ package collection
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPositionNegative = fmt.Errorf("position can not be negative")
|
|
||||||
ErrEmptyList = fmt.Errorf("no nodes in list")
|
|
||||||
ErrNodeNotFound = fmt.Errorf("node not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrIndexOutOfBound struct {
|
|
||||||
index int
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ErrIndexOutOfBound) Error() string {
|
|
||||||
return fmt.Sprintf("index %d out of bound from range %d and %d", e.index, 0, e.size-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type node[E any] struct {
|
type node[E any] struct {
|
||||||
value E
|
value E
|
||||||
prev *node[E]
|
prev *node[E]
|
||||||
@@ -112,7 +97,7 @@ func (l *LinkedList[E]) GetAt(pos int) (E, error) {
|
|||||||
// findNode returns node at given position from linked list
|
// findNode returns node at given position from linked list
|
||||||
func (l *LinkedList[E]) findNode(pos int) (*node[E], error) {
|
func (l *LinkedList[E]) findNode(pos int) (*node[E], error) {
|
||||||
if l.Empty() {
|
if l.Empty() {
|
||||||
return nil, ErrEmptyList
|
return nil, ErrEmptyCollection
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr := l.head
|
ptr := l.head
|
||||||
@@ -131,6 +116,30 @@ func (l *LinkedList[E]) findNode(pos int) (*node[E], error) {
|
|||||||
return ptr, nil
|
return ptr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LinkedList[E]) Contains(item E) bool {
|
||||||
|
for it := l.Iterator(); it.HasNext(); {
|
||||||
|
x := it.Next()
|
||||||
|
if reflect.DeepEqual(item, x) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LinkedList[E]) Index(item E) (int, error) {
|
||||||
|
for it := l.Iterator(); it.HasNext(); {
|
||||||
|
i, x := it.NextWithIndex()
|
||||||
|
if reflect.DeepEqual(item, x) {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, ErrItemNotFound{item}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LinkedList[E]) Push(item E) {
|
||||||
|
l.PushBack(item)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *LinkedList[E]) PushFront(item E) {
|
func (l *LinkedList[E]) PushFront(item E) {
|
||||||
l.PushAt(item, 0)
|
l.PushAt(item, 0)
|
||||||
}
|
}
|
||||||
@@ -175,17 +184,23 @@ func (l *LinkedList[E]) PushAt(item E, pos int) {
|
|||||||
l.size++
|
l.size++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LinkedList[E]) Delete(item E) error {
|
||||||
|
pos, err := l.Index(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return l.DeleteAt(pos)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteAt deletes node at given position from linked list
|
// DeleteAt deletes node at given position from linked list
|
||||||
func (l *LinkedList[E]) DeleteAt(pos int) error {
|
func (l *LinkedList[E]) DeleteAt(pos int) error {
|
||||||
// validate the position
|
// validate the position
|
||||||
if pos < 0 {
|
if pos < 0 {
|
||||||
log.Println("position can not be negative")
|
|
||||||
return ErrPositionNegative
|
return ErrPositionNegative
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.size == 0 {
|
if l.size == 0 {
|
||||||
log.Println("no nodes in list")
|
return ErrEmptyCollection
|
||||||
return ErrEmptyList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pos == 0 {
|
if pos == 0 {
|
||||||
@@ -199,7 +214,6 @@ func (l *LinkedList[E]) DeleteAt(pos int) error {
|
|||||||
} else {
|
} else {
|
||||||
prevNode, _ := l.findNode(pos - 1)
|
prevNode, _ := l.findNode(pos - 1)
|
||||||
if prevNode == nil {
|
if prevNode == nil {
|
||||||
log.Println("node not found")
|
|
||||||
return ErrNodeNotFound
|
return ErrNodeNotFound
|
||||||
}
|
}
|
||||||
myNode, err := l.findNode(pos)
|
myNode, err := l.findNode(pos)
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ func TestLinkedList_GetAt(t *testing.T) {
|
|||||||
{description: "get middle item", list: NewLinkedList(1, 2, 3), pos: 1, want: 2, err: nil},
|
{description: "get middle item", list: NewLinkedList(1, 2, 3), pos: 1, want: 2, err: nil},
|
||||||
{description: "get last item", list: NewLinkedList(1, 2, 3), pos: 2, want: 3, err: nil},
|
{description: "get last item", list: NewLinkedList(1, 2, 3), pos: 2, want: 3, err: nil},
|
||||||
{description: "get last item", list: NewLinkedList(1, 2, 3), pos: 2, want: 3, err: nil},
|
{description: "get last item", list: NewLinkedList(1, 2, 3), pos: 2, want: 3, err: nil},
|
||||||
{description: "no items in list return zero value of the type", list: NewLinkedList[int](), pos: 0, want: 0, err: ErrEmptyList},
|
{description: "no items in list return zero value of the type", list: NewLinkedList[int](), pos: 0, want: 0, err: ErrEmptyCollection},
|
||||||
{description: "get item with negative position", list: NewLinkedList(1, 2, 3), pos: -1, want: 0, err: ErrPositionNegative},
|
{description: "get item with negative position", list: NewLinkedList(1, 2, 3), pos: -1, want: 0, err: ErrPositionNegative},
|
||||||
{description: "get item with index out of bound", list: NewLinkedList(1, 2, 3), pos: 4, want: 0, err: ErrIndexOutOfBound{4, 3}},
|
{description: "get item with index out of bound", list: NewLinkedList(1, 2, 3), pos: 4, want: 0, err: ErrIndexOutOfBound{4, 3}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range useCases {
|
for _, tt := range useCases {
|
||||||
result, err := tt.list.GetAt(tt.pos)
|
result, err := tt.list.GetAt(tt.pos)
|
||||||
if result != tt.want && err != tt.err {
|
if result != tt.want || err != tt.err {
|
||||||
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,14 +74,14 @@ func TestLinkedList_Back(t *testing.T) {
|
|||||||
err error
|
err error
|
||||||
want int
|
want int
|
||||||
}{
|
}{
|
||||||
{description: "no items in list return zero value of the type", list: NewLinkedList[int](), want: 0, err: ErrEmptyList},
|
{description: "no items in list return zero value of the type", list: NewLinkedList[int](), want: 0, err: ErrEmptyCollection},
|
||||||
{description: "singleton list return first element", list: NewLinkedList(1), want: 1},
|
{description: "singleton list return first element", list: NewLinkedList(1), want: 1},
|
||||||
{description: "get last item", list: NewLinkedList(1, 2, 3), want: 3},
|
{description: "get last item", list: NewLinkedList(1, 2, 3), want: 3},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range useCases {
|
for _, tt := range useCases {
|
||||||
result, err := tt.list.Back()
|
result, err := tt.list.Back()
|
||||||
if result != tt.want && err != tt.err {
|
if result != tt.want || err != tt.err {
|
||||||
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,19 +94,44 @@ func TestLinkedList_Front(t *testing.T) {
|
|||||||
err error
|
err error
|
||||||
want int
|
want int
|
||||||
}{
|
}{
|
||||||
{description: "no items in list return zero value of the type", list: NewLinkedList[int](), want: 0, err: ErrEmptyList},
|
{description: "no items in list return zero value of the type", list: NewLinkedList[int](), want: 0, err: ErrEmptyCollection},
|
||||||
{description: "singleton list return first element", list: NewLinkedList(1), want: 1},
|
{description: "singleton list return first element", list: NewLinkedList(1), want: 1},
|
||||||
{description: "get first item", list: NewLinkedList(1, 2, 3), want: 1},
|
{description: "get first item", list: NewLinkedList(1, 2, 3), want: 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range useCases {
|
for _, tt := range useCases {
|
||||||
result, err := tt.list.Front()
|
result, err := tt.list.Front()
|
||||||
if result != tt.want && err != tt.err {
|
if result != tt.want || err != tt.err {
|
||||||
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLinkedList_Push(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *LinkedList[int]
|
||||||
|
modified *LinkedList[int]
|
||||||
|
item int
|
||||||
|
}{
|
||||||
|
{description: "push item in empty slice",
|
||||||
|
original: NewLinkedList[int](),
|
||||||
|
modified: NewLinkedList(1),
|
||||||
|
item: 1},
|
||||||
|
{description: "push item in slice with item in last position",
|
||||||
|
original: NewLinkedList(1, 2),
|
||||||
|
modified: NewLinkedList(1, 2, 3),
|
||||||
|
item: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
tt.original.Push(tt.item)
|
||||||
|
if !compareLists(tt.original, tt.modified) {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLinkedList_PushAt(t *testing.T) {
|
func TestLinkedList_PushAt(t *testing.T) {
|
||||||
useCases := []struct {
|
useCases := []struct {
|
||||||
description string
|
description string
|
||||||
@@ -205,6 +230,41 @@ func TestLinkedList_PushFront(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLinkedList_Delete(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *LinkedList[int]
|
||||||
|
modified *LinkedList[int]
|
||||||
|
item int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "delete item in empty list",
|
||||||
|
original: NewLinkedList[int](),
|
||||||
|
modified: NewLinkedList[int](),
|
||||||
|
item: 0,
|
||||||
|
err: ErrItemNotFound{0}},
|
||||||
|
{description: "delete item in list with item in first position",
|
||||||
|
original: NewLinkedList(1, 2, 3),
|
||||||
|
modified: NewLinkedList(2, 3),
|
||||||
|
item: 1},
|
||||||
|
{description: "delete item in list with item in middle position",
|
||||||
|
original: NewLinkedList(1, 2, 3),
|
||||||
|
modified: NewLinkedList(1, 3),
|
||||||
|
item: 2},
|
||||||
|
{description: "delete item in list with item in last position",
|
||||||
|
original: NewLinkedList(1, 2, 3),
|
||||||
|
modified: NewLinkedList(1, 2),
|
||||||
|
item: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
err := tt.original.Delete(tt.item)
|
||||||
|
if !compareLists(tt.original, tt.modified) || err != tt.err {
|
||||||
|
t.Errorf("test: %s want {%v, %v} got {%v, %v}", tt.description, tt.modified, tt.err, tt.original, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLinkedList_DeleteAt(t *testing.T) {
|
func TestLinkedList_DeleteAt(t *testing.T) {
|
||||||
useCases := []struct {
|
useCases := []struct {
|
||||||
description string
|
description string
|
||||||
@@ -215,17 +275,17 @@ func TestLinkedList_DeleteAt(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{description: "delete item with negative position",
|
{description: "delete item with negative position",
|
||||||
original: NewLinkedList[int](),
|
original: NewLinkedList[int](),
|
||||||
modified: nil,
|
modified: NewLinkedList[int](),
|
||||||
pos: -1,
|
pos: -1,
|
||||||
err: ErrPositionNegative},
|
err: ErrPositionNegative},
|
||||||
{description: "delete item in empty list",
|
{description: "delete item in empty list",
|
||||||
original: NewLinkedList[int](),
|
original: NewLinkedList[int](),
|
||||||
modified: nil,
|
modified: NewLinkedList[int](),
|
||||||
pos: 0,
|
pos: 0,
|
||||||
err: ErrEmptyList},
|
err: ErrEmptyCollection},
|
||||||
{description: "delete item in position not found",
|
{description: "delete item in position not found",
|
||||||
original: NewLinkedList(1, 2, 3),
|
original: NewLinkedList(1, 2, 3),
|
||||||
modified: NewLinkedList[int](),
|
modified: NewLinkedList(1, 2, 3),
|
||||||
pos: 5,
|
pos: 5,
|
||||||
err: ErrNodeNotFound},
|
err: ErrNodeNotFound},
|
||||||
{description: "delete item in first position",
|
{description: "delete item in first position",
|
||||||
@@ -247,8 +307,49 @@ func TestLinkedList_DeleteAt(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range useCases {
|
for _, tt := range useCases {
|
||||||
err := tt.original.DeleteAt(tt.pos)
|
err := tt.original.DeleteAt(tt.pos)
|
||||||
if !compareLists(tt.original, tt.modified) && err != tt.err {
|
if !compareLists(tt.original, tt.modified) || err != tt.err {
|
||||||
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
t.Errorf("test: %s want {%v, %v} got {%v, %v}", tt.description, tt.modified, tt.err, tt.original, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedList_Contains(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
list *LinkedList[int]
|
||||||
|
item int
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{description: "empty linked list contain item", list: NewLinkedList[int](), item: 0, want: false},
|
||||||
|
{description: "linked list with items search item no found", list: NewLinkedList[int](1, 2, 3), item: 0, want: false},
|
||||||
|
{description: "linked list with items search item found", list: NewLinkedList[int](1, 2, 3), item: 3, want: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
result := tt.list.Contains(tt.item)
|
||||||
|
if result != tt.want {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedList_Index(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
list *LinkedList[int]
|
||||||
|
item int
|
||||||
|
pos int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "empty linked list contain item", list: NewLinkedList[int](), item: 0, pos: 0, err: ErrItemNotFound{0}},
|
||||||
|
{description: "linked list with items search item no found", list: NewLinkedList[int](1, 2, 3), item: 0, pos: 0, err: ErrItemNotFound{0}},
|
||||||
|
{description: "linked list with items search item found", list: NewLinkedList[int](1, 2, 3), item: 3, pos: 2, err: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
pos, err := tt.list.Index(tt.item)
|
||||||
|
if pos != tt.pos || err != tt.err {
|
||||||
|
t.Errorf("test: %s want {%v, %v} got {%v, %v}", tt.description, tt.pos, tt.err, pos, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
176
pkg/collection/slice.go
Normal file
176
pkg/collection/slice.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package collection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sliceIterator[E any] struct {
|
||||||
|
index int
|
||||||
|
slice []E
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *sliceIterator[E]) HasNext() bool {
|
||||||
|
return it.index < len(it.slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *sliceIterator[E]) Next() E {
|
||||||
|
item := it.slice[it.index]
|
||||||
|
it.index += 1
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *sliceIterator[E]) NextWithIndex() (int, E) {
|
||||||
|
index := it.index
|
||||||
|
item := it.slice[index]
|
||||||
|
it.index += 1
|
||||||
|
return index, item
|
||||||
|
}
|
||||||
|
|
||||||
|
type Slice[E any] struct {
|
||||||
|
inner []E
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSlice[E any](items ...E) *Slice[E] {
|
||||||
|
return &Slice[E]{items}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Iterator() Iterator[E] {
|
||||||
|
if s.Empty() {
|
||||||
|
return &emptyListIterator[E]{}
|
||||||
|
}
|
||||||
|
return &sliceIterator[E]{0, s.inner}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Empty() bool {
|
||||||
|
return s.Size() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Size() int {
|
||||||
|
return len(s.inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Back() (E, error) {
|
||||||
|
return s.GetAt(s.Size() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Front() (E, error) {
|
||||||
|
return s.GetAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) GetAt(pos int) (E, error) {
|
||||||
|
if s.Empty() {
|
||||||
|
return *new(E), ErrEmptyCollection
|
||||||
|
}
|
||||||
|
if pos < 0 {
|
||||||
|
return *new(E), ErrPositionNegative
|
||||||
|
}
|
||||||
|
if pos < 0 || pos >= s.Size() {
|
||||||
|
return *new(E), ErrIndexOutOfBound{pos, s.Size()}
|
||||||
|
}
|
||||||
|
return s.inner[pos], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Push(item E) {
|
||||||
|
s.PushBack(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) PushBack(item E) {
|
||||||
|
pos := int(math.Max(0.0, float64(s.Size())))
|
||||||
|
s.PushAt(item, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) PushFront(item E) {
|
||||||
|
s.PushAt(item, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) PushAt(item E, pos int) {
|
||||||
|
s.inner = insert(s.inner, item, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert[E any](slice []E, item E, pos int) []E {
|
||||||
|
// found https://stackoverflow.com/questions/46128016/insert-a-value-in-a-slice-at-a-given-index
|
||||||
|
n := len(slice)
|
||||||
|
if pos < 0 {
|
||||||
|
pos = (pos%n + n) % n
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case pos == n: // nil or empty slice or after last element
|
||||||
|
return append(slice, item)
|
||||||
|
|
||||||
|
case pos < n: // pos < len(slice)
|
||||||
|
slice = append(slice[:pos+1], slice[pos:]...)
|
||||||
|
slice[pos] = item
|
||||||
|
return slice
|
||||||
|
|
||||||
|
case pos < cap(slice): // pos > len(slice)
|
||||||
|
slice = slice[:pos+1]
|
||||||
|
var zero E
|
||||||
|
for i := n; i < pos; i++ {
|
||||||
|
slice[i] = zero
|
||||||
|
}
|
||||||
|
slice[pos] = item
|
||||||
|
return slice
|
||||||
|
|
||||||
|
default:
|
||||||
|
b := make([]E, pos+1) // malloc
|
||||||
|
if n > 0 {
|
||||||
|
copy(b, slice)
|
||||||
|
}
|
||||||
|
b[pos] = item
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Delete(item E) error {
|
||||||
|
pos, err := s.Index(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.DeleteAt(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) DeleteAt(pos int) error {
|
||||||
|
if s.Empty() {
|
||||||
|
return ErrEmptyCollection
|
||||||
|
}
|
||||||
|
s.inner = append(s.inner[:pos], s.inner[pos+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Contains(item E) bool {
|
||||||
|
for _, elem := range s.inner {
|
||||||
|
if reflect.DeepEqual(elem, item) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) Index(item E) (int, error) {
|
||||||
|
for i, x := range s.inner {
|
||||||
|
if reflect.DeepEqual(x, item) {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, ErrItemNotFound{item}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[E]) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("[")
|
||||||
|
for i := 0; i < s.Size(); i++ {
|
||||||
|
var str string
|
||||||
|
item, _ := s.GetAt(i)
|
||||||
|
if i >= s.Size()-1 {
|
||||||
|
str = fmt.Sprintf("%v", item)
|
||||||
|
} else {
|
||||||
|
str = fmt.Sprintf("%v, ", item)
|
||||||
|
}
|
||||||
|
sb.WriteString(str)
|
||||||
|
}
|
||||||
|
sb.WriteString("]")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
330
pkg/collection/slice_test.go
Normal file
330
pkg/collection/slice_test.go
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
package collection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSlice_Empty(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
slice *Slice[int]
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{description: "empty slice", slice: NewSlice[int](), want: true},
|
||||||
|
{description: "full slice", slice: NewSlice[int](1, 2, 3), want: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
result := tt.slice.Empty()
|
||||||
|
if result != tt.want {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_Size(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
slice *Slice[int]
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{description: "empty slice", slice: NewSlice[int](), want: 0},
|
||||||
|
{description: "full slice", slice: NewSlice[int](1, 2, 3), want: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
result := tt.slice.Size()
|
||||||
|
if result != tt.want {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_GetAt(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
slice *Slice[int]
|
||||||
|
pos int
|
||||||
|
item int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "get item on empty slice", slice: NewSlice[int](), pos: 0, item: 0, err: ErrEmptyCollection},
|
||||||
|
{description: "get item on full slice with out of bound index", slice: NewSlice[int](1, 2, 3), pos: 5, item: 0, err: ErrIndexOutOfBound{5, 3}},
|
||||||
|
{description: "get item on full slice with negative index", slice: NewSlice[int](1, 2, 3), pos: -1, item: 0, err: ErrPositionNegative},
|
||||||
|
{description: "get item on full slice in first position", slice: NewSlice[int](1, 2, 3), pos: 0, item: 1, err: nil},
|
||||||
|
{description: "get item on full slice in middle position", slice: NewSlice[int](1, 2, 3), pos: 1, item: 2, err: nil},
|
||||||
|
{description: "get item on full slice in last position", slice: NewSlice[int](1, 2, 3), pos: 2, item: 3, err: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
result, err := tt.slice.GetAt(tt.pos)
|
||||||
|
if result != tt.item || err != tt.err {
|
||||||
|
t.Errorf("test: %s want {%v, %v} got {%v, %v}", tt.description, tt.item, tt.err, result, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_Back(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
slice *Slice[int]
|
||||||
|
pos int
|
||||||
|
item int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "get item on empty slice", slice: NewSlice[int](), pos: 0, item: 0, err: ErrEmptyCollection},
|
||||||
|
{description: "get item on full slice in last position", slice: NewSlice[int](1, 2, 3), pos: 2, item: 3, err: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
result, err := tt.slice.Back()
|
||||||
|
if result != tt.item || err != tt.err {
|
||||||
|
t.Errorf("test: %s want {%v, %v} got {%v, %v}", tt.description, tt.item, tt.err, result, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_Front(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
slice *Slice[int]
|
||||||
|
pos int
|
||||||
|
item int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "get item on empty slice", slice: NewSlice[int](), pos: 0, item: 0, err: ErrEmptyCollection},
|
||||||
|
{description: "get item on full slice in first position", slice: NewSlice[int](1, 2, 3), pos: 2, item: 1, err: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
result, err := tt.slice.Front()
|
||||||
|
if result != tt.item || err != tt.err {
|
||||||
|
t.Errorf("test: %s want {%v, %v} got {%v, %v}", tt.description, tt.item, tt.err, result, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_PushAt(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *Slice[int]
|
||||||
|
modified *Slice[int]
|
||||||
|
item int
|
||||||
|
pos int
|
||||||
|
}{
|
||||||
|
{description: "push item in empty slice",
|
||||||
|
original: NewSlice[int](),
|
||||||
|
modified: NewSlice(1),
|
||||||
|
item: 1,
|
||||||
|
pos: 0},
|
||||||
|
{description: "push item in slice with item in first position",
|
||||||
|
original: NewSlice(1, 2, 3),
|
||||||
|
modified: NewSlice(0, 1, 2, 3),
|
||||||
|
item: 0,
|
||||||
|
pos: 0},
|
||||||
|
{description: "push item in slice with item in middle position",
|
||||||
|
original: NewSlice(1, 3),
|
||||||
|
modified: NewSlice(1, 2, 3),
|
||||||
|
item: 2,
|
||||||
|
pos: 1},
|
||||||
|
{description: "push item in slice with item in last position",
|
||||||
|
original: NewSlice(1, 2),
|
||||||
|
modified: NewSlice(1, 2, 3),
|
||||||
|
item: 3,
|
||||||
|
pos: 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
tt.original.PushAt(tt.item, tt.pos)
|
||||||
|
if !reflect.DeepEqual(tt.original.inner, tt.modified.inner) {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_Push(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *Slice[int]
|
||||||
|
modified *Slice[int]
|
||||||
|
item int
|
||||||
|
}{
|
||||||
|
{description: "push item in empty slice",
|
||||||
|
original: NewSlice[int](),
|
||||||
|
modified: NewSlice(1),
|
||||||
|
item: 1},
|
||||||
|
{description: "push item in slice with item in last position",
|
||||||
|
original: NewSlice(1, 2),
|
||||||
|
modified: NewSlice(1, 2, 3),
|
||||||
|
item: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
tt.original.Push(tt.item)
|
||||||
|
if !reflect.DeepEqual(tt.original.inner, tt.modified.inner) {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_PushBack(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *Slice[int]
|
||||||
|
modified *Slice[int]
|
||||||
|
item int
|
||||||
|
}{
|
||||||
|
{description: "push item in empty slice",
|
||||||
|
original: NewSlice[int](),
|
||||||
|
modified: NewSlice(1),
|
||||||
|
item: 1},
|
||||||
|
{description: "push item in slice with item in last position",
|
||||||
|
original: NewSlice(1, 2),
|
||||||
|
modified: NewSlice(1, 2, 3),
|
||||||
|
item: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
tt.original.PushBack(tt.item)
|
||||||
|
if !reflect.DeepEqual(tt.original.inner, tt.modified.inner) {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_PushFront(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *Slice[int]
|
||||||
|
modified *Slice[int]
|
||||||
|
item int
|
||||||
|
}{
|
||||||
|
{description: "push item in empty slice",
|
||||||
|
original: NewSlice[int](),
|
||||||
|
modified: NewSlice(1),
|
||||||
|
item: 1},
|
||||||
|
{description: "push item in slice with item in first position",
|
||||||
|
original: NewSlice(2, 3),
|
||||||
|
modified: NewSlice(1, 2, 3),
|
||||||
|
item: 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
tt.original.PushFront(tt.item)
|
||||||
|
if !reflect.DeepEqual(tt.original.inner, tt.modified.inner) {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_Delete(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *Slice[int]
|
||||||
|
modified *Slice[int]
|
||||||
|
item int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "delete item in empty slice",
|
||||||
|
original: NewSlice[int](),
|
||||||
|
modified: NewSlice[int](),
|
||||||
|
item: 0,
|
||||||
|
err: ErrItemNotFound{0}},
|
||||||
|
{description: "delete item in slice with item in first position",
|
||||||
|
original: NewSlice(1, 2, 3),
|
||||||
|
modified: NewSlice(2, 3),
|
||||||
|
item: 1},
|
||||||
|
{description: "delete item in slice with item in middle position",
|
||||||
|
original: NewSlice(1, 2, 3),
|
||||||
|
modified: NewSlice(1, 3),
|
||||||
|
item: 2},
|
||||||
|
{description: "delete item in slice with item in last position",
|
||||||
|
original: NewSlice(1, 2, 3),
|
||||||
|
modified: NewSlice(1, 2),
|
||||||
|
item: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
err := tt.original.Delete(tt.item)
|
||||||
|
if !reflect.DeepEqual(tt.original.inner, tt.modified.inner) || err != tt.err {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_DeleteAt(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
original *Slice[int]
|
||||||
|
modified *Slice[int]
|
||||||
|
pos int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "delete item in empty slice",
|
||||||
|
original: NewSlice[int](),
|
||||||
|
modified: NewSlice[int](),
|
||||||
|
pos: 0,
|
||||||
|
err: ErrEmptyCollection},
|
||||||
|
{description: "delete item in slice with item in first position",
|
||||||
|
original: NewSlice(1, 2, 3),
|
||||||
|
modified: NewSlice(2, 3),
|
||||||
|
pos: 0},
|
||||||
|
{description: "delete item in slice with item in middle position",
|
||||||
|
original: NewSlice(1, 2, 3),
|
||||||
|
modified: NewSlice(1, 3),
|
||||||
|
pos: 1},
|
||||||
|
{description: "delete item in slice with item in last position",
|
||||||
|
original: NewSlice(1, 2, 3),
|
||||||
|
modified: NewSlice(1, 2),
|
||||||
|
pos: 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
err := tt.original.DeleteAt(tt.pos)
|
||||||
|
if !reflect.DeepEqual(tt.original.inner, tt.modified.inner) || err != tt.err {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_Contains(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
slice *Slice[int]
|
||||||
|
item int
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{description: "empty slice contain item", slice: NewSlice[int](), item: 0, want: false},
|
||||||
|
{description: "slice with items search item no found", slice: NewSlice[int](1, 2, 3), item: 0, want: false},
|
||||||
|
{description: "slice with items search item found", slice: NewSlice[int](1, 2, 3), item: 3, want: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
result := tt.slice.Contains(tt.item)
|
||||||
|
if result != tt.want {
|
||||||
|
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice_Index(t *testing.T) {
|
||||||
|
useCases := []struct {
|
||||||
|
description string
|
||||||
|
slice *Slice[int]
|
||||||
|
item int
|
||||||
|
pos int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{description: "empty slice contain item", slice: NewSlice[int](), item: 0, pos: 0, err: ErrItemNotFound{0}},
|
||||||
|
{description: "slice with items search item no found", slice: NewSlice[int](1, 2, 3), item: 0, pos: 0, err: ErrItemNotFound{0}},
|
||||||
|
{description: "slice with items search item found", slice: NewSlice[int](1, 2, 3), item: 3, pos: 2, err: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range useCases {
|
||||||
|
pos, err := tt.slice.Index(tt.item)
|
||||||
|
if pos != tt.pos || err != tt.err {
|
||||||
|
t.Errorf("test: %s want {%v, %v} got {%v, %v}", tt.description, tt.pos, tt.err, pos, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrEmptyStack is error when you have an empty stack structure
|
|
||||||
ErrEmptyStack = fmt.Errorf("this stack is empty")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Stack is an structure with method for handle underlying slice as a stack.
|
// Stack is an structure with method for handle underlying slice as a stack.
|
||||||
type Stack[E any] struct {
|
type Stack[E any] struct {
|
||||||
items []E
|
items []E
|
||||||
@@ -35,16 +30,16 @@ func (s *Stack[E]) Empty() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Top get first item in the stack structure, but
|
// Top get first item in the stack structure, but
|
||||||
// if stack is empty well this method return ErrEmptyStack error.
|
// if stack is empty well this method return ErrEmptyCollection error.
|
||||||
func (s *Stack[E]) Top() (E, error) {
|
func (s *Stack[E]) Top() (E, error) {
|
||||||
if s.Empty() {
|
if s.Empty() {
|
||||||
return *new(E), ErrEmptyStack
|
return *new(E), ErrEmptyCollection
|
||||||
}
|
}
|
||||||
return s.items[s.Size()-1], nil
|
return s.items[s.Size()-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop get and remove first item in the stack structure, but
|
// Pop get and remove first item in the stack structure, but
|
||||||
// if stack is empty well this method return ErrEmptyStack error.
|
// if stack is empty well this method return ErrEmptyCollection error.
|
||||||
func (s *Stack[E]) Pop() (E, error) {
|
func (s *Stack[E]) Pop() (E, error) {
|
||||||
item, err := s.Top()
|
item, err := s.Top()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ var staticTests = []struct {
|
|||||||
size int
|
size int
|
||||||
top pair
|
top pair
|
||||||
}{
|
}{
|
||||||
{"empty stack", NewStack[int](), true, 0, pair{0, ErrEmptyStack}},
|
{"empty stack", NewStack[int](), true, 0, pair{0, ErrEmptyCollection}},
|
||||||
{"stack with items", NewStack(1, 2, 3), false, 3, pair{3, nil}},
|
{"stack with items", NewStack(1, 2, 3), false, 3, pair{3, nil}},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ var popTests = []struct {
|
|||||||
input *Stack[int]
|
input *Stack[int]
|
||||||
item pair
|
item pair
|
||||||
}{
|
}{
|
||||||
{"empty stack pop item", NewStack[int](), pair{0, ErrEmptyStack}},
|
{"empty stack pop item", NewStack[int](), pair{0, ErrEmptyCollection}},
|
||||||
{"stack with items pop item", NewStack(1, 2, 3), pair{3, nil}},
|
{"stack with items pop item", NewStack(1, 2, 3), pair{3, nil}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user