GoDoxy/internal/utils/functional/map.go
2025-01-01 06:07:32 +08:00

190 lines
3.9 KiB
Go

package functional
import (
"sync"
"github.com/puzpuzpuz/xsync/v3"
"gopkg.in/yaml.v3"
)
type Map[KT comparable, VT any] struct {
*xsync.MapOf[KT, VT]
}
const minParallelSize = 4
func NewMapOf[KT comparable, VT any](options ...func(*xsync.MapConfig)) Map[KT, VT] {
return Map[KT, VT]{xsync.NewMapOf[KT, VT](options...)}
}
func NewMapFrom[KT comparable, VT any](m map[KT]VT) (res Map[KT, VT]) {
res = NewMapOf[KT, VT](xsync.WithPresize(len(m)))
for k, v := range m {
res.Store(k, v)
}
return
}
func NewMap[MapType Map[KT, VT], KT comparable, VT any]() Map[KT, VT] {
return NewMapOf[KT, VT]()
}
// MapFind iterates over the map and returns the first value
// that satisfies the given criteria. The iteration is stopped
// once a value is found. If no value satisfies the criteria,
// the function returns the zero value of CT.
//
// The criteria function takes a value of type VT and returns a
// value of type CT and a boolean indicating whether the value
// satisfies the criteria. The boolean value is used to determine
// whether the iteration should be stopped.
//
// The function is safe for concurrent use.
func MapFind[KT comparable, VT, CT any](m Map[KT, VT], criteria func(VT) (CT, bool)) (_ CT) {
result := make(chan CT, 1)
m.Range(func(key KT, value VT) bool {
select {
case <-result: // already have a result
return false // stop iteration
default:
if got, ok := criteria(value); ok {
result <- got
return false
}
return true
}
})
select {
case v := <-result:
return v
default:
return
}
}
// MergeFrom merges the contents of another Map into this one, ignoring duplicated keys.
//
// Parameters:
//
// other: Map of values to add from
//
// Returns:
//
// Map of duplicated keys-value pairs
func (m Map[KT, VT]) MergeFrom(other Map[KT, VT]) Map[KT, VT] {
dups := NewMapOf[KT, VT]()
other.Range(func(k KT, v VT) bool {
if _, ok := m.Load(k); ok {
dups.Store(k, v)
} else {
m.Store(k, v)
}
return true
})
return dups
}
// RangeAll calls the given function for each key-value pair in the map.
//
// Parameters:
//
// do: function to call for each key-value pair
//
// Returns:
//
// nothing
func (m Map[KT, VT]) RangeAll(do func(k KT, v VT)) {
m.Range(func(k KT, v VT) bool {
do(k, v)
return true
})
}
// RangeAllParallel calls the given function for each key-value pair in the map,
// in parallel. The map is not safe for modification from within the function.
//
// Parameters:
//
// do: function to call for each key-value pair
//
// Returns:
//
// nothing
func (m Map[KT, VT]) RangeAllParallel(do func(k KT, v VT)) {
if m.Size() < minParallelSize {
m.RangeAll(do)
return
}
var wg sync.WaitGroup
m.Range(func(k KT, v VT) bool {
wg.Add(1)
go func() {
do(k, v)
wg.Done()
}()
return true
})
wg.Wait()
}
// CollectErrors calls the given function for each key-value pair in the map,
// then returns a slice of errors collected.
func (m Map[KT, VT]) CollectErrors(do func(k KT, v VT) error) []error {
errs := make([]error, 0)
m.Range(func(k KT, v VT) bool {
if err := do(k, v); err != nil {
errs = append(errs, err)
}
return true
})
return errs
}
// CollectErrors calls the given function for each key-value pair in the map,
// then returns a slice of errors collected.
func (m Map[KT, VT]) CollectErrorsParallel(do func(k KT, v VT) error) []error {
if m.Size() < minParallelSize {
return m.CollectErrors(do)
}
var errs []error
var mu sync.Mutex
var wg sync.WaitGroup
m.Range(func(k KT, v VT) bool {
wg.Add(1)
go func() {
if err := do(k, v); err != nil {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
}
wg.Done()
}()
return true
})
wg.Wait()
return errs
}
func (m Map[KT, VT]) Has(k KT) bool {
_, ok := m.Load(k)
return ok
}
func (m Map[KT, VT]) String() string {
tmp := make(map[KT]VT, m.Size())
m.RangeAll(func(k KT, v VT) {
tmp[k] = v
})
data, err := yaml.Marshal(tmp)
if err != nil {
return err.Error()
}
return string(data)
}