more set tests, started writing slice stuff

main
RageCage64 2 years ago
parent 99865a3ccd
commit 21e82bb2b5

@ -1,3 +1,5 @@
# 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.
type Set[T comparable] map[T]struct{}
// Initialize a new Set by providing a slice. The Set will have the
// same type as the slice.
// Initialize a new Set with a set of arguments. Good for if you want to initialize
// a new set in place with values.
//
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations: 1 set, n elements
func NewSet[T comparable](slice []T) Set[T] {
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations: 1 set, n elements.
func NewSet[T comparable](elements ...T) Set[T] {
set := make(Set[T])
for _, el := range slice {
set[el] = struct{}{}
for i := 0; i < len(elements); i++ {
set.Add(elements[i])
}
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.
// The duplicate slice will be nil if there are no duplicates to save on allocations.
//
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations (best case): 1 set, n elements
// 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.
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations (best case): 1 set, n elements
// Allocations (worst case): 1 set, n/2 elements, 2 slices, one with n-1 space, the
// second is the first one resized if necessary.
func NewSetSaveDuplicates[T comparable](slice []T) (Set[T], []T) {
set := make(Set[T])
var dupes []T = nil
@ -46,14 +46,17 @@ func NewSetSaveDuplicates[T comparable](slice []T) (Set[T], []T) {
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.
//
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations: 1 slice, n elements
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations: 1 slice, n elements
func (set Set[T]) ToSlice() []T {
slice := make([]T, len(set))
i := 0
@ -66,27 +69,27 @@ func (set Set[T]) ToSlice() []T {
// Add an element to the set.
//
// Time Complexity: O(1)
// Space Complexity: O(1)
// Allocations: 1 elements
// Time Complexity: O(1)
// Space Complexity: O(1)
// Allocations: 1 elements
func (s Set[T]) Add(el T) {
s[el] = struct{}{}
}
// Remove an element from the set.
//
// Time Complexity: O(1)
// Space Complexity: O(1)
// Allocations: None
// Time Complexity: O(1)
// Space Complexity: O(1)
// Allocations: None
func (s Set[T]) Remove(el T) {
delete(s, el)
}
// Check if the set contains a particular value.
//
// Time Complexity: O(1)
// Space Complexity: O(1)
// Allocations: None
// Time Complexity: O(1)
// Space Complexity: O(1)
// Allocations: None
func (s Set[T]) Contains(el T) bool {
_, containsEl := s[el]
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.
//
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations: 1 set, n elements
// Time Complexity: O(n)
// Space Complexity: O(n)
// Allocations: 1 set, n elements
func (s Set[T]) Clone() Set[T] {
clone := make(Set[T])
for el := range s {
@ -107,9 +110,9 @@ func (s Set[T]) Clone() Set[T] {
// Checks if the set is equal to another.
//
// Time Complexity: O(n)
// Space Complexity: O(1)
// Allocations: None
// Time Complexity: O(n)
// Space Complexity: O(1)
// Allocations: None
func (s Set[T]) Equals(s2 Set[T]) bool {
if len(s) != len(s2) {
return false

@ -7,14 +7,14 @@ import (
"github.com/RageCage64/go-assert"
)
func TestSetToSlice(t *testing.T) {
set := collections.Set[int]{
1: {},
2: {},
3: {},
}
slice := set.ToSlice()
assert.SliceEqual(t, []int{1, 2, 3}, slice)
func runNewSetTestCase[T comparable](t *testing.T, elements ...T) {
t.Helper()
compareSetAndSlice(t, collections.NewSet(elements...), elements)
}
func TestNewSet(t *testing.T) {
runNewSetTestCase(t, []int{1, 2, 3}...)
}
func runNewSetSaveDuplicatesTestCase[T comparable](
@ -24,6 +24,7 @@ func runNewSetSaveDuplicatesTestCase[T comparable](
expectedDupes []T,
) {
t.Helper()
newSet, dupes := collections.NewSetSaveDuplicates(slice)
assert.Assert(
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