lots more generics

This commit is contained in:
Jesse Duffield 2022-03-19 15:36:46 +11:00
parent c7a629c440
commit eda8f4a5d4
19 changed files with 384 additions and 299 deletions

View file

@ -1,49 +0,0 @@
package list
import (
"golang.org/x/exp/slices"
)
type ComparableList[T comparable] struct {
*List[T]
}
func NewComparable[T comparable]() *ComparableList[T] {
return &ComparableList[T]{List: New[T]()}
}
func NewComparableFromSlice[T comparable](slice []T) *ComparableList[T] {
return &ComparableList[T]{List: NewFromSlice(slice)}
}
func (l *ComparableList[T]) Equal(other *ComparableList[T]) bool {
return l.EqualSlice(other.ToSlice())
}
func (l *ComparableList[T]) EqualSlice(other []T) bool {
return slices.Equal(l.ToSlice(), other)
}
func (l *ComparableList[T]) Compact() {
l.slice = slices.Compact(l.slice)
}
func (l *ComparableList[T]) Index(needle T) int {
return slices.Index(l.slice, needle)
}
func (l *ComparableList[T]) Contains(needle T) bool {
return slices.Contains(l.slice, needle)
}
func (l *ComparableList[T]) SortFuncInPlace(test func(a T, b T) bool) {
slices.SortFunc(l.slice, test)
}
func (l *ComparableList[T]) SortFunc(test func(a T, b T) bool) *ComparableList[T] {
newSlice := slices.Clone(l.slice)
slices.SortFunc(newSlice, test)
return NewComparableFromSlice(newSlice)
}

View file

@ -1,72 +0,0 @@
package list
func Some[T any](slice []T, test func(T) bool) bool {
for _, value := range slice {
if test(value) {
return true
}
}
return false
}
func Every[T any](slice []T, test func(T) bool) bool {
for _, value := range slice {
if !test(value) {
return false
}
}
return true
}
func Map[T any, V any](slice []T, f func(T) V) []V {
result := make([]V, len(slice))
for i, value := range slice {
result[i] = f(value)
}
return result
}
func MapInPlace[T any](slice []T, f func(T) T) {
for i, value := range slice {
slice[i] = f(value)
}
}
func Filter[T any](slice []T, test func(T) bool) []T {
result := make([]T, 0)
for _, element := range slice {
if test(element) {
result = append(result, element)
}
}
return result
}
func FilterInPlace[T any](slice []T, test func(T) bool) []T {
newLength := 0
for _, element := range slice {
if test(element) {
slice[newLength] = element
newLength++
}
}
return slice[:newLength]
}
func Reverse[T any](slice []T) []T {
result := make([]T, len(slice))
for i := range slice {
result[i] = slice[len(slice)-1-i]
}
return result
}
func ReverseInPlace[T any](slice []T) {
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i]
}
}

View file

