more set tests, started writing slice stuff

main
RageCage64 2 years ago
parent 99865a3ccd
commit 21e82bb2b5

@ -1,3 +1,5 @@
# collections-go # collections-go
A dependency-free package of generic Go collection data structures. Compatible with Go 1.18 and above. A dependency-free package of generic Go collection data structures. Compatible with Go 1.18 and above. Strives for the most optimal solutions available with lowest number of allocations.
Contributions are not currently open as this project is in active early development.

@ -0,0 +1,9 @@
package collections
// A UnaryPredicate is a function that takes a single element of type
// T and returns a boolean.
type UnaryPredicate[T any] func(T) bool
// A UnaryOperator is a function that takes a single element of type
// T and returns an element of type T.
type UnaryOperator[T any] func(T) T

@ -6,16 +6,16 @@ package collections
// empty struct initialization any time you add to the set. // empty struct initialization any time you add to the set.
type Set[T comparable] map[T]struct{} type Set[T comparable] map[T]struct{}
// Initialize a new Set by providing a slice. The Set will have the // Initialize a new Set with a set of arguments. Good for if you want to initialize
// same type as the slice. // a new set in place with values.
// //
// Time Complexity: O(n) // Time Complexity: O(n)
// Space Complexity: O(n) // Space Complexity: O(n)
// Allocations: 1 set, n elements // Allocations: 1 set, n elements.
func NewSet[T comparable](slice []T) Set[T] { func NewSet[T comparable](elements ...T) Set[T] {
set := make(Set[T]) set := make(Set[T])
for _, el := range slice { for i := 0; i < len(elements); i++ {
set[el] = struct{}{} set.Add(elements[i])
} }
return set return set
} }
@ -27,7 +27,7 @@ func NewSet[T comparable](slice []T) Set[T] {
// Space Complexity: O(n) // Space Complexity: O(n)
// Allocations (best case): 1 set, n elements // Allocations (best case): 1 set, n elements
// Allocations (worst case): 1 set, n/2 elements, 2 slices, one with n-1 space, the // Allocations (worst case): 1 set, n/2 elements, 2 slices, one with n-1 space, the
// other is the first one resized based on number of duplicates. // second is the first one resized if necessary.
func NewSetSaveDuplicates[T comparable](slice []T) (Set[T], []T) { func NewSetSaveDuplicates[T comparable](slice []T) (Set[T], []T) {
set := make(Set[T]) set := make(Set[T])
var dupes []T = nil var dupes []T = nil
@ -46,7 +46,10 @@ func NewSetSaveDuplicates[T comparable](slice []T) (Set[T], []T) {
set[el] = struct{}{} set[el] = struct{}{}
} }
} }
return set, dupes[:dupesIdx] if dupes != nil && dupesIdx < len(dupes)-1 {
dupes = dupes[:dupesIdx]
}
return set, dupes
} }
// ToSlice will take all of the elements of the set and create a new slice out of them. // ToSlice will take all of the elements of the set and create a new slice out of them.

