|
|
|
// 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 in
|
|
|
|
// the Software without restriction, including without limitation the rights to
|
|
|
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
|
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
|
|
|
// subject to the following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
|
|
// copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
|
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
|
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
|
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
package collections
|
|
|
|
|
|
|
|
/*****************
|
|
|
|
The arguments for higher-order function algorithms.
|
|
|
|
*****************/
|
|
|
|
|
|
|
|
// UnaryPredicate is a function that takes a single element of type
|
|
|
|
// T and returns a boolean. Used for filtering operations.
|
|
|
|
type UnaryPredicate[T any] func(T) bool
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// Reducer is a function that takes an accumulator and the current
|
|
|
|
// element of the slice.
|
|
|
|
type Reducer[Acc any, T any] func(accumulator Acc, current T)
|
|
|
|
|
|
|
|
/*****************
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if a slice contains each individual element from another slice. Order
|
|
|
|
// is not considered. To consider order, use Subset. Calls SliceContainsEach.
|
|
|
|
func (sl Slice[T]) ContainsEach(needles []T) bool {
|
|
|
|
return SliceContainsEach(sl, needles)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if another slice is a (non-strict) subset of the slice. Calls SliceSubset.
|
|
|
|
func (sl Slice[T]) Subset(sub []T) bool {
|
|
|
|
return SliceSubset(sl, sub)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run a predicate on every element in the slice, and return a slice
|
|
|
|
// that contains every element for which the predicate was true. Calls
|
|
|
|
// SliceFilter.
|
|
|
|
func (sl Slice[T]) Filter(pred UnaryPredicate[T]) Slice[T] {
|
|
|
|
return SliceFilter(sl, pred)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run a predicate on every element in the slice, and return a slice
|
|
|
|
// that contains every element for which the predicate was true. Calls
|
|
|
|
// SliceFilter.
|
|
|
|
func (sl AnySlice[T]) Filter(pred UnaryPredicate[T]) AnySlice[T] {
|
|
|
|
return SliceFilter(sl, pred)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if a slice contains each individual element of another slice. Order
|
|
|
|
// is not considered. To consider order, use SliceSubset.
|
|
|
|
//
|
|
|
|
// Time Complexity: O(n)
|
|
|
|
// Space Complexity: O(n)
|
|
|
|
// Allocations: 1 map, m (needles argument) elements
|
|
|
|
func SliceContainsEach[T comparable](haystack []T, needles []T) bool {
|
|
|
|
// Allocating a map here is better for time complexity and allocations,
|
|
|
|
// since the alternative is working with a slice that would need to be
|
|
|
|
// searched through and in most cases resized when elements are found.
|
|
|
|
needleSet := make(map[T]struct{}, len(needles))
|
|
|
|
for i := 0; i < len(needles); i++ {
|
|
|
|
needleSet[needles[i]] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(haystack); i++ {
|
|
|
|
delete(needleSet, haystack[i])
|
|
|
|
}
|
|
|
|
return len(needleSet) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if another slice is a subset of the slice. This check is non-strict.
|
|
|
|
// For a strict subset, use SliceSubsetStrict.
|
|
|
|
//
|
|
|
|
// Time Complexity: O(n)
|
|
|
|
// Space Complexity: O(1)
|
|
|
|
// Allocations: None
|
|
|
|
func SliceSubset[T comparable](sl []T, sub []T) bool {
|
|
|
|
if len(sub) > len(sl) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return subset(sl, sub)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if another slice is a strict subset of the slice. That is, the other
|
|
|
|
// slice is a subset but not equal to the main slice.
|
|
|
|
//
|
|
|
|
// Time Complexity: O(n)
|
|
|
|
// Space Complexity: O(1)
|
|
|
|
// Allocations: None
|
|
|
|
func SliceSubsetStrict[T comparable](sl []T, sub []T) bool {
|
|
|
|
// Strict subset means the two slices can't be the same size.
|
|
|
|
if len(sub) > len(sl)-1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return subset(sl, sub)
|
|
|
|
}
|
|
|
|
|
|
|
|
func subset[T comparable](sl []T, sub []T) bool {
|
|
|
|
subIdx := 0
|
|
|
|
for i := 0; i < len(sl); i++ {
|
|
|
|
if sl[i] == sub[subIdx] {
|
|
|
|
if subIdx == len(sub)-1 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
subIdx++
|
|
|
|
} else {
|
|
|
|
subIdx = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run an operator on every element in the slice, and return a slice that
|
|
|
|
// contains the result of every operation.
|
|
|
|
//
|
|
|
|
// Sometimes known by other names: Transform, Select
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
// Sometimes known by other names: Where,
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// With a starting accumulator, run the reducer operator with the accumulator
|
|
|
|
// and each element of the slice. The accumulator should be a slice or a pointer
|
|
|
|
// to a value so the change is reflected throughout the combination.
|
|
|
|
//
|
|
|
|
// Sometimes known by other names: Fold, Aggregate, Combine
|
|
|
|
//
|
|
|
|
// Time Complexity: O(n * m) (where m = complexity of reducer)
|
|
|
|
// Space Complexity: O(1)
|
|
|
|
// Allocations: None
|
|
|
|
func SliceReduce[Acc any, T any](
|
|
|
|
sl []T,
|
|
|
|
accumulator Acc,
|
|
|
|
folder Reducer[Acc, T],
|
|
|
|
) Acc {
|
|
|
|
for i := 0; i < len(sl); i++ {
|
|
|
|
folder(accumulator, sl[i])
|
|
|
|
}
|
|
|
|
return accumulator
|
|
|
|
}
|