@ -1,117 +0,0 @@
package list
import (
"golang.org/x/exp/slices"
)
type List[T any] struct {
slice []T
}
func New[T any]() *List[T] {
return &List[T]{}
}
func NewFromSlice[T any](slice []T) *List[T] {
return &List[T]{slice: slice}
}
func (l *List[T]) ToSlice() []T {
return l.slice
}
// Mutative methods
func (l *List[T]) Push(v T) {
l.slice = append(l.slice, v)
}
func (l *List[T]) Pop() {
l.slice = l.slice[0 : len(l.slice)-1]
}
func (l *List[T]) Insert(index int, values ...T) {
l.slice = slices.Insert(l.slice, index, values...)
}
func (l *List[T]) Append(values ...T) {
l.slice = append(l.slice, values...)
}
func (l *List[T]) Prepend(values ...T) {
l.slice = append(values, l.slice...)
}
func (l *List[T]) Remove(index int) {
l.Delete(index, index+1)
}
func (l *List[T]) Delete(from int, to int) {
l.slice = slices.Delete(l.slice, from, to)
}
func (l *List[T]) FilterInPlace(test func(value T) bool) {
l.slice = FilterInPlace(l.slice, test)
}
func (l *List[T]) MapInPlace(f func(value T) T) {
MapInPlace(l.slice, f)
}
func (l *List[T]) ReverseInPlace() {
ReverseInPlace(l.slice)
}
// Non-mutative methods
// Similar to Append but we leave the original slice untouched and return a new list
func (l *List[T]) Concat(values ...T) *List[T] {
newSlice := make([]T, 0, len(l.slice)+len(values))
newSlice = append(newSlice, l.slice...)
newSlice = append(newSlice, values...)
return &List[T]{slice: newSlice}
}
func (l *List[T]) Filter(test func(value T) bool) *List[T] {
return NewFromSlice(Filter(l.slice, test))
}
// Unfortunately this does not support mapping from one type to another
// because Go does not yet (and may never) support methods defining their own
// type parameters. For that functionality you'll need to use the standalone
// Map function instead
func (l *List[T]) Map(f func(value T) T) *List[T] {
return NewFromSlice(Map(l.slice, f))
}
func (l *List[T]) Clone() *List[T] {
return NewFromSlice(slices.Clone(l.slice))
}
func (l *List[T]) Some(test func(value T) bool) bool {
return Some(l.slice, test)
}
func (l *List[T]) Every(test func(value T) bool) bool {
return Every(l.slice, test)
}
func (l *List[T]) IndexFunc(f func(T) bool) int {
return slices.IndexFunc(l.slice, f)
}
func (l *List[T]) ContainsFunc(f func(T) bool) bool {
return l.IndexFunc(f) != -1
}
func (l *List[T]) Reverse() *List[T] {
return NewFromSlice(Reverse(l.slice))
}
func (l *List[T]) IsEmpty() bool {
return len(l.slice) == 0
}
func (l *List[T]) Len() int {
return len(l.slice)
}

View file

@ -1,4 +1,4 @@
package hashmap
package maps
func Keys[Key comparable, Value any](m map[Key]Value) []Key {
keys := make([]Key, 0, len(m))

View file

@ -1,6 +1,6 @@
package set
import "github.com/jesseduffield/generics/hashmap"
import "github.com/jesseduffield/generics/maps"
type Set[T comparable] struct {
hashMap map[T]bool
@ -45,5 +45,5 @@ func (s *Set[T]) Includes(value T) bool {
// output slice is not necessarily in the same order that items were added
func (s *Set[T]) ToSlice() []T {
return hashmap.Keys(s.hashMap)
return maps.Keys(s.hashMap)
}

View file

@ -0,0 +1,117 @@
package slices
import (
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
)
// This file delegates to the official slices package, so that we end up with a superset of the official API.
// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[E comparable](s1, s2 []E) bool {
return slices.Equal(s1, s2)
}
// EqualFunc reports whether two slices are equal using a comparison
// function on each pair of elements. If the lengths are different,
// EqualFunc returns false. Otherwise, the elements are compared in
// increasing index order, and the comparison stops at the first index
// for which eq returns false.
func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
return slices.EqualFunc(s1, s2, eq)
}
// Compare compares the elements of s1 and s2.
// The elements are compared sequentially, starting at index 0,
// until one element is not equal to the other.
// The result of comparing the first non-matching elements is returned.
// If both slices are equal until one of them ends, the shorter slice is
// considered less than the longer one.
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
// Comparisons involving floating point NaNs are ignored.
func Compare[E constraints.Ordered](s1, s2 []E) int {
return slices.Compare(s1, s2)
}
// CompareFunc is like Compare but uses a comparison function
// on each pair of elements. The elements are compared in increasing
// index order, and the comparisons stop after the first time cmp
// returns non-zero.
// The result is the first non-zero result of cmp; if cmp always
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
// and +1 if len(s1) > len(s2).
func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int {
return slices.CompareFunc(s1, s2, cmp)
}
// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func Index[E comparable](s []E, v E) int {
return slices.Index(s, v)
}
// IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do.
func IndexFunc[E any](s []E, f func(E) bool) int {
return slices.IndexFunc(s, f)
}
// Contains reports whether v is present in s.
func Contains[E comparable](s []E, v E) bool {
return slices.Contains(s, v)
}
// Insert inserts the values v... into s at index i,
// returning the modified slice.
// In the returned slice r, r[i] == v[0].
// Insert panics if i is out of range.
// This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
return slices.Insert(s, i, v...)
}
// Delete removes the elements s[i:j] from s, returning the modified slice.
// Delete panics if s[i:j] is not a valid slice of s.
// Delete modifies the contents of the slice s; it does not create a new slice.
// Delete is O(len(s)-(j-i)), so if many items must be deleted, it is better to
// make a single call deleting them all together than to delete one at a time.
func Delete[S ~[]E, E any](s S, i, j int) S {
return slices.Delete(s, i, j)
}
// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
return slices.Clone(s)
}
// Compact replaces consecutive runs of equal elements with a single copy.
// This is like the uniq command found on Unix.
// Compact modifies the contents of the slice s; it does not create a new slice.
// Intended usage is to assign the result back to the input slice.
func Compact[S ~[]E, E comparable](s S) S {
return slices.Compact(s)
}
// CompactFunc is like Compact but uses a comparison function.
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
return slices.CompactFunc(s, eq)
}
// Grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After Grow(n), at least n elements can be appended
// to the slice without another allocation. Grow may modify elements of the
// slice between the length and the capacity. If n is negative or too large to
// allocate the memory, Grow panics.
func Grow[S ~[]E, E any](s S, n int) S {
return slices.Grow(s, n)
}
// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
func Clip[S ~[]E, E any](s S) S {
return slices.Clip(s)
}