@ -7,14 +7,14 @@ import (
"github.com/RageCage64/go-assert" "github.com/RageCage64/go-assert"
) )
func TestSetToSlice(t *testing.T) { func runNewSetTestCase[T comparable](t *testing.T, elements ...T) {
set := collections.Set[int]{ t.Helper()
1: {},
2: {}, compareSetAndSlice(t, collections.NewSet(elements...), elements)
3: {}, }
}
slice := set.ToSlice() func TestNewSet(t *testing.T) {
assert.SliceEqual(t, []int{1, 2, 3}, slice) runNewSetTestCase(t, []int{1, 2, 3}...)
} }
func runNewSetSaveDuplicatesTestCase[T comparable]( func runNewSetSaveDuplicatesTestCase[T comparable](
@ -24,6 +24,7 @@ func runNewSetSaveDuplicatesTestCase[T comparable](
expectedDupes []T, expectedDupes []T,
) { ) {
t.Helper() t.Helper()
newSet, dupes := collections.NewSetSaveDuplicates(slice) newSet, dupes := collections.NewSetSaveDuplicates(slice)
assert.Assert( assert.Assert(
t, t,
@ -76,3 +77,41 @@ func TestNewSetSaveDuplicates(t *testing.T) {
) )
}) })
} }
func runSetToSliceTestCase[T comparable](t *testing.T, set collections.Set[T], expected []T) {
assert.SliceEqual(t, expected, set.ToSlice())
}
func TestSetToSlice(t *testing.T) {
runSetToSliceTestCase(
t,
collections.Set[int]{
1: {},
2: {},
3: {},
},
[]int{1, 2, 3},
)
}
func compareSetAndSlice[T comparable](t *testing.T, set collections.Set[T], slice []T) {
t.Helper()
for _, el := range slice {
if !set.Contains(el) {
t.Fatalf("expected set to contain %v", el)
}
}
for el := range set {
found := false
for _, sliceEl := range slice {
if el == sliceEl {
found = true
break
}
}
if !found {
t.Fatalf("set contained unexpected element %v", el)
}
}
}

@ -0,0 +1,87 @@
package collections
/*****************
This section contains the alias API, which allows for receiver-style calling
convention.
*****************/
// Slice is an alias over a Go slice that enables a direct receiver-style API.
// For Slice, T must satisfy comparable.
type Slice[T comparable] []T
// Check if a slice contains an element. Calls SliceContains.
func (sl Slice[T]) Contains(needle T) bool {
return SliceContains(sl, needle)
}
// Run an operator on every element in the slice, and return a slice that
// contains the result of every operation. Calls SliceMap.
func (sl Slice[T]) Map(op UnaryOperator[T]) Slice[T] {
return SliceMap(sl, op)
}
// AnySlice is an alias over a Go slice that does not enforce T satisfying
// comparable. Any method that relies on comparison of elements is not
// available for AnySlice.
type AnySlice[T any] []T
// Run an operator on every element in the slice, and return a slice that
// contains the result of every operation. Calls SliceMap.
func (sl AnySlice[T]) Map(op UnaryOperator[T]) AnySlice[T] {
return SliceMap(sl, op)
}
/*****************
This section contains the direct API, which is a more traditional Go style
API.
*****************/
// Check if a slice contains an element.
//
// Time Complexity: O(n)
// Space Complexity: O(1)
// Allocations: None.
func SliceContains[T comparable](haystack []T, needle T) bool {
for i := 0; i < len(haystack); i++ {
if haystack[i] == needle {
return true
}
}
return false
}
// Run an operator on every element in the slice, and return a slice that
// contains the result of every operation.
//
// Time Complexity: O(n * m) (where m = complexity of operator)
// Space Complexity: O(n)
// Allocations: 1 slice, n elements.
func SliceMap[T any](sl []T, op UnaryOperator[T]) []T {
result := make([]T, len(sl))
for i := 0; i < len(sl); i++ {
result[i] = op(sl[i])
}
return result
}
// Run a predicate on every element in the slice, and return a slice
// that contains every element for which the predicate was true.
//
// Time Complexity: O(n * m) (where m = complexity of predicate)
// Space Complexity: O(n)
// Allocations: 2 slice, first with n elements, second is the first slice
// resized if necessary.
func SliceFilter[T any](sl []T, pred UnaryPredicate[T]) []T {
result := make([]T, len(sl))
resultIdx := 0
for i := 0; i < len(sl); i++ {
if pred(sl[i]) {
result[resultIdx] = sl[i]
resultIdx++
}
}
if resultIdx < len(result)-1 {
result = result[:resultIdx]
}
return result
}

@ -0,0 +1,13 @@
package collections_test
import (
"testing"
"github.com/RageCage64/collections-go"
"github.com/RageCage64/go-assert"
)
func TestSliceContainsDirect(t *testing.T) {
x := []int{1, 2, 3}
assert.Assert(t, collections.SliceContains(x, 1), "it didn't")
}
Loading…
Cancel
Save