diff --git a/.gitignore b/.gitignore index 66fd13c..f2dd955 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,3 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out - -# Dependency directories (remove the comment below to include it) -# vendor/ diff --git a/LICENSE b/LICENSE index e5c546c..523c0f9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 RageCage64 +Copyright (c) 2023 Braydon Kains Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 27ba7a7..482397c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # collections-go -Generic collections to use with Go >1.18 + +A dependency-free package of generic Go collection data structures. Compatible with Go 1.18 and above. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3bda7a2 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/RageCage64/collections-go + +go 1.18 + +require github.com/RageCage64/go-assert v0.2.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3622b8c --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/RageCage64/go-assert v0.2.1 h1:xTDJnm9q2IDdXKKUVUMEr3pCnwQCiubB5gu+t60NbNY= +github.com/RageCage64/go-assert v0.2.1/go.mod h1:YuWzhAJlE4Z2TW2D4msNx1mNU0wA+6lmAL0lP1l7yd4= +github.com/RageCage64/go-assert v0.2.2 h1:wwPA2yibB7XmaQKpw4xk7NfVPdJ1v79GlBvmS8P9ROM= +github.com/RageCage64/go-assert v0.2.2/go.mod h1:YuWzhAJlE4Z2TW2D4msNx1mNU0wA+6lmAL0lP1l7yd4= diff --git a/set.go b/set.go new file mode 100644 index 0000000..72bdc1f --- /dev/null +++ b/set.go @@ -0,0 +1,123 @@ +package collections + +// Set is a type alias over a map to an empty struct. This allows a nice +// API over the hash map functionality already available, where you can instead +// view the set as a single dimension collection and save a lot of clutter of +// 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. +// +// Time Complexity: O(n) +// Space Complexity: O(n) +// Allocations: 1 set, n elements +func NewSet[T comparable](slice []T) Set[T] { + set := make(Set[T]) + for _, el := range slice { + set[el] = struct{}{} + } + return set +} + +// 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. +func NewSetSaveDuplicates[T comparable](slice []T) (Set[T], []T) { + set := make(Set[T]) + var dupes []T = nil + dupesIdx := 0 + for _, el := range slice { + if set.Contains(el) { + if dupes == nil { + // Now that we know there is at least one dupe, we can make the allocation. + // The max possible size of the duplicate array is n-1 (aka a slice where + // every element is the same). + dupes = make([]T, len(slice)-1) + } + dupes[dupesIdx] = el + dupesIdx++ + } else { + set[el] = struct{}{} + } + } + return set, dupes[:dupesIdx] +} + +// 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 +func (set Set[T]) ToSlice() []T { + slice := make([]T, len(set)) + i := 0 + for el := range set { + slice[i] = el + i++ + } + return slice +} + +// Add an element to the set. +// +// 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 +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 +func (s Set[T]) Contains(el T) bool { + _, containsEl := s[el] + return containsEl +} + +// 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 +func (s Set[T]) Clone() Set[T] { + clone := make(Set[T]) + for el := range s { + clone.Add(el) + } + return clone +} + +// Checks if the set is equal to another. +// +// 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 + } + for el := range s { + if !s2.Contains(el) { + return false + } + } + return true +} diff --git a/set_test.go b/set_test.go new file mode 100644 index 0000000..08e0acd --- /dev/null +++ b/set_test.go @@ -0,0 +1,78 @@ +package collections_test + +import ( + "testing" + + "github.com/RageCage64/collections-go" + "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 runNewSetSaveDuplicatesTestCase[T comparable]( + t *testing.T, + slice []T, + expectedSet collections.Set[T], + expectedDupes []T, +) { + t.Helper() + newSet, dupes := collections.NewSetSaveDuplicates(slice) + assert.Assert( + t, + expectedSet.Equals(newSet), + "new set does not equal expectation:\nexpected: %v\ngot: %v", + expectedSet, + newSet, + ) + assert.SliceEqual(t, expectedDupes, dupes) +} + +func TestNewSetSaveDuplicates(t *testing.T) { + t.Run("no duplicates", func(t *testing.T) { + t.Parallel() + runNewSetSaveDuplicatesTestCase( + t, + []int{1, 2, 3}, + collections.Set[int]{ + 1: {}, + 2: {}, + 3: {}, + }, + []int{}, + ) + }) + + t.Run("all duplicates", func(t *testing.T) { + t.Parallel() + runNewSetSaveDuplicatesTestCase( + t, + []int{1, 1, 2, 2, 3, 3}, + collections.Set[int]{ + 1: {}, + 2: {}, + 3: {}, + }, + []int{1, 2, 3}, + ) + }) + + t.Run("all elements the same", func(t *testing.T) { + t.Parallel() + runNewSetSaveDuplicatesTestCase( + t, + []int{1, 1, 1, 1}, + collections.Set[int]{ + 1: {}, + }, + []int{1, 1, 1}, + ) + }) +}