View file

@ -0,0 +1,57 @@
package slices
import (
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
)
// This file delegates to the official slices package, so that we end up with a superset of the official API.
// Sort sorts a slice of any ordered type in ascending order.
func Sort[E constraints.Ordered](x []E) {
slices.Sort(x)
}
// Sort sorts the slice x in ascending order as determined by the less function.
// This sort is not guaranteed to be stable.
func SortFunc[E any](x []E, less func(a, b E) bool) {
slices.SortFunc(x, less)
}
// SortStable sorts the slice x while keeping the original order of equal
// elements, using less to compare elements.
func SortStableFunc[E any](x []E, less func(a, b E) bool) {
slices.SortStableFunc(x, less)
}
// IsSorted reports whether x is sorted in ascending order.
func IsSorted[E constraints.Ordered](x []E) bool {
return slices.IsSorted(x)
}
// IsSortedFunc reports whether x is sorted in ascending order, with less as the
// comparison function.
func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool {
return slices.IsSortedFunc(x, less)
}
// BinarySearch searches for target in a sorted slice and returns the smallest
// index at which target is found. If the target is not found, the index at
// which it could be inserted into the slice is returned; therefore, if the
// intention is to find target itself a separate check for equality with the
// element at the returned index is required.
func BinarySearch[E constraints.Ordered](x []E, target E) int {
return slices.BinarySearch(x, target)
}
// BinarySearchFunc uses binary search to find and return the smallest index i
// in [0, n) at which ok(i) is true, assuming that on the range [0, n),
// ok(i) == true implies ok(i+1) == true. That is, BinarySearchFunc requires
// that ok is false for some (possibly empty) prefix of the input range [0, n)
// and then true for the (possibly empty) remainder; BinarySearchFunc returns
// the first true index. If there is no such index, BinarySearchFunc returns n.
// (Note that the "not found" return value is not -1 as in, for instance,
// strings.Index.) Search calls ok(i) only for i in the range [0, n).
func BinarySearchFunc[E any](x []E, ok func(E) bool) int {
return slices.BinarySearchFunc(x, ok)
}

