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
} }
@ -23,11 +23,11 @@ func NewSet[T comparable](slice []T) Set[T] {
// Initialize a new set while preserving the elements that were found to be duplicate. // Initialize a new set while preserving the elements that were found to be duplicate.
// The duplicate slice will be nil if there are no duplicates to save on allocations. // The duplicate slice will be nil if there are no duplicates to save on allocations.
// //
// Time Complexity: O(n) // Time Complexity: O(n)
// 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,14 +46,17 @@ 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.
// //
// Time Complexity: O(n) // Time Complexity: O(n)
// Space Complexity: O(n) // Space Complexity: O(n)
// Allocations: 1 slice, n elements // Allocations: 1 slice, n elements
func (set Set[T]) ToSlice() []T { func (set Set[T]) ToSlice() []T {
slice := make([]T, len(set)) slice := make([]T, len(set))
i := 0 i := 0
@ -66,27 +69,27 @@ func (set Set[T]) ToSlice() []T {
// Add an element to the set. // Add an element to the set.
// //
// Time Complexity: O(1) // Time Complexity: O(1)
// Space Complexity: O(1) // Space Complexity: O(1)
// Allocations: 1 elements // Allocations: 1 elements
func (s Set[T]) Add(el T) { func (s Set[T]) Add(el T) {
s[el] = struct{}{} s[el] = struct{}{}
} }
// Remove an element from the set. // Remove an element from the set.
// //
// Time Complexity: O(1) // Time Complexity: O(1)
// Space Complexity: O(1) // Space Complexity: O(1)
// Allocations: None // Allocations: None
func (s Set[T]) Remove(el T) { func (s Set[T]) Remove(el T) {
delete(s, el) delete(s, el)
} }
// Check if the set contains a particular value. // Check if the set contains a particular value.
// //
// Time Complexity: O(1) // Time Complexity: O(1)
// Space Complexity: O(1) // Space Complexity: O(1)
// Allocations: None // Allocations: None
func (s Set[T]) Contains(el T) bool { func (s Set[T]) Contains(el T) bool {
_, containsEl := s[el] _, containsEl := s[el]
return containsEl return containsEl
@ -94,9 +97,9 @@ func (s Set[T]) Contains(el T) bool {
// Creates a Clone of the set which contains all the same values. // Creates a Clone of the set which contains all the same 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 (s Set[T]) Clone() Set[T] { func (s Set[T]) Clone() Set[T] {
clone := make(Set[T]) clone := make(Set[T])
for el := range s { for el := range s {
@ -107,9 +110,9 @@ func (s Set[T]) Clone() Set[T] {
// Checks if the set is equal to another. // Checks if the set is equal to another.
// //
// Time Complexity: O(n) // Time Complexity: O(n)
// Space Complexity: O(1) // Space Complexity: O(1)
// Allocations: None // Allocations: None
func (s Set[T]) Equals(s2 Set[T]) bool { func (s Set[T]) Equals(s2 Set[T]) bool {
if len(s) != len(s2) { if len(s) != len(s2) {
return false return false

@ -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