You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
12 KiB
Go
479 lines
12 KiB
Go
package narr
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
)
|
|
|
|
// ErrElementNotFound is the error returned when the element is not found.
|
|
var ErrElementNotFound = errors.New("element not found")
|
|
|
|
// Comparer Function to compare two elements.
|
|
type Comparer func(a, b any) int
|
|
|
|
// Predicate Function to predicate a struct/value satisfies a condition.
|
|
type Predicate func(a any) bool
|
|
|
|
var (
|
|
// StringEqualsComparer Comparer for string. It will compare the string by their value.
|
|
// returns: 0 if equal, -1 if a != b
|
|
StringEqualsComparer Comparer = func(a, b any) int {
|
|
typeOfA := reflect.TypeOf(a)
|
|
if typeOfA.Kind() == reflect.Ptr {
|
|
typeOfA = typeOfA.Elem()
|
|
}
|
|
|
|
typeOfB := reflect.TypeOf(b)
|
|
if typeOfB.Kind() == reflect.Ptr {
|
|
typeOfB = typeOfB.Elem()
|
|
}
|
|
|
|
if typeOfA != typeOfB {
|
|
return -1
|
|
}
|
|
|
|
strA := ""
|
|
strB := ""
|
|
|
|
if val, ok := a.(string); ok {
|
|
strA = val
|
|
} else if val, ok := a.(*string); ok {
|
|
strA = *val
|
|
} else {
|
|
return -1
|
|
}
|
|
|
|
if val, ok := b.(string); ok {
|
|
strB = val
|
|
} else if val, ok := b.(*string); ok {
|
|
strB = *val
|
|
} else {
|
|
return -1
|
|
}
|
|
|
|
if strA == strB {
|
|
return 0
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// ReferenceEqualsComparer Comparer for strcut ptr. It will compare the struct by their ptr addr.
|
|
// returns: 0 if equal, -1 if a != b
|
|
ReferenceEqualsComparer Comparer = func(a, b any) int {
|
|
if a == b {
|
|
return 0
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// ElemTypeEqualsComparer Comparer for struct/value. It will compare the struct by their element type (reflect.Type.Elem()).
|
|
// returns: 0 if same type, -1 if not.
|
|
ElemTypeEqualsComparer Comparer = func(a, b any) int {
|
|
at := reflect.TypeOf(a)
|
|
bt := reflect.TypeOf(b)
|
|
if at.Kind() == reflect.Ptr {
|
|
at = at.Elem()
|
|
}
|
|
|
|
if bt.Kind() == reflect.Ptr {
|
|
bt = bt.Elem()
|
|
}
|
|
|
|
if at == bt {
|
|
return 0
|
|
}
|
|
return -1
|
|
}
|
|
)
|
|
|
|
// TwoWaySearch Find specialized element in a slice forward and backward in the same time, should be more quickly.
|
|
//
|
|
// data: the slice to search in. MUST BE A SLICE.
|
|
// item: the element to search.
|
|
// fn: the comparer function.
|
|
// return: the index of the element, or -1 if not found.
|
|
func TwoWaySearch(data any, item any, fn Comparer) (int, error) {
|
|
if data == nil {
|
|
return -1, errors.New("collections.TwowaySearch: data is nil")
|
|
}
|
|
if fn == nil {
|
|
return -1, errors.New("collections.TwowaySearch: fn is nil")
|
|
}
|
|
|
|
dataType := reflect.TypeOf(data)
|
|
if dataType.Kind() != reflect.Slice {
|
|
return -1, errors.New("collections.TwowaySearch: data is not a slice")
|
|
}
|
|
|
|
dataVal := reflect.ValueOf(data)
|
|
if dataVal.Len() == 0 {
|
|
return -1, errors.New("collections.TwowaySearch: data is empty")
|
|
}
|
|
itemType := dataType.Elem()
|
|
if itemType.Kind() == reflect.Ptr {
|
|
itemType = itemType.Elem()
|
|
}
|
|
|
|
if itemType != dataVal.Index(0).Type() {
|
|
return -1, errors.New("collections.TwowaySearch: item type is not the same as data type")
|
|
}
|
|
|
|
forward := 0
|
|
backward := dataVal.Len() - 1
|
|
|
|
for forward <= backward {
|
|
forwardVal := dataVal.Index(forward).Interface()
|
|
if fn(forwardVal, item) == 0 {
|
|
return forward, nil
|
|
}
|
|
|
|
backwardVal := dataVal.Index(backward).Interface()
|
|
if fn(backwardVal, item) == 0 {
|
|
return backward, nil
|
|
}
|
|
|
|
forward++
|
|
backward--
|
|
}
|
|
|
|
return -1, ErrElementNotFound
|
|
}
|
|
|
|
// MakeEmptySlice Create a new slice with the elements of the source that satisfy the predicate.
|
|
//
|
|
// itemType: the type of the elements in the source.
|
|
// returns: the new slice.
|
|
func MakeEmptySlice(itemType reflect.Type) any {
|
|
ret := reflect.MakeSlice(reflect.SliceOf(itemType), 0, 0).Interface()
|
|
return ret
|
|
}
|
|
|
|
// CloneSlice Clone a slice.
|
|
//
|
|
// data: the slice to clone.
|
|
// returns: the cloned slice.
|
|
func CloneSlice(data any) any {
|
|
typeOfData := reflect.TypeOf(data)
|
|
if typeOfData.Kind() != reflect.Slice {
|
|
panic("collections.CloneSlice: data must be a slice")
|
|
}
|
|
return reflect.AppendSlice(reflect.New(reflect.SliceOf(typeOfData.Elem())).Elem(), reflect.ValueOf(data)).Interface()
|
|
}
|
|
|
|
// Differences Produces the set difference of two slice according to a comparer function.
|
|
//
|
|
// first: the first slice. MUST BE A SLICE.
|
|
// second: the second slice. MUST BE A SLICE.
|
|
// fn: the comparer function.
|
|
// returns: the difference of the two slices.
|
|
func Differences[T any](first, second []T, fn Comparer) []T {
|
|
typeOfFirst := reflect.TypeOf(first)
|
|
if typeOfFirst.Kind() != reflect.Slice {
|
|
panic("collections.Excepts: first must be a slice")
|
|
}
|
|
|
|
typeOfSecond := reflect.TypeOf(second)
|
|
if typeOfSecond.Kind() != reflect.Slice {
|
|
panic("collections.Excepts: second must be a slice")
|
|
}
|
|
|
|
firstLen := len(first)
|
|
if firstLen == 0 {
|
|
return CloneSlice(second).([]T)
|
|
}
|
|
|
|
secondLen := len(second)
|
|
if secondLen == 0 {
|
|
return CloneSlice(first).([]T)
|
|
}
|
|
|
|
max := firstLen
|
|
if secondLen > firstLen {
|
|
max = secondLen
|
|
}
|
|
|
|
result := make([]T, 0)
|
|
for i := 0; i < max; i++ {
|
|
if i < firstLen {
|
|
s := first[i]
|
|
if i, _ := TwoWaySearch(second, s, fn); i < 0 {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
|
|
if i < secondLen {
|
|
t := second[i]
|
|
if i, _ := TwoWaySearch(first, t, fn); i < 0 {
|
|
result = append(result, t)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Excepts Produces the set difference of two slice according to a comparer function.
|
|
//
|
|
// first: the first slice. MUST BE A SLICE.
|
|
// second: the second slice. MUST BE A SLICE.
|
|
// fn: the comparer function.
|
|
// returns: the difference of the two slices.
|
|
func Excepts(first, second any, fn Comparer) any {
|
|
typeOfFirst := reflect.TypeOf(first)
|
|
if typeOfFirst.Kind() != reflect.Slice {
|
|
panic("collections.Excepts: first must be a slice")
|
|
}
|
|
valOfFirst := reflect.ValueOf(first)
|
|
if valOfFirst.Len() == 0 {
|
|
return MakeEmptySlice(typeOfFirst.Elem())
|
|
}
|
|
|
|
typeOfSecond := reflect.TypeOf(second)
|
|
if typeOfSecond.Kind() != reflect.Slice {
|
|
panic("collections.Excepts: second must be a slice")
|
|
}
|
|
|
|
valOfSecond := reflect.ValueOf(second)
|
|
if valOfSecond.Len() == 0 {
|
|
return CloneSlice(first)
|
|
}
|
|
|
|
result := reflect.New(reflect.SliceOf(typeOfFirst.Elem())).Elem()
|
|
for i := 0; i < valOfFirst.Len(); i++ {
|
|
s := valOfFirst.Index(i).Interface()
|
|
if i, _ := TwoWaySearch(second, s, fn); i < 0 {
|
|
result = reflect.Append(result, reflect.ValueOf(s))
|
|
}
|
|
}
|
|
|
|
return result.Interface()
|
|
}
|
|
|
|
// Intersects Produces to intersect of two slice according to a comparer function.
|
|
//
|
|
// first: the first slice. MUST BE A SLICE.
|
|
// second: the second slice. MUST BE A SLICE.
|
|
// fn: the comparer function.
|
|
// returns: to intersect of the two slices.
|
|
func Intersects(first any, second any, fn Comparer) any {
|
|
typeOfFirst := reflect.TypeOf(first)
|
|
if typeOfFirst.Kind() != reflect.Slice {
|
|
panic("collections.Intersects: first must be a slice")
|
|
}
|
|
valOfFirst := reflect.ValueOf(first)
|
|
if valOfFirst.Len() == 0 {
|
|
return MakeEmptySlice(typeOfFirst.Elem())
|
|
}
|
|
|
|
typeOfSecond := reflect.TypeOf(second)
|
|
if typeOfSecond.Kind() != reflect.Slice {
|
|
panic("collections.Intersects: second must be a slice")
|
|
}
|
|
|
|
valOfSecond := reflect.ValueOf(second)
|
|
if valOfSecond.Len() == 0 {
|
|
return MakeEmptySlice(typeOfFirst.Elem())
|
|
}
|
|
|
|
result := reflect.New(reflect.SliceOf(typeOfFirst.Elem())).Elem()
|
|
for i := 0; i < valOfFirst.Len(); i++ {
|
|
s := valOfFirst.Index(i).Interface()
|
|
if i, _ := TwoWaySearch(second, s, fn); i >= 0 {
|
|
result = reflect.Append(result, reflect.ValueOf(s))
|
|
}
|
|
}
|
|
|
|
return result.Interface()
|
|
}
|
|
|
|
// Union Produces the set union of two slice according to a comparer function
|
|
//
|
|
// first: the first slice. MUST BE A SLICE.
|
|
// second: the second slice. MUST BE A SLICE.
|
|
// fn: the comparer function.
|
|
// returns: the union of the two slices.
|
|
func Union(first, second any, fn Comparer) any {
|
|
excepts := Excepts(second, first, fn)
|
|
|
|
typeOfFirst := reflect.TypeOf(first)
|
|
if typeOfFirst.Kind() != reflect.Slice {
|
|
panic("collections.Intersects: first must be a slice")
|
|
}
|
|
valOfFirst := reflect.ValueOf(first)
|
|
if valOfFirst.Len() == 0 {
|
|
return CloneSlice(second)
|
|
}
|
|
|
|
result := reflect.AppendSlice(reflect.New(reflect.SliceOf(typeOfFirst.Elem())).Elem(), valOfFirst)
|
|
result = reflect.AppendSlice(result, reflect.ValueOf(excepts))
|
|
return result.Interface()
|
|
}
|
|
|
|
// Find Produces the struct/value of a slice according to a predicate function.
|
|
//
|
|
// source: the slice. MUST BE A SLICE.
|
|
// fn: the predicate function.
|
|
// returns: the struct/value of the slice.
|
|
func Find(source any, fn Predicate) (any, error) {
|
|
aType := reflect.TypeOf(source)
|
|
if aType.Kind() != reflect.Slice {
|
|
panic("collections.Find: source must be a slice")
|
|
}
|
|
|
|
sourceVal := reflect.ValueOf(source)
|
|
if sourceVal.Len() == 0 {
|
|
return nil, ErrElementNotFound
|
|
}
|
|
|
|
for i := 0; i < sourceVal.Len(); i++ {
|
|
s := sourceVal.Index(i).Interface()
|
|
if fn(s) {
|
|
return s, nil
|
|
}
|
|
}
|
|
return nil, ErrElementNotFound
|
|
}
|
|
|
|
// FindOrDefault Produce the struct/value f a slice to a predicate function,
|
|
// Produce default value when predicate function not found.
|
|
//
|
|
// source: the slice. MUST BE A SLICE.
|
|
// fn: the predicate function.
|
|
// defaultValue: the default value.
|
|
// returns: the struct/value of the slice.
|
|
func FindOrDefault(source any, fn Predicate, defaultValue any) any {
|
|
item, err := Find(source, fn)
|
|
if err != nil {
|
|
if errors.Is(err, ErrElementNotFound) {
|
|
return defaultValue
|
|
}
|
|
}
|
|
return item
|
|
}
|
|
|
|
// TakeWhile Produce the set of a slice according to a predicate function,
|
|
// Produce empty slice when predicate function not matched.
|
|
//
|
|
// data: the slice. MUST BE A SLICE.
|
|
// fn: the predicate function.
|
|
// returns: the set of the slice.
|
|
func TakeWhile(data any, fn Predicate) any {
|
|
aType := reflect.TypeOf(data)
|
|
if aType.Kind() != reflect.Slice {
|
|
panic("collections.TakeWhile: data must be a slice")
|
|
}
|
|
|
|
sourceVal := reflect.ValueOf(data)
|
|
if sourceVal.Len() == 0 {
|
|
return MakeEmptySlice(aType.Elem())
|
|
}
|
|
|
|
result := reflect.New(reflect.SliceOf(aType.Elem())).Elem()
|
|
for i := 0; i < sourceVal.Len(); i++ {
|
|
s := sourceVal.Index(i).Interface()
|
|
if fn(s) {
|
|
result = reflect.Append(result, reflect.ValueOf(s))
|
|
}
|
|
}
|
|
return result.Interface()
|
|
}
|
|
|
|
// ExceptWhile Produce the set of a slice except with a predicate function,
|
|
// Produce original slice when predicate function not match.
|
|
//
|
|
// data: the slice. MUST BE A SLICE.
|
|
// fn: the predicate function.
|
|
// returns: the set of the slice.
|
|
func ExceptWhile(data any, fn Predicate) any {
|
|
aType := reflect.TypeOf(data)
|
|
if aType.Kind() != reflect.Slice {
|
|
panic("collections.ExceptWhile: data must be a slice")
|
|
}
|
|
|
|
sourceVal := reflect.ValueOf(data)
|
|
if sourceVal.Len() == 0 {
|
|
return MakeEmptySlice(aType.Elem())
|
|
}
|
|
|
|
result := reflect.New(reflect.SliceOf(aType.Elem())).Elem()
|
|
for i := 0; i < sourceVal.Len(); i++ {
|
|
s := sourceVal.Index(i).Interface()
|
|
if !fn(s) {
|
|
result = reflect.Append(result, reflect.ValueOf(s))
|
|
}
|
|
}
|
|
return result.Interface()
|
|
}
|
|
|
|
// type MapFn func(obj T) (target V, find bool)
|
|
|
|
// Map a list to new list
|
|
//
|
|
// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
|
|
func Map[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V {
|
|
flatArr := make([]V, 0, len(list))
|
|
|
|
for _, obj := range list {
|
|
if target, ok := mapFn(obj); ok {
|
|
flatArr = append(flatArr, target)
|
|
}
|
|
}
|
|
return flatArr
|
|
}
|
|
|
|
// Column alias of Map func
|
|
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V {
|
|
return Map(list, mapFn)
|
|
}
|
|
|
|
// Every check all true in given slice
|
|
func Every[T any](list []T, fn func(T) bool) bool {
|
|
if len(list) == 0 {
|
|
return false
|
|
}
|
|
for _, t := range list {
|
|
if !fn(t) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Some check some true in given slice
|
|
func Some[T any](list []T, fn func(T) bool) bool {
|
|
if len(list) == 0 {
|
|
return false
|
|
}
|
|
for _, t := range list {
|
|
if fn(t) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Filter returns a new slice by given filter func
|
|
func Filter[T any](list []T, filterFn func(i int, currentValue T) bool) []T {
|
|
ret := make([]T, 0)
|
|
if list == nil {
|
|
return ret
|
|
}
|
|
|
|
for i, v := range list {
|
|
if filterFn(i, v) {
|
|
ret = append(ret, v)
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// Reduce all list item by given func
|
|
func Reduce[T any, M any](list []T, reduceFn func(total M, currentVal T, idx int) M, initialValue M) M {
|
|
ret := initialValue
|
|
for i, item := range list {
|
|
ret = reduceFn(ret, item, i)
|
|
}
|
|
return ret
|
|
}
|