View file

@ -0,0 +1,154 @@
package slices
import (
"golang.org/x/exp/slices"
)
// This file contains the new functions that do not live in the official slices package.
func Some[T any](slice []T, test func(T) bool) bool {
for _, value := range slice {
if test(value) {
return true
}
}
return false
}
func Every[T any](slice []T, test func(T) bool) bool {
for _, value := range slice {
if !test(value) {
return false
}
}
return true
}
// Produces a new slice, leaves the input slice untouched.
func Map[T any, V any](slice []T, f func(T) V) []V {
result := make([]V, len(slice))
for i, value := range slice {
result[i] = f(value)
}
return result
}
func MapInPlace[T any](slice []T, f func(T) T) {
for i, value := range slice {
slice[i] = f(value)
}
}
// Produces a new slice, leaves the input slice untouched.
func Filter[T any](slice []T, test func(T) bool) []T {
result := make([]T, 0)
for _, element := range slice {
if test(element) {
result = append(result, element)
}
}
return result
}
// Mutates original slice. Intended usage is to reassign the slice result to the input slice.
func FilterInPlace[T any](slice []T, test func(T) bool) []T {
newLength := 0
for _, element := range slice {
if test(element) {
slice[newLength] = element
newLength++
}
}
return slice[:newLength]
}
// Produces a new slice, leaves the input slice untouched
func Reverse[T any](slice []T) []T {
result := make([]T, len(slice))
for i := range slice {
result[i] = slice[len(slice)-1-i]
}
return result
}
func ReverseInPlace[T any](slice []T) {
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i]
}
}
// Produces a new slice, leaves the input slice untouched.
func FilterMap[T any, E any](slice []T, test func(T) (bool, E)) []E {
result := make([]E, 0, len(slice))
for _, element := range slice {
ok, mapped := test(element)
if ok {
result = append(result, mapped)
}
}
return result
}
// Produces a new slice, leaves the input slice untouched.
func FilterThenMap[T any, E any](slice []T, test func(T) bool, mapFn func(T) E) []E {
result := make([]E, 0, len(slice))
for _, element := range slice {
if test(element) {
result = append(result, mapFn(element))
}
}
return result
}
// Prepends items to the beginning of a slice.
// E.g. Prepend([]int{1,2}, 3, 4) = []int{3,4,1,2}
// Mutates original slice. Intended usage is to reassign the slice result to the input slice.
func Prepend[T any](slice []T, values ...T) []T {
return append(values, slice...)
}
// Removes the element at the given index. Intended usage is to reassign the result to the input slice.
func Remove[T any](slice []T, index int) []T {
return slices.Delete(slice, index, index+1)
}
// Operates on the input slice. Expected use is to reassign the result to the input slice.
func Move[T any](slice []T, fromIndex int, toIndex int) []T {
item := slice[fromIndex]
slice = Remove(slice, fromIndex)
return slices.Insert(slice, toIndex, item)
}
// Similar to Append but we leave the original slice untouched and return a new slice
func Concat[T any](slice []T, values ...T) []T {
newSlice := make([]T, 0, len(slice)+len(values))
newSlice = append(newSlice, slice...)
newSlice = append(newSlice, values...)
return newSlice
}
func ContainsFunc[T any](slice []T, f func(T) bool) bool {
return IndexFunc(slice, f) != -1
}
// Pops item from the end of the slice and returns it, along with the updated slice
// Mutates original slice. Intended usage is to reassign the slice result to the input slice.
func Pop[T any](slice []T) (T, []T) {
index := len(slice) - 1
value := slice[index]
slice = slice[0:index]
return value, slice
}
// Shifts item from the beginning of the slice and returns it, along with the updated slice.
// Mutates original slice. Intended usage is to reassign the slice result to the input slice.
func Shift[T any](slice []T) (T, []T) {
value := slice[0]
slice = slice[1:]
return value, slice
}