From d473776e86677244f957338723ba5bfe2381e600 Mon Sep 17 00:00:00 2001 From: "kevin.zhao" Date: Thu, 18 Sep 2025 12:13:45 +0800 Subject: [PATCH 1/2] implement some xiter/xmap utils --- pkg/union/union.go | 36 +++--- pkg/xiter/xiter.go | 7 +- pkg/xiter/xiter_old.go | 4 +- pkg/xmap/xmap_common.go | 226 +++++++++++++++++++++++++++++++++-- pkg/xmap/xmap_test.go | 240 ++++++++++++++++++++++++++++++++++++++ pkg/xslice/xslice.go | 73 ++++++++++++ pkg/xslice/xslice_test.go | 124 ++++++++++++++++++++ 7 files changed, 678 insertions(+), 32 deletions(-) diff --git a/pkg/union/union.go b/pkg/union/union.go index f590f6d..735ae66 100644 --- a/pkg/union/union.go +++ b/pkg/union/union.go @@ -69,28 +69,28 @@ type U9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct { } type U10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct { - T1 T1 - T2 T2 - T3 T3 - T4 T4 - T5 T5 - T6 T6 - T7 T7 - T8 T8 - T9 T9 + T1 T1 + T2 T2 + T3 T3 + T4 T4 + T5 T5 + T6 T6 + T7 T7 + T8 T8 + T9 T9 T10 T10 } type U11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct { - T1 T1 - T2 T2 - T3 T3 - T4 T4 - T5 T5 - T6 T6 - T7 T7 - T8 T8 - T9 T9 + T1 T1 + T2 T2 + T3 T3 + T4 T4 + T5 T5 + T6 T6 + T7 T7 + T8 T8 + T9 T9 T10 T10 T11 T11 } diff --git a/pkg/xiter/xiter.go b/pkg/xiter/xiter.go index 8ec7f73..f3b2708 100644 --- a/pkg/xiter/xiter.go +++ b/pkg/xiter/xiter.go @@ -9,10 +9,9 @@ import ( "math/rand" "strings" - "github.com/dashjay/xiter/pkg/internal/utils" - "github.com/dashjay/xiter/pkg/cmp" "github.com/dashjay/xiter/pkg/internal/constraints" + "github.com/dashjay/xiter/pkg/internal/utils" "github.com/dashjay/xiter/pkg/optional" "github.com/dashjay/xiter/pkg/union" ) @@ -746,7 +745,7 @@ func Max[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) { } // MaxBy return the maximum element in seq, evaluated by f. -func MaxBy[T constraints.Ordered](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { +func MaxBy[T any](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { first := true var _max T for v := range seq { @@ -782,7 +781,7 @@ func Min[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) { } // MinBy return the minimum element in seq, evaluated by f. -func MinBy[T constraints.Ordered](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { +func MinBy[T any](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { first := true var _min T for v := range seq { diff --git a/pkg/xiter/xiter_old.go b/pkg/xiter/xiter_old.go index 1f5a114..caa79c9 100644 --- a/pkg/xiter/xiter_old.go +++ b/pkg/xiter/xiter_old.go @@ -722,7 +722,7 @@ func Max[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) { return optional.FromValue(_max) } -func MaxBy[T constraints.Ordered](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { +func MaxBy[T any](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { first := true var _max T seq(func(v T) bool { @@ -758,7 +758,7 @@ func Min[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) { return optional.FromValue(_min) } -func MinBy[T constraints.Ordered](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { +func MinBy[T any](seq Seq[T], less func(T, T) bool) (r optional.O[T]) { first := true var _min T seq(func(v T) bool { diff --git a/pkg/xmap/xmap_common.go b/pkg/xmap/xmap_common.go index 5519407..e920d8a 100644 --- a/pkg/xmap/xmap_common.go +++ b/pkg/xmap/xmap_common.go @@ -1,6 +1,7 @@ package xmap import ( + "github.com/dashjay/xiter/pkg/internal/constraints" "github.com/dashjay/xiter/pkg/optional" "github.com/dashjay/xiter/pkg/union" "github.com/dashjay/xiter/pkg/xiter" @@ -117,6 +118,18 @@ func ToXSyncMap[K comparable, V any](in map[K]V) *xsync.SyncMap[K, V] { return m } +// FindKey finds a key in the map and returns the key, its value, and whether it was found. +// This is essentially a wrapper around the built-in map lookup that returns the key along with the value. +// If the key exists, it returns the key, its value, and true. +// If the key doesn't exist, it returns the zero key, zero value, and false. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2} +// key, value, found := xmap.FindKey(m, "b") +// // key="b", value=2, found=true +// key, value, found = xmap.FindKey(m, "c") +// // key="", value=0, found=false func FindKey[K comparable, V any](in map[K]V, target K) (K, V, bool) { v, ok := in[target] if ok { @@ -126,6 +139,19 @@ func FindKey[K comparable, V any](in map[K]V, target K) (K, V, bool) { return zero, v, ok } +// FindKeyO finds a key in the map and returns the key-value pair as an optional.O[union.U2[K, V]]. +// If the key exists, it returns an optional containing a union.U2 with the key in T1 and value in T2. +// If the key doesn't exist, it returns an empty optional. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2} +// result := xmap.FindKeyO(m, "b") +// // result.Ok() == true, result.Must() contains union.U2[string, int]{T1: "b", T2: 2} +// key := result.Must().T1 // "b" +// value := result.Must().T2 // 2 +// result2 := xmap.FindKeyO(m, "c") +// // result2.Ok() == false func FindKeyO[K comparable, V any](in map[K]V, target K) optional.O[union.U2[K, V]] { v, ok := in[target] if ok { @@ -134,17 +160,39 @@ func FindKeyO[K comparable, V any](in map[K]V, target K) optional.O[union.U2[K, return optional.Empty[union.U2[K, V]]() } +// Find searches for the first key-value pair in the map that satisfies the given condition function. +// It returns the key, value, and whether such a pair was found. +// The search order is not guaranteed to be consistent due to the nature of Go's map iteration. +// If no pair satisfies the condition, it returns the zero key, zero value, and false. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2, "c": 3} +// key, value, found := xmap.Find(m, func(k string, v int) bool { return v > 1 }) +// // key could be "b" or "c", value=2 or 3, found=true (first match found) +// key, value, found = xmap.Find(m, func(k string, v int) bool { return v > 10 }) +// // key="", value=0, found=false func Find[K comparable, V any](in map[K]V, fn func(K, V) bool) (K, V, bool) { - for k, v := range in { - if fn(k, v) { - return k, v, true - } - } - var k K - var v V - return k, v, false + o := FindO(in, fn) + z := o.ValueOrZero() + return z.T1, z.T2, o.Ok() } +// FindO searches for the first key-value pair in the map that satisfies the given condition function. +// It returns the key-value pair as an optional.O[union.U2[K, V]]. +// The search order is not guaranteed to be consistent due to the nature of Go's map iteration. +// If a pair satisfies the condition, it returns an optional containing a union.U2 with the key in T1 and value in T2. +// If no pair satisfies the condition, it returns an empty optional. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2, "c": 3} +// result := xmap.FindO(m, func(k string, v int) bool { return v > 1 }) +// // result.Ok() == true, result.Must() contains union.U2[string, int] with key and value of first match +// key := result.Must().T1 // could be "b" or "c" +// value := result.Must().T2 // could be 2 or 3 +// result2 := xmap.FindO(m, func(k string, v int) bool { return v > 10 }) +// // result2.Ok() == false func FindO[K comparable, V any](in map[K]V, fn func(K, V) bool) optional.O[union.U2[K, V]] { for k, v := range in { if fn(k, v) { @@ -153,3 +201,165 @@ func FindO[K comparable, V any](in map[K]V, fn func(K, V) bool) optional.O[union } return optional.Empty[union.U2[K, V]]() } + +// Update updates the value for a key in the map and returns the old value and whether it was replaced. +// If the key exists, it updates the value and returns the old value with replaced=true. +// If the key doesn't exist, it adds the key-value pair and returns the zero value with replaced=false. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2} +// old, replaced := xmap.Update(m, "b", 20) +// // old=2, replaced=true, m is now {"a": 1, "b": 20} +// old, replaced = xmap.Update(m, "c", 3) +// // old=0, replaced=false, m is now {"a": 1, "b": 20, "c": 3} +func Update[K comparable, V any](m map[K]V, k K, v V) (old V, replaced bool) { + o := FindKeyO(m, k) + m[k] = v + return o.ValueOrZero().T2, o.Ok() +} + +// UpdateIf updates the value for a key in the map only if the condition function returns true. +// It returns the old value and whether it was replaced. +// If the key exists and the condition is true, it updates the value and returns the old value with replaced=true. +// Otherwise, it returns the zero value with replaced=false. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2} +// old, replaced := xmap.UpdateIf(m, "b", 20, func(k string, v int) bool { return v > 1 }) +// // old=2, replaced=true, m is now {"a": 1, "b": 20} +// old, replaced = xmap.UpdateIf(m, "b", 200, func(k string, v int) bool { return v > 50 }) +// // old=0, replaced=false, m remains {"a": 1, "b": 20} +// old, replaced = xmap.UpdateIf(m, "c", 3, func(k string, v int) bool { return v > 0 }) +// // old=0, replaced=false (key doesn't exist), m remains {"a": 1, "b": 20} +func UpdateIf[K comparable, V any](m map[K]V, k K, v V, c func(K, V) bool) (old V, replaced bool) { + o := FindKeyO(m, k) + if o.Ok() && c(o.Must().T1, o.Must().T2) { + m[k] = v + return o.Must().T2, true + } + return +} + +// Delete removes a key from the map and returns the old value and whether it was deleted. +// If the key exists, it removes the key-value pair and returns the old value with deleted=true. +// If the key doesn't exist, it returns the zero value with deleted=false. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2} +// old, deleted := xmap.Delete(m, "b") +// // old=2, deleted=true, m is now {"a": 1} +// old, deleted = xmap.Delete(m, "c") +// // old=0, deleted=false, m remains {"a": 1} +func Delete[K comparable, V any](m map[K]V, k K) (old V, deleted bool) { + o := FindKeyO(m, k) + delete(m, k) + return o.ValueOrZero().T2, o.Ok() +} + +// DeleteIf removes a key from the map only if the condition function returns true. +// It returns the old value and whether it was deleted. +// If the key exists and the condition is true, it removes the key-value pair and returns the old value with deleted=true. +// Otherwise, it returns the zero value with deleted=false. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 2} +// old, deleted := xmap.DeleteIf(m, "b", func(k string, v int) bool { return v > 1 }) +// // old=2, deleted=true, m is now {"a": 1} +// old, deleted = xmap.DeleteIf(m, "b", func(k string, v int) bool { return v > 10 }) +// // old=0, deleted=false, m remains {"a": 1} +// old, deleted = xmap.DeleteIf(m, "c", func(k string, v int) bool { return v > 0 }) +// // old=0, deleted=false (key doesn't exist), m remains {"a": 1} +func DeleteIf[K comparable, V any](m map[K]V, k K, c func(K, V) bool) (old V, deleted bool) { + o := FindKeyO(m, k) + if o.Ok() && c(o.Must().T1, o.Must().T2) { + delete(m, k) + return o.Must().T2, true + } + return +} + +// MaxKey returns the maximum key in the map and its associated value as an optional.O[union.U2[K, V]]. +// If the map is empty, it returns an optional.O[union.U2[K, V]] with Ok() == false. +// The returned union.U2 contains the key in T1 and the value in T2. +// +// EXAMPLE: +// +// m := map[string]int{"b": 2, "a": 1, "c": 3} +// result := xmap.MaxKey(m) +// // result contains union.U2[string, int]{T1: "c", T2: 3} +// maxKey := result.Must().T1 // "c" +// maxValue := result.Must().T2 // 3 +// result2 := xmap.MaxKey(map[string]int{}) +// // result2.Ok() == false +func MaxKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]] { + o := xiter.Max(xiter.FromMapKeys(m)) + if o.Ok() { + return optional.FromValue(union.U2[K, V]{T1: o.Must(), T2: m[o.Must()]}) + } + return optional.Empty[union.U2[K, V]]() +} + +// MinKey returns the minimum key in the map and its associated value as an optional.O[union.U2[K, V]]. +// If the map is empty, it returns an optional.O[union.U2[K, V]] with Ok() == false. +// The returned union.U2 contains the key in T1 and the value in T2. +// +// EXAMPLE: +// +// m := map[string]int{"b": 2, "a": 1, "c": 3} +// result := xmap.MinKey(m) +// // result contains union.U2[string, int]{T1: "a", T2: 1} +// minKey := result.Must().T1 // "a" +// minValue := result.Must().T2 // 1 +// result2 := xmap.MinKey(map[string]int{}) +// // result2.Ok() == false +func MinKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]] { + o := xiter.Min(xiter.FromMapKeys(m)) + if o.Ok() { + return optional.FromValue(union.U2[K, V]{T1: o.Must(), T2: m[o.Must()]}) + } + return optional.Empty[union.U2[K, V]]() +} + +// MaxValue returns the maximum value in the map and its associated key as an optional.O[union.U2[K, V]]. +// If the map is empty, it returns an optional.O[union.U2[K, V]] with Ok() == false. +// The returned union.U2 contains the key in T1 and the maximum value in T2. +// +// EXAMPLE: +// +// m := map[string]int{"a": 1, "b": 3, "c": 2} +// result := xmap.MaxValue(m) +// // result contains union.U2[string, int]{T1: "b", T2: 3} +// maxKey := result.Must().T1 // "b" +// maxValue := result.Must().T2 // 3 +// result2 := xmap.MaxValue(map[string]int{}) +// // result2.Ok() == false +func MaxValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]] { + o := xiter.MaxBy(xiter.Seq2ToSeqUnion(xiter.FromMapKeyAndValues(m)), func(a union.U2[K, V], b union.U2[K, V]) bool { + return a.T2 < b.T2 + }) + return o +} + +// MinValue returns the minimum value in the map and its associated key as an optional.O[union.U2[K, V]]. +// If the map is empty, it returns an optional.O[union.U2[K, V]] with Ok() == false. +// The returned union.U2 contains the key in T1 and the minimum value in T2. +// +// EXAMPLE: +// +// m := map[string]int{"a": 3, "b": 1, "c": 2} +// result := xmap.MinValue(m) +// // result contains union.U2[string, int]{T1: "b", T2: 1} +// minKey := result.Must().T1 // "b" +// minValue := result.Must().T2 // 1 +// result2 := xmap.MinValue(map[string]int{}) +// // result2.Ok() == false +func MinValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]] { + o := xiter.MinBy(xiter.Seq2ToSeqUnion(xiter.FromMapKeyAndValues(m)), func(a union.U2[K, V], b union.U2[K, V]) bool { + return a.T2 < b.T2 + }) + return o +} diff --git a/pkg/xmap/xmap_test.go b/pkg/xmap/xmap_test.go index 1c43a47..de8bd57 100644 --- a/pkg/xmap/xmap_test.go +++ b/pkg/xmap/xmap_test.go @@ -178,6 +178,246 @@ func TestMap(t *testing.T) { res = xmap.FindKeyO(_map(0, 100), 100) assert.False(t, res.Ok()) + }) + + t.Run("update", func(t *testing.T) { + // Test updating existing key + m := map[string]int{"a": 1, "b": 2, "c": 3} + old, replaced := xmap.Update(m, "b", 20) + assert.Equal(t, 2, old) + assert.True(t, replaced) + assert.Equal(t, 20, m["b"]) + assert.Equal(t, 3, len(m)) // Map size should not change + + // Test updating non-existing key + old, replaced = xmap.Update(m, "d", 4) + assert.Equal(t, 0, old) + assert.False(t, replaced) + assert.Equal(t, 4, m["d"]) + assert.Equal(t, 4, len(m)) // Map size should increase + + // Test updating empty map + emptyMap := map[string]int{} + old, replaced = xmap.Update(emptyMap, "key", 100) + assert.Equal(t, 0, old) + assert.False(t, replaced) + assert.Equal(t, 100, emptyMap["key"]) + assert.Equal(t, 1, len(emptyMap)) + }) + + t.Run("update if", func(t *testing.T) { + // Test condition is true - should update + m := map[string]int{"a": 1, "b": 2, "c": 3} + old, replaced := xmap.UpdateIf(m, "b", 20, func(k string, v int) bool { return v > 1 }) + assert.Equal(t, 2, old) + assert.True(t, replaced) + assert.Equal(t, 20, m["b"]) + + // Test condition is false - should not update + old, replaced = xmap.UpdateIf(m, "b", 200, func(k string, v int) bool { return v > 50 }) + assert.Equal(t, 0, old) + assert.False(t, replaced) + assert.Equal(t, 20, m["b"]) // Value should remain unchanged + + // Test key doesn't exist - should not update + old, replaced = xmap.UpdateIf(m, "d", 4, func(k string, v int) bool { return v > 0 }) + assert.Equal(t, 0, old) + assert.False(t, replaced) + _, exists := m["d"] + assert.False(t, exists) // Key should not be added + + // Test complex condition + old, replaced = xmap.UpdateIf(m, "a", 10, func(k string, v int) bool { return k == "a" && v < 5 }) + assert.Equal(t, 1, old) + assert.True(t, replaced) + assert.Equal(t, 10, m["a"]) + }) + t.Run("delete", func(t *testing.T) { + // Test deleting existing key + m := map[string]int{"a": 1, "b": 2, "c": 3} + old, deleted := xmap.Delete(m, "b") + assert.Equal(t, 2, old) + assert.True(t, deleted) + _, exists := m["b"] + assert.False(t, exists) + assert.Equal(t, 2, len(m)) + + // Test deleting non-existing key + old, deleted = xmap.Delete(m, "d") + assert.Equal(t, 0, old) + assert.False(t, deleted) + assert.Equal(t, 2, len(m)) + + // Test deleting from empty map + emptyMap := map[string]int{} + old, deleted = xmap.Delete(emptyMap, "key") + assert.Equal(t, 0, old) + assert.False(t, deleted) + assert.Equal(t, 0, len(emptyMap)) + + // Test deleting all elements + m2 := map[string]int{"x": 10, "y": 20} + xmap.Delete(m2, "x") + xmap.Delete(m2, "y") + assert.Equal(t, 0, len(m2)) }) + + t.Run("delete if", func(t *testing.T) { + // Test condition is true - should delete + m := map[string]int{"a": 1, "b": 2, "c": 3} + old, deleted := xmap.DeleteIf(m, "b", func(k string, v int) bool { return v > 1 }) + assert.Equal(t, 2, old) + assert.True(t, deleted) + _, exists := m["b"] + assert.False(t, exists) + assert.Equal(t, 2, len(m)) + + // Test condition is false - should not delete + old, deleted = xmap.DeleteIf(m, "a", func(k string, v int) bool { return v > 10 }) + assert.Equal(t, 0, old) + assert.False(t, deleted) + assert.Equal(t, 1, m["a"]) // Value should remain unchanged + assert.Equal(t, 2, len(m)) + + // Test key doesn't exist - should not delete + old, deleted = xmap.DeleteIf(m, "d", func(k string, v int) bool { return v > 0 }) + assert.Equal(t, 0, old) + assert.False(t, deleted) + assert.Equal(t, 2, len(m)) + + // Test complex condition + m2 := map[string]int{"x": 10, "y": 5, "z": 15} + old, deleted = xmap.DeleteIf(m2, "y", func(k string, v int) bool { return k == "y" && v < 10 }) + assert.Equal(t, 5, old) + assert.True(t, deleted) + assert.Equal(t, 2, len(m2)) + }) + + t.Run("max key", func(t *testing.T) { + // Test with string keys + m := map[string]int{"b": 2, "a": 1, "c": 3} + result := xmap.MaxKey(m) + assert.True(t, result.Ok()) + assert.Equal(t, "c", result.Must().T1) + assert.Equal(t, 3, result.Must().T2) + + // Test with int keys + m2 := map[int]string{3: "three", 1: "one", 5: "five"} + result2 := xmap.MaxKey(m2) + assert.True(t, result2.Ok()) + assert.Equal(t, 5, result2.Must().T1) + assert.Equal(t, "five", result2.Must().T2) + + // Test empty map + emptyMap := map[string]int{} + result3 := xmap.MaxKey(emptyMap) + assert.False(t, result3.Ok()) + + // Test single element + singleMap := map[string]int{"only": 42} + result4 := xmap.MaxKey(singleMap) + assert.True(t, result4.Ok()) + assert.Equal(t, "only", result4.Must().T1) + assert.Equal(t, 42, result4.Must().T2) + }) + + t.Run("min key", func(t *testing.T) { + // Test with string keys + m := map[string]int{"b": 2, "a": 1, "c": 3} + result := xmap.MinKey(m) + assert.True(t, result.Ok()) + assert.Equal(t, "a", result.Must().T1) + assert.Equal(t, 1, result.Must().T2) + + // Test with int keys + m2 := map[int]string{3: "three", 1: "one", 5: "five"} + result2 := xmap.MinKey(m2) + assert.True(t, result2.Ok()) + assert.Equal(t, 1, result2.Must().T1) + assert.Equal(t, "one", result2.Must().T2) + + // Test empty map + emptyMap := map[string]int{} + result3 := xmap.MinKey(emptyMap) + assert.False(t, result3.Ok()) + + // Test single element + singleMap := map[string]int{"only": 42} + result4 := xmap.MinKey(singleMap) + assert.True(t, result4.Ok()) + assert.Equal(t, "only", result4.Must().T1) + assert.Equal(t, 42, result4.Must().T2) + }) + + t.Run("max value", func(t *testing.T) { + // Test with int values + m := map[string]int{"a": 3, "b": 1, "c": 2} + result := xmap.MaxValue(m) + assert.True(t, result.Ok()) + assert.Equal(t, "a", result.Must().T1) + assert.Equal(t, 3, result.Must().T2) + + // Test with string values + m2 := map[int]string{1: "zebra", 2: "apple", 3: "banana"} + result2 := xmap.MaxValue(m2) + assert.True(t, result2.Ok()) + assert.Equal(t, 1, result2.Must().T1) + assert.Equal(t, "zebra", result2.Must().T2) + + // Test empty map + emptyMap := map[string]int{} + result3 := xmap.MaxValue(emptyMap) + assert.False(t, result3.Ok()) + + // Test single element + singleMap := map[string]int{"only": 42} + result4 := xmap.MaxValue(singleMap) + assert.True(t, result4.Ok()) + assert.Equal(t, "only", result4.Must().T1) + assert.Equal(t, 42, result4.Must().T2) + + // Test with duplicate max values - should return one of them + dupMap := map[string]int{"x": 5, "y": 5, "z": 3} + result5 := xmap.MaxValue(dupMap) + assert.True(t, result5.Ok()) + assert.Equal(t, 5, result5.Must().T2) + assert.Contains(t, []string{"x", "y"}, result5.Must().T1) + }) + + t.Run("min value", func(t *testing.T) { + // Test with int values + m := map[string]int{"a": 3, "b": 1, "c": 2} + result := xmap.MinValue(m) + assert.True(t, result.Ok()) + assert.Equal(t, "b", result.Must().T1) + assert.Equal(t, 1, result.Must().T2) + + // Test with string values + m2 := map[int]string{1: "zebra", 2: "apple", 3: "banana"} + result2 := xmap.MinValue(m2) + assert.True(t, result2.Ok()) + assert.Equal(t, 2, result2.Must().T1) + assert.Equal(t, "apple", result2.Must().T2) + + // Test empty map + emptyMap := map[string]int{} + result3 := xmap.MinValue(emptyMap) + assert.False(t, result3.Ok()) + + // Test single element + singleMap := map[string]int{"only": 42} + result4 := xmap.MinValue(singleMap) + assert.True(t, result4.Ok()) + assert.Equal(t, "only", result4.Must().T1) + assert.Equal(t, 42, result4.Must().T2) + + // Test with duplicate min values - should return one of them + dupMap := map[string]int{"x": 1, "y": 1, "z": 3} + result5 := xmap.MinValue(dupMap) + assert.True(t, result5.Ok()) + assert.Equal(t, 1, result5.Must().T2) + assert.Contains(t, []string{"x", "y"}, result5.Must().T1) + }) + } diff --git a/pkg/xslice/xslice.go b/pkg/xslice/xslice.go index 6644b18..910826c 100644 --- a/pkg/xslice/xslice.go +++ b/pkg/xslice/xslice.go @@ -676,3 +676,76 @@ func Union[T comparable, Slice ~[]T](left, right Slice) Slice { } return xiter.ToSlice(xiter.Union(xiter.FromSlice(smaller), xiter.FromSlice(larger))) } + +// Remove returns a slice that remove all elements in wantToRemove +// +// EXAMPLE: +// +// arr := []int{1, 2, 3, 4} +// arr1 := xslice.Remove(arr, 1) +// fmt.Println(arr1) // [2, 3, 4] +func Remove[T comparable, Slice ~[]T](in Slice, wantToRemove ...T) Slice { + seq := xiter.FromSlice(wantToRemove) + return xiter.ToSlice(xiter.Filter(func(v T) bool { + if len(wantToRemove) == 0 { + return true + } + if len(wantToRemove) == 1 { + return wantToRemove[0] != v + } + return !xiter.Contains(seq, v) + }, xiter.FromSlice(in))) +} + +// Flatten returns a new slice with all nested slices flattened into a single slice. +// +// EXAMPLE: +// +// xslice.Flatten([][]int{{1, 2}, {3, 4}, {5}}) 👉 [1, 2, 3, 4, 5] +// xslice.Flatten([][]int{{1, 2}, {}, {3, 4}}) 👉 [1, 2, 3, 4] +// xslice.Flatten([][]int{}) 👉 []int{} +// xslice.Flatten([][]int{{}, {}, {}}) 👉 []int{} +func Flatten[T any](in [][]T) []T { + return Concat(in...) +} + +// ToMap returns a map where keys are elements from the slice and values are the result of applying f to each element. +// If there are duplicate keys in the slice, only the last element with that key will be present in the map. +// +// EXAMPLE: +// +// xslice.ToMap([]string{"a", "b", "c"}, func(s string) int { return len(s) }) 👉 map[a:1 b:1 c:1] +// xslice.ToMap([]int{1, 2, 3}, func(i int) string { return fmt.Sprintf("num_%d", i) }) 👉 map[1:num_1 2:num_2 3:num_3] +// xslice.ToMap([]int{1, 2, 1, 3}, func(i int) string { return fmt.Sprintf("val_%d", i) }) 👉 map[1:val_1 2:val_2 3:val_3] (note: key 1 has "val_1" from the last occurrence) +// xslice.ToMap([]int{}, func(i int) string { return "" }) 👉 map[int]string{} +func ToMap[T comparable, U any](in []T, f func(T) U) map[T]U { + return xiter.ToMap(xiter.Map2(func(idx int, in T) (T, U) { + return in, f(in) + }, xiter.FromSliceIdx(in))) +} + +// Sample returns a new slice with n randomly selected elements from the input slice. +// If n is greater than the length of the slice, it returns all elements in random order. +// If n is less than or equal to 0, it returns an empty slice. +// +// EXAMPLE: +// +// xslice.Sample([]int{1, 2, 3, 4, 5}, 3) 👉 [3, 1, 5] (random order, 3 elements) +// xslice.Sample([]int{1, 2, 3}, 5) 👉 [2, 1, 3] (random order, all elements) +// xslice.Sample([]int{1, 2, 3}, 0) 👉 []int{} +// xslice.Sample([]int{}, 3) 👉 []int{} +func Sample[T any, Slice ~[]T](in Slice, n int) Slice { + return xiter.ToSlice(xiter.Limit(xiter.FromSliceShuffle(in), n)) +} + +// RandomElement returns a random element from the slice as an optional.O[T]. +// If the slice is empty, it returns an optional.O[T] with Ok() == false. +// +// EXAMPLE: +// +// xslice.RandomElement([]int{1, 2, 3, 4, 5}) 👉 3 (random element) +// xslice.RandomElement([]int{42}) 👉 42 (always returns the only element) +// xslice.RandomElement([]int{}).Ok() 👉 false +func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T] { + return xiter.FirstO(xiter.FromSliceShuffle(in)) +} diff --git a/pkg/xslice/xslice_test.go b/pkg/xslice/xslice_test.go index 597b304..a58ce28 100644 --- a/pkg/xslice/xslice_test.go +++ b/pkg/xslice/xslice_test.go @@ -1,6 +1,7 @@ package xslice_test import ( + "fmt" "sort" "strconv" "testing" @@ -363,5 +364,128 @@ func TestSlices(t *testing.T) { res := xslice.Union(left, right) sort.Sort(sort.IntSlice(res)) assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8}, res) + res = xslice.Union(right, left) + sort.Sort(sort.IntSlice(res)) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8}, res) + }) + + t.Run("remove", func(t *testing.T) { + arr := []int{1, 2, 3, 4, 5, 6} + arr1 := xslice.Remove(arr, 1) + assert.Equal(t, []int{2, 3, 4, 5, 6}, arr1) + arr2 := xslice.Remove(_range(1, 100), _range(1, 50)...) + assert.Equal(t, _range(50, 100), arr2) + + arr3 := xslice.Remove(_range(1, 100)) + assert.Equal(t, _range(1, 100), arr3) + }) + + t.Run("flatten", func(t *testing.T) { + res := xslice.Flatten(append([][]int{}, _range(1, 100), _range(100, 200))) + assert.Equal(t, _range(1, 200), res) + + res = xslice.Flatten(xslice.Chunk(_range(1, 1000), 10)) + assert.Equal(t, _range(1, 1000), res) + }) + + t.Run("to map", func(t *testing.T) { + // Test basic string to int mapping + result := xslice.ToMap([]string{"a", "bb", "ccc"}, func(s string) int { return len(s) }) + expected := map[string]int{"a": 1, "bb": 2, "ccc": 3} + assert.Equal(t, expected, result) + + // Test int to string mapping + result2 := xslice.ToMap([]int{1, 2, 3}, func(i int) string { + return strconv.Itoa(i * 10) + }) + expected2 := map[int]string{1: "10", 2: "20", 3: "30"} + assert.Equal(t, expected2, result2) + + // Test duplicate keys - last one should win + result3 := xslice.ToMap([]int{1, 2, 1, 3}, func(i int) string { + return fmt.Sprintf("val_%d", i) + }) + expected3 := map[int]string{1: "val_1", 2: "val_2", 3: "val_3"} + assert.Equal(t, expected3, result3) + + // Test empty slice + result4 := xslice.ToMap([]int{}, func(i int) string { return "" }) + expected4 := map[int]string{} + assert.Equal(t, expected4, result4) + + // Test with struct transformation + type Person struct { + Name string + Age int + } + people := []Person{ + {Name: "Alice", Age: 30}, + {Name: "Bob", Age: 25}, + {Name: "Charlie", Age: 35}, + } + result5 := xslice.ToMap(people, func(p Person) int { return p.Age }) + expected5 := map[Person]int{ + {Name: "Alice", Age: 30}: 30, + {Name: "Bob", Age: 25}: 25, + {Name: "Charlie", Age: 35}: 35, + } + assert.Equal(t, expected5, result5) + }) + + t.Run("sample", func(t *testing.T) { + // Test basic sampling + source := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + sample := xslice.Sample(source, 3) + + // Should have exactly 3 elements + assert.Len(t, sample, 3) + + // All elements should be from the original slice + for _, elem := range sample { + assert.Contains(t, source, elem) + } + + // Test sampling more elements than available (should return all in random order) + sample2 := xslice.Sample(source, 15) + assert.Len(t, sample2, 10) // Should return all elements + + // Test with n = 0 (should return empty) + sample3 := xslice.Sample(source, 0) + assert.Len(t, sample3, 0) + + // Test with empty slice + sample4 := xslice.Sample([]int{}, 3) + assert.Len(t, sample4, 0) + + // Test with single element + sample5 := xslice.Sample([]int{42}, 1) + assert.Len(t, sample5, 1) + assert.Equal(t, 42, sample5[0]) + }) + + t.Run("random element", func(t *testing.T) { + // Test with non-empty slice + source := []int{1, 2, 3, 4, 5} + elem := xslice.RandomElement(source) + assert.True(t, elem.Ok()) + assert.Contains(t, source, elem.Must()) + + // Test with single element (should always return that element) + single := []int{42} + elem2 := xslice.RandomElement(single) + assert.True(t, elem2.Ok()) + assert.Equal(t, 42, elem2.Must()) + + // Test with empty slice + empty := []int{} + elem3 := xslice.RandomElement(empty) + assert.False(t, elem3.Ok()) + + // Test with string slice + strings := []string{"hello", "world", "test"} + elem4 := xslice.RandomElement(strings) + assert.True(t, elem4.Ok()) + assert.Contains(t, strings, elem4.Must()) }) + } From cf5e2966a8b8773ce39e91a6a94fafa7f067700b Mon Sep 17 00:00:00 2001 From: "kevin.zhao" Date: Thu, 18 Sep 2025 12:14:41 +0800 Subject: [PATCH 2/2] add doc --- pkg/xiter/README.md | 150 +++++++++++++-------------- pkg/xmap/README.md | 234 +++++++++++++++++++++++++++++++++++++++++-- pkg/xslice/README.md | 93 +++++++++++++++++ 3 files changed, 393 insertions(+), 84 deletions(-) diff --git a/pkg/xiter/README.md b/pkg/xiter/README.md index 3becc14..3f7fc48 100644 --- a/pkg/xiter/README.md +++ b/pkg/xiter/README.md @@ -40,11 +40,11 @@ WARNING: golang 1.23 has higher performance on iterating Seq/Seq2 which boost by - [func Last\[T any\]\(in Seq\[T\]\) \(T, bool\)](<#Last>) - [func LastO\[T any\]\(in Seq\[T\]\) optional.O\[T\]](<#LastO>) - [func Max\[T constraints.Ordered\]\(seq Seq\[T\]\) \(r optional.O\[T\]\)](<#Max>) -- [func MaxBy\[T constraints.Ordered\]\(seq Seq\[T\], less func\(T, T\) bool\) \(r optional.O\[T\]\)](<#MaxBy>) +- [func MaxBy\[T any\]\(seq Seq\[T\], less func\(T, T\) bool\) \(r optional.O\[T\]\)](<#MaxBy>) - [func Mean\[T constraints.Number\]\(in Seq\[T\]\) T](<#Mean>) - [func MeanBy\[T any, R constraints.Number\]\(in Seq\[T\], fn func\(T\) R\) R](<#MeanBy>) - [func Min\[T constraints.Ordered\]\(seq Seq\[T\]\) \(r optional.O\[T\]\)](<#Min>) -- [func MinBy\[T constraints.Ordered\]\(seq Seq\[T\], less func\(T, T\) bool\) \(r optional.O\[T\]\)](<#MinBy>) +- [func MinBy\[T any\]\(seq Seq\[T\], less func\(T, T\) bool\) \(r optional.O\[T\]\)](<#MinBy>) - [func Moderate\[T comparable\]\(in Seq\[T\]\) \(T, bool\)](<#Moderate>) - [func ModerateO\[T constraints.Number\]\(in Seq\[T\]\) optional.O\[T\]](<#ModerateO>) - [func Pull\[V any\]\(seq Seq\[V\]\) \(next func\(\) \(V, bool\), stop func\(\)\)](<#Pull>) @@ -102,7 +102,7 @@ WARNING: golang 1.23 has higher performance on iterating Seq/Seq2 which boost by -## func [AllFromSeq]() +## func [AllFromSeq]() ```go func AllFromSeq[T any](seq Seq[T], f func(T) bool) bool @@ -111,7 +111,7 @@ func AllFromSeq[T any](seq Seq[T], f func(T) bool) bool AllFromSeq return true if all elements from seq satisfy the condition evaluated by f. -## func [AnyFromSeq]() +## func [AnyFromSeq]() ```go func AnyFromSeq[T any](seq Seq[T], f func(T) bool) bool @@ -129,7 +129,7 @@ func At[T any](seq Seq[T], index int) optional.O[T] At return the element at index from seq. -## func [AvgByFromSeq]() +## func [AvgByFromSeq]() ```go func AvgByFromSeq[V any, T constraints.Number](seq Seq[V], f func(V) T) float64 @@ -138,7 +138,7 @@ func AvgByFromSeq[V any, T constraints.Number](seq Seq[V], f func(V) T) float64 AvgByFromSeq return the average value of all elements from seq, evaluated by f. -## func [AvgFromSeq]() +## func [AvgFromSeq]() ```go func AvgFromSeq[T constraints.Number](seq Seq[T]) float64 @@ -147,7 +147,7 @@ func AvgFromSeq[T constraints.Number](seq Seq[T]) float64 AvgFromSeq return the average value of all elements from seq. -## func [Contains]() +## func [Contains]() ```go func Contains[T comparable](seq Seq[T], in T) bool @@ -156,7 +156,7 @@ func Contains[T comparable](seq Seq[T], in T) bool Contains return true if v is in seq. -## func [ContainsAll]() +## func [ContainsAll]() ```go func ContainsAll[T comparable](seq Seq[T], in []T) bool @@ -165,7 +165,7 @@ func ContainsAll[T comparable](seq Seq[T], in []T) bool ContainsAll return true if all elements from seq is in vs. -## func [ContainsAny]() +## func [ContainsAny]() ```go func ContainsAny[T comparable](seq Seq[T], in []T) bool @@ -174,7 +174,7 @@ func ContainsAny[T comparable](seq Seq[T], in []T) bool ContainsAny return true if any element from seq is in vs. -## func [ContainsBy]() +## func [ContainsBy]() ```go func ContainsBy[T any](seq Seq[T], f func(T) bool) bool @@ -183,7 +183,7 @@ func ContainsBy[T any](seq Seq[T], f func(T) bool) bool ContainsBy return true if any element from seq satisfies the condition evaluated by f. -## func [Count]() +## func [Count]() ```go func Count[T any](seq Seq[T]) int @@ -211,7 +211,7 @@ onlyLeft, onlyRight := Difference(FromSlice(left), FromSlice(right)) ``` -## func [Equal]() +## func [Equal]() ```go func Equal[V comparable](x, y Seq[V]) bool @@ -266,7 +266,7 @@ false -## func [Equal2]() +## func [Equal2]() ```go func Equal2[K, V comparable](x, y Seq2[K, V]) bool @@ -275,7 +275,7 @@ func Equal2[K, V comparable](x, y Seq2[K, V]) bool Equal2 returns whether the two Seq2 are equal. Like Equal but run with Seq2 -## func [EqualFunc]() +## func [EqualFunc]() ```go func EqualFunc[V1, V2 any](x Seq[V1], y Seq[V2], f func(V1, V2) bool) bool @@ -335,7 +335,7 @@ false -## func [EqualFunc2]() +## func [EqualFunc2]() ```go func EqualFunc2[K1, V1, K2, V2 any](x Seq2[K1, V1], y Seq2[K2, V2], f func(K1, V1, K2, V2) bool) bool @@ -344,7 +344,7 @@ func EqualFunc2[K1, V1, K2, V2 any](x Seq2[K1, V1], y Seq2[K2, V2], f func(K1, V EqualFunc2 returns whether the two sequences are equal according to the function f. Like EqualFunc but run with Seq2 -## func [Find]() +## func [Find]() ```go func Find[T any](seq Seq[T], f func(T) bool) (val T, found bool) @@ -353,7 +353,7 @@ func Find[T any](seq Seq[T], f func(T) bool) (val T, found bool) Find return the first element from seq that satisfies the condition evaluated by f with a boolean representing whether it exists. -## func [FindO]() +## func [FindO]() ```go func FindO[T any](seq Seq[T], f func(T) bool) optional.O[T] @@ -362,7 +362,7 @@ func FindO[T any](seq Seq[T], f func(T) bool) optional.O[T] FindO return the first element from seq that satisfies the condition evaluated by f. -## func [First]() +## func [First]() ```go func First[T any](in Seq[T]) (T, bool) @@ -377,7 +377,7 @@ first, ok := First(seq) ``` -## func [FirstO]() +## func [FirstO]() ```go func FirstO[T any](in Seq[T]) optional.O[T] @@ -392,7 +392,7 @@ first, ok := FirstO(seq) ``` -## func [ForEach]() +## func [ForEach]() ```go func ForEach[T any](seq Seq[T], f func(T) bool) @@ -401,7 +401,7 @@ func ForEach[T any](seq Seq[T], f func(T) bool) ForEach execute f for each element in seq. -## func [ForEachIdx]() +## func [ForEachIdx]() ```go func ForEachIdx[T any](seq Seq[T], f func(idx int, v T) bool) @@ -410,7 +410,7 @@ func ForEachIdx[T any](seq Seq[T], f func(idx int, v T) bool) ForEachIdx execute f for each element in seq with its index. -## func [Head]() +## func [Head]() ```go func Head[T any](seq Seq[T]) (v T, hasOne bool) @@ -419,7 +419,7 @@ func Head[T any](seq Seq[T]) (v T, hasOne bool) Head return the first element from seq with a boolean representing whether it is at least one element in seq. -## func [HeadO]() +## func [HeadO]() ```go func HeadO[T any](seq Seq[T]) optional.O[T] @@ -428,7 +428,7 @@ func HeadO[T any](seq Seq[T]) optional.O[T] HeadO return the first element from seq. -## func [Index]() +## func [Index]() ```go func Index[T comparable](seq Seq[T], v T) int @@ -445,7 +445,7 @@ idx := xiter.Index(seq, 3) ``` -## func [Join]() +## func [Join]() ```go func Join[T ~string](seq Seq[T], sep T) T @@ -454,7 +454,7 @@ func Join[T ~string](seq Seq[T], sep T) T Join return the concatenation of all elements in seq with sep. -## func [Last]() +## func [Last]() ```go func Last[T any](in Seq[T]) (T, bool) @@ -469,7 +469,7 @@ last, ok := Last(seq) ``` -## func [LastO]() +## func [LastO]() ```go func LastO[T any](in Seq[T]) optional.O[T] @@ -484,7 +484,7 @@ last, ok := LastO(seq) ``` -## func [Max]() +## func [Max]() ```go func Max[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) @@ -493,10 +493,10 @@ func Max[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) Max returns the maximum element in seq. -## func [MaxBy]() +## func [MaxBy]() ```go -func MaxBy[T constraints.Ordered](seq Seq[T], less func(T, T) bool) (r optional.O[T]) +func MaxBy[T any](seq Seq[T], less func(T, T) bool) (r optional.O[T]) ``` MaxBy return the maximum element in seq, evaluated by f. @@ -536,7 +536,7 @@ mean := MeanBy(FromSlice([]int{1, 2, 3, 4, 5}), func(v int) int { ``` -## func [Min]() +## func [Min]() ```go func Min[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) @@ -545,10 +545,10 @@ func Min[T constraints.Ordered](seq Seq[T]) (r optional.O[T]) Min return the minimum element in seq. -## func [MinBy]() +## func [MinBy]() ```go -func MinBy[T constraints.Ordered](seq Seq[T], less func(T, T) bool) (r optional.O[T]) +func MinBy[T any](seq Seq[T], less func(T, T) bool) (r optional.O[T]) ``` MinBy return the minimum element in seq, evaluated by f. @@ -586,7 +586,7 @@ moderate := ModerateO(FromSlice([]int{1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 6})) ``` -## func [Pull]() +## func [Pull]() ```go func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func()) @@ -637,7 +637,7 @@ func main() { -## func [Pull2]() +## func [Pull2]() ```go func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func()) @@ -646,7 +646,7 @@ func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func()) -## func [Reduce]() +## func [Reduce]() ```go func Reduce[Sum, V any](f func(Sum, V) Sum, sum Sum, seq Seq[V]) Sum @@ -705,7 +705,7 @@ func main() { -## func [Reduce2]() +## func [Reduce2]() ```go func Reduce2[Sum, K, V any](f func(Sum, K, V) Sum, sum Sum, seq Seq2[K, V]) Sum @@ -714,7 +714,7 @@ func Reduce2[Sum, K, V any](f func(Sum, K, V) Sum, sum Sum, seq Seq2[K, V]) Sum Reduce2 combines the values in seq using f. For each pair k, v in seq, it updates sum = f\(sum, k, v\) and then returns the final sum. For example, if iterating over seq yields \(k1, v1\), \(k2, v2\), \(k3, v3\) Reduce returns f\(f\(f\(sum, k1, v1\), k2, v2\), k3, v3\). -## func [Sum]() +## func [Sum]() ```go func Sum[T constraints.Number](seq Seq[T]) T @@ -731,7 +731,7 @@ sum := xiter.Sum(seq) ``` -## func [ToMap]() +## func [ToMap]() ```go func ToMap[K comparable, V any](seq Seq2[K, V]) (out map[K]V) @@ -740,7 +740,7 @@ func ToMap[K comparable, V any](seq Seq2[K, V]) (out map[K]V) -## func [ToMapFromSeq]() +## func [ToMapFromSeq]() ```go func ToMapFromSeq[K comparable, V any](seq Seq[K], fn func(k K) V) (out map[K]V) @@ -749,7 +749,7 @@ func ToMapFromSeq[K comparable, V any](seq Seq[K], fn func(k K) V) (out map[K]V) -## func [ToSlice]() +## func [ToSlice]() ```go func ToSlice[T any](seq Seq[T]) (out []T) @@ -758,7 +758,7 @@ func ToSlice[T any](seq Seq[T]) (out []T) ToSlice returns the elements in seq as a slice. -## func [ToSliceN]() +## func [ToSliceN]() ```go func ToSliceN[T any](seq Seq[T], n int) (out []T) @@ -767,7 +767,7 @@ func ToSliceN[T any](seq Seq[T], n int) (out []T) ToSliceN pull out n elements from seq. -## func [ToSliceSeq2Key]() +## func [ToSliceSeq2Key]() ```go func ToSliceSeq2Key[K, V any](seq Seq2[K, V]) (out []K) @@ -784,7 +784,7 @@ keys := ToSliceSeq2Key(seq) ``` -## func [ToSliceSeq2Value]() +## func [ToSliceSeq2Value]() ```go func ToSliceSeq2Value[K, V any](seq Seq2[K, V]) (out []V) @@ -801,7 +801,7 @@ values := ToSliceSeq2Value(seq) ``` -## type [Seq]() +## type [Seq]() Seq is a sequence of elements provided by an iterator\-like function. We made this alias Seq to iter.Seq for providing a compatible interface in lower go versions. @@ -810,7 +810,7 @@ type Seq[V any] iter.Seq[V] ``` -### func [Chunk]() +### func [Chunk]() ```go func Chunk[T any](seq Seq[T], n int) Seq[[]T] @@ -827,7 +827,7 @@ chunkedSeq := xiter.Chunk(seq, 2) ``` -### func [Compact]() +### func [Compact]() ```go func Compact[T comparable](in Seq[T]) Seq[T] @@ -842,7 +842,7 @@ Compact([]int{0, 1, 2, 3, 4}) 👉 [1 2 3 4] ``` -### func [Concat]() +### func [Concat]() ```go func Concat[V any](seqs ...Seq[V]) Seq[V] @@ -895,7 +895,7 @@ func main() { -### func [Filter]() +### func [Filter]() ```go func Filter[V any](f func(V) bool, seq Seq[V]) Seq[V] @@ -965,7 +965,7 @@ _ = ToSlice(seq) // Returns []int{1, 2} ``` -### func [FromMapKeys]() +### func [FromMapKeys]() ```go func FromMapKeys[K comparable, V any](m map[K]V) Seq[K] @@ -974,7 +974,7 @@ func FromMapKeys[K comparable, V any](m map[K]V) Seq[K] -### func [FromMapValues]() +### func [FromMapValues]() ```go func FromMapValues[K comparable, V any](m map[K]V) Seq[V] @@ -1001,7 +1001,7 @@ func FromSliceReverse[T any, Slice ~[]T](in Slice) Seq[T] -### func [FromSliceShuffle]() +### func [FromSliceShuffle]() ```go func FromSliceShuffle[T any](in []T) Seq[T] @@ -1036,7 +1036,7 @@ intersect := Intersect(FromSlice(left), FromSlice(right)) ``` -### func [Limit]() +### func [Limit]() ```go func Limit[V any](seq Seq[V], n int) Seq[V] @@ -1085,7 +1085,7 @@ func main() { -### func [Map]() +### func [Map]() ```go func Map[In, Out any](f func(In) Out, seq Seq[In]) Seq[Out] @@ -1134,7 +1134,7 @@ func main() { -### func [Merge]() +### func [Merge]() ```go func Merge[V cmp.Ordered](x, y Seq[V]) Seq[V] @@ -1176,7 +1176,7 @@ func main() { -### func [MergeFunc]() +### func [MergeFunc]() ```go func MergeFunc[V any](x, y Seq[V], f func(V, V) int) Seq[V] @@ -1194,7 +1194,7 @@ func Repeat[T any](seq Seq[T], count int) Seq[T] Repeat return a seq that repeat seq for count times. -### func [Replace]() +### func [Replace]() ```go func Replace[T comparable](seq Seq[T], from, to T, n int) Seq[T] @@ -1211,7 +1211,7 @@ replacedSeq := Replace(seq, 2, 99, -1) // Replace all 2s with 99 ``` -### func [ReplaceAll]() +### func [ReplaceAll]() ```go func ReplaceAll[T comparable](seq Seq[T], from, to T) Seq[T] @@ -1237,7 +1237,7 @@ func Reverse[T any](seq Seq[T]) Seq[T] Reverse return a reversed seq. -### func [Seq2KeyToSeq]() +### func [Seq2KeyToSeq]() ```go func Seq2KeyToSeq[K, V any](in Seq2[K, V]) Seq[K] @@ -1246,7 +1246,7 @@ func Seq2KeyToSeq[K, V any](in Seq2[K, V]) Seq[K] Seq2KeyToSeq return a seq that only contain keys in seq2. -### func [Seq2ToSeqUnion]() +### func [Seq2ToSeqUnion]() ```go func Seq2ToSeqUnion[K, V any](seq Seq2[K, V]) Seq[union.U2[K, V]] @@ -1264,7 +1264,7 @@ for v := range Seq2ToSeqUnion(seq2) { ``` -### func [Seq2ValueToSeq]() +### func [Seq2ValueToSeq]() ```go func Seq2ValueToSeq[K, V any](in Seq2[K, V]) Seq[V] @@ -1273,7 +1273,7 @@ func Seq2ValueToSeq[K, V any](in Seq2[K, V]) Seq[V] Seq2ValueToSeq return a seq that only contain values in seq2. -### func [Skip]() +### func [Skip]() ```go func Skip[T any](seq Seq[T], n int) Seq[T] @@ -1300,7 +1300,7 @@ union := Union(FromSlice(left), FromSlice(right)) ``` -### func [Uniq]() +### func [Uniq]() ```go func Uniq[T comparable](seq Seq[T]) Seq[T] @@ -1317,7 +1317,7 @@ uniqSeq := xiter.Uniq(seq) ``` -### func [Zip]() +### func [Zip]() ```go func Zip[V1, V2 any](x Seq[V1], y Seq[V2]) Seq[Zipped[V1, V2]] @@ -1375,7 +1375,7 @@ func main() { -### func [Zip2]() +### func [Zip2]() ```go func Zip2[K1, V1, K2, V2 any](x Seq2[K1, V1], y Seq2[K2, V2]) Seq[Zipped2[K1, V1, K2, V2]] @@ -1397,7 +1397,7 @@ func Equal2[K, V comparable](x, y Seq2[K, V]) bool { ``` -## type [Seq2]() +## type [Seq2]() Seq2 is a sequence of key/value pair provided by an iterator\-like function. We made this alias Seq2 to iter.Seq2 for providing a compatible interface in lower go versions. @@ -1406,7 +1406,7 @@ type Seq2[K, V any] iter.Seq2[K, V] ``` -### func [Concat2]() +### func [Concat2]() ```go func Concat2[K, V any](seqs ...Seq2[K, V]) Seq2[K, V] @@ -1415,7 +1415,7 @@ func Concat2[K, V any](seqs ...Seq2[K, V]) Seq2[K, V] Concat2 returns an Seq2 over the concatenation of the given Seq2s. Like Concat but run with Seq2 -### func [Filter2]() +### func [Filter2]() ```go func Filter2[K, V any](f func(K, V) bool, seq Seq2[K, V]) Seq2[K, V] @@ -1424,7 +1424,7 @@ func Filter2[K, V any](f func(K, V) bool, seq Seq2[K, V]) Seq2[K, V] Filter2 returns an Seq over seq that only includes the key\-value pairs k, v for which f\(k, v\) is true. Like Filter but run with Seq2 -### func [FromMapKeyAndValues]() +### func [FromMapKeyAndValues]() ```go func FromMapKeyAndValues[K comparable, V any](m map[K]V) Seq2[K, V] @@ -1442,7 +1442,7 @@ func FromSliceIdx[T any](in []T) Seq2[int, T] FromSliceIdx received a slice and returned a Seq2 for this slice, key is index. -### func [Limit2]() +### func [Limit2]() ```go func Limit2[K, V any](seq Seq2[K, V], n int) Seq2[K, V] @@ -1451,7 +1451,7 @@ func Limit2[K, V any](seq Seq2[K, V], n int) Seq2[K, V] Limit2 returns a Seq over Seq2 that stops after n key\-value pairs. Like Limit but run with Seq2 -### func [Map2]() +### func [Map2]() ```go func Map2[KIn, VIn, KOut, VOut any](f func(KIn, VIn) (KOut, VOut), seq Seq2[KIn, VIn]) Seq2[KOut, VOut] @@ -1460,7 +1460,7 @@ func Map2[KIn, VIn, KOut, VOut any](f func(KIn, VIn) (KOut, VOut), seq Seq2[KIn, Map2 returns a Seq2 over the results of applying f to each key\-value pair in seq. Like Map but run with Seq2 -### func [MapToSeq2]() +### func [MapToSeq2]() ```go func MapToSeq2[T any, K comparable](in Seq[T], mapFn func(ele T) K) Seq2[K, T] @@ -1481,7 +1481,7 @@ fmt.Println(ToMap(lenMap)) ``` -### func [MapToSeq2Value]() +### func [MapToSeq2Value]() ```go func MapToSeq2Value[T any, K comparable, V any](in Seq[T], mapFn func(ele T) (K, V)) Seq2[K, V] @@ -1501,7 +1501,7 @@ fmt.Println(ToMap(transformed)) ``` -### func [Merge2]() +### func [Merge2]() ```go func Merge2[K cmp.Ordered, V any](x, y Seq2[K, V]) Seq2[K, V] @@ -1512,7 +1512,7 @@ Merge2 merges two sequences of key\-value pairs ordered by their keys. Pairs app Merge2 is equivalent to calling MergeFunc2 with cmp.Compare\[K\] as the ordering function. -### func [MergeFunc2]() +### func [MergeFunc2]() ```go func MergeFunc2[K, V any](x, y Seq2[K, V], f func(K, K) int) Seq2[K, V] diff --git a/pkg/xmap/README.md b/pkg/xmap/README.md index 5e6e50d..0388229 100644 --- a/pkg/xmap/README.md +++ b/pkg/xmap/README.md @@ -11,6 +11,8 @@ import "github.com/dashjay/xiter/pkg/xmap" - [func Clone\[M \~map\[K\]V, K comparable, V any\]\(m M\) M](<#Clone>) - [func CoalesceMaps\[M \~map\[K\]V, K comparable, V any\]\(maps ...M\) M](<#CoalesceMaps>) - [func Copy\[M1 \~map\[K\]V, M2 \~map\[K\]V, K comparable, V any\]\(dst M1, src M2\)](<#Copy>) +- [func Delete\[K comparable, V any\]\(m map\[K\]V, k K\) \(old V, deleted bool\)](<#Delete>) +- [func DeleteIf\[K comparable, V any\]\(m map\[K\]V, k K, c func\(K, V\) bool\) \(old V, deleted bool\)](<#DeleteIf>) - [func Equal\[M1, M2 \~map\[K\]V, K, V comparable\]\(m1 M1, m2 M2\) bool](<#Equal>) - [func EqualFunc\[M1 \~map\[K\]V1, M2 \~map\[K\]V2, K comparable, V1, V2 any\]\(m1 M1, m2 M2, eq func\(V1, V2\) bool\) bool](<#EqualFunc>) - [func Filter\[M \~map\[K\]V, K comparable, V any\]\(in M, fn func\(K, V\) bool\) M](<#Filter>) @@ -21,8 +23,14 @@ import "github.com/dashjay/xiter/pkg/xmap" - [func Keys\[M \~map\[K\]V, K comparable, V any\]\(m M\) \[\]K](<#Keys>) - [func MapKeys\[K comparable, V1 any\]\(in map\[K\]V1, fn func\(K, V1\) K\) map\[K\]V1](<#MapKeys>) - [func MapValues\[K comparable, V1, V2 any\]\(in map\[K\]V1, fn func\(K, V1\) V2\) map\[K\]V2](<#MapValues>) +- [func MaxKey\[K constraints.Ordered, V any\]\(m map\[K\]V\) optional.O\[union.U2\[K, V\]\]](<#MaxKey>) +- [func MaxValue\[K comparable, V constraints.Ordered\]\(m map\[K\]V\) optional.O\[union.U2\[K, V\]\]](<#MaxValue>) +- [func MinKey\[K constraints.Ordered, V any\]\(m map\[K\]V\) optional.O\[union.U2\[K, V\]\]](<#MinKey>) +- [func MinValue\[K comparable, V constraints.Ordered\]\(m map\[K\]V\) optional.O\[union.U2\[K, V\]\]](<#MinValue>) - [func ToUnionSlice\[M \~map\[K\]V, K comparable, V any\]\(m M\) \[\]union.U2\[K, V\]](<#ToUnionSlice>) - [func ToXSyncMap\[K comparable, V any\]\(in map\[K\]V\) \*xsync.SyncMap\[K, V\]](<#ToXSyncMap>) +- [func Update\[K comparable, V any\]\(m map\[K\]V, k K, v V\) \(old V, replaced bool\)](<#Update>) +- [func UpdateIf\[K comparable, V any\]\(m map\[K\]V, k K, v V, c func\(K, V\) bool\) \(old V, replaced bool\)](<#UpdateIf>) - [func Values\[M \~map\[K\]V, K comparable, V any\]\(m M\) \[\]V](<#Values>) @@ -36,7 +44,7 @@ func Clone[M ~map[K]V, K comparable, V any](m M) M -## func [CoalesceMaps]() +## func [CoalesceMaps]() ```go func CoalesceMaps[M ~map[K]V, K comparable, V any](maps ...M) M @@ -79,6 +87,46 @@ func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) + +## func [Delete]() + +```go +func Delete[K comparable, V any](m map[K]V, k K) (old V, deleted bool) +``` + +Delete removes a key from the map and returns the old value and whether it was deleted. If the key exists, it removes the key\-value pair and returns the old value with deleted=true. If the key doesn't exist, it returns the zero value with deleted=false. + +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 2} +old, deleted := xmap.Delete(m, "b") +// old=2, deleted=true, m is now {"a": 1} +old, deleted = xmap.Delete(m, "c") +// old=0, deleted=false, m remains {"a": 1} +``` + + +## func [DeleteIf]() + +```go +func DeleteIf[K comparable, V any](m map[K]V, k K, c func(K, V) bool) (old V, deleted bool) +``` + +DeleteIf removes a key from the map only if the condition function returns true. It returns the old value and whether it was deleted. If the key exists and the condition is true, it removes the key\-value pair and returns the old value with deleted=true. Otherwise, it returns the zero value with deleted=false. + +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 2} +old, deleted := xmap.DeleteIf(m, "b", func(k string, v int) bool { return v > 1 }) +// old=2, deleted=true, m is now {"a": 1} +old, deleted = xmap.DeleteIf(m, "b", func(k string, v int) bool { return v > 10 }) +// old=0, deleted=false, m remains {"a": 1} +old, deleted = xmap.DeleteIf(m, "c", func(k string, v int) bool { return v > 0 }) +// old=0, deleted=false (key doesn't exist), m remains {"a": 1} +``` + ## func [Equal]() @@ -98,7 +146,7 @@ func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M -## func [Filter]() +## func [Filter]() ```go func Filter[M ~map[K]V, K comparable, V any](in M, fn func(K, V) bool) M @@ -116,40 +164,84 @@ result := Filter(m, fn) ``` -## func [Find]() +## func [Find]() ```go func Find[K comparable, V any](in map[K]V, fn func(K, V) bool) (K, V, bool) ``` +Find searches for the first key\-value pair in the map that satisfies the given condition function. It returns the key, value, and whether such a pair was found. The search order is not guaranteed to be consistent due to the nature of Go's map iteration. If no pair satisfies the condition, it returns the zero key, zero value, and false. + +EXAMPLE: +``` +m := map[string]int{"a": 1, "b": 2, "c": 3} +key, value, found := xmap.Find(m, func(k string, v int) bool { return v > 1 }) +// key could be "b" or "c", value=2 or 3, found=true (first match found) +key, value, found = xmap.Find(m, func(k string, v int) bool { return v > 10 }) +// key="", value=0, found=false +``` -## func [FindKey]() +## func [FindKey]() ```go func FindKey[K comparable, V any](in map[K]V, target K) (K, V, bool) ``` +FindKey finds a key in the map and returns the key, its value, and whether it was found. This is essentially a wrapper around the built\-in map lookup that returns the key along with the value. If the key exists, it returns the key, its value, and true. If the key doesn't exist, it returns the zero key, zero value, and false. +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 2} +key, value, found := xmap.FindKey(m, "b") +// key="b", value=2, found=true +key, value, found = xmap.FindKey(m, "c") +// key="", value=0, found=false +``` -## func [FindKeyO]() +## func [FindKeyO]() ```go func FindKeyO[K comparable, V any](in map[K]V, target K) optional.O[union.U2[K, V]] ``` +FindKeyO finds a key in the map and returns the key\-value pair as an optional.O\[union.U2\[K, V\]\]. If the key exists, it returns an optional containing a union.U2 with the key in T1 and value in T2. If the key doesn't exist, it returns an empty optional. +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 2} +result := xmap.FindKeyO(m, "b") +// result.Ok() == true, result.Must() contains union.U2[string, int]{T1: "b", T2: 2} +key := result.Must().T1 // "b" +value := result.Must().T2 // 2 +result2 := xmap.FindKeyO(m, "c") +// result2.Ok() == false +``` -## func [FindO]() +## func [FindO]() ```go func FindO[K comparable, V any](in map[K]V, fn func(K, V) bool) optional.O[union.U2[K, V]] ``` +FindO searches for the first key\-value pair in the map that satisfies the given condition function. It returns the key\-value pair as an optional.O\[union.U2\[K, V\]\]. The search order is not guaranteed to be consistent due to the nature of Go's map iteration. If a pair satisfies the condition, it returns an optional containing a union.U2 with the key in T1 and value in T2. If no pair satisfies the condition, it returns an empty optional. +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 2, "c": 3} +result := xmap.FindO(m, func(k string, v int) bool { return v > 1 }) +// result.Ok() == true, result.Must() contains union.U2[string, int] with key and value of first match +key := result.Must().T1 // could be "b" or "c" +value := result.Must().T2 // could be 2 or 3 +result2 := xmap.FindO(m, func(k string, v int) bool { return v > 10 }) +// result2.Ok() == false +``` ## func [Keys]() @@ -161,7 +253,7 @@ func Keys[M ~map[K]V, K comparable, V any](m M) []K -## func [MapKeys]() +## func [MapKeys]() ```go func MapKeys[K comparable, V1 any](in map[K]V1, fn func(K, V1) K) map[K]V1 @@ -194,7 +286,7 @@ result := MapKeys(m, fn) ``` -## func [MapValues]() +## func [MapValues]() ```go func MapValues[K comparable, V1, V2 any](in map[K]V1, fn func(K, V1) V2) map[K]V2 @@ -226,6 +318,90 @@ result := MapValues(m, fn) // result will be map[string]string{"a": "value_1", "b": "value_2", "c": "value_3"} ``` + +## func [MaxKey]() + +```go +func MaxKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]] +``` + +MaxKey returns the maximum key in the map and its associated value as an optional.O\[union.U2\[K, V\]\]. If the map is empty, it returns an optional.O\[union.U2\[K, V\]\] with Ok\(\) == false. The returned union.U2 contains the key in T1 and the value in T2. + +EXAMPLE: + +``` +m := map[string]int{"b": 2, "a": 1, "c": 3} +result := xmap.MaxKey(m) +// result contains union.U2[string, int]{T1: "c", T2: 3} +maxKey := result.Must().T1 // "c" +maxValue := result.Must().T2 // 3 +result2 := xmap.MaxKey(map[string]int{}) +// result2.Ok() == false +``` + + +## func [MaxValue]() + +```go +func MaxValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]] +``` + +MaxValue returns the maximum value in the map and its associated key as an optional.O\[union.U2\[K, V\]\]. If the map is empty, it returns an optional.O\[union.U2\[K, V\]\] with Ok\(\) == false. The returned union.U2 contains the key in T1 and the maximum value in T2. + +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 3, "c": 2} +result := xmap.MaxValue(m) +// result contains union.U2[string, int]{T1: "b", T2: 3} +maxKey := result.Must().T1 // "b" +maxValue := result.Must().T2 // 3 +result2 := xmap.MaxValue(map[string]int{}) +// result2.Ok() == false +``` + + +## func [MinKey]() + +```go +func MinKey[K constraints.Ordered, V any](m map[K]V) optional.O[union.U2[K, V]] +``` + +MinKey returns the minimum key in the map and its associated value as an optional.O\[union.U2\[K, V\]\]. If the map is empty, it returns an optional.O\[union.U2\[K, V\]\] with Ok\(\) == false. The returned union.U2 contains the key in T1 and the value in T2. + +EXAMPLE: + +``` +m := map[string]int{"b": 2, "a": 1, "c": 3} +result := xmap.MinKey(m) +// result contains union.U2[string, int]{T1: "a", T2: 1} +minKey := result.Must().T1 // "a" +minValue := result.Must().T2 // 1 +result2 := xmap.MinKey(map[string]int{}) +// result2.Ok() == false +``` + + +## func [MinValue]() + +```go +func MinValue[K comparable, V constraints.Ordered](m map[K]V) optional.O[union.U2[K, V]] +``` + +MinValue returns the minimum value in the map and its associated key as an optional.O\[union.U2\[K, V\]\]. If the map is empty, it returns an optional.O\[union.U2\[K, V\]\] with Ok\(\) == false. The returned union.U2 contains the key in T1 and the minimum value in T2. + +EXAMPLE: + +``` +m := map[string]int{"a": 3, "b": 1, "c": 2} +result := xmap.MinValue(m) +// result contains union.U2[string, int]{T1: "b", T2: 1} +minKey := result.Must().T1 // "b" +minValue := result.Must().T2 // 1 +result2 := xmap.MinValue(map[string]int{}) +// result2.Ok() == false +``` + ## func [ToUnionSlice]() @@ -236,7 +412,7 @@ func ToUnionSlice[M ~map[K]V, K comparable, V any](m M) []union.U2[K, V] -## func [ToXSyncMap]() +## func [ToXSyncMap]() ```go func ToXSyncMap[K comparable, V any](in map[K]V) *xsync.SyncMap[K, V] @@ -256,6 +432,46 @@ Returns: *xsync.SyncMap[K, V]: A new xsync.SyncMap containing the same key-value pairs as the input map ``` + +## func [Update]() + +```go +func Update[K comparable, V any](m map[K]V, k K, v V) (old V, replaced bool) +``` + +Update updates the value for a key in the map and returns the old value and whether it was replaced. If the key exists, it updates the value and returns the old value with replaced=true. If the key doesn't exist, it adds the key\-value pair and returns the zero value with replaced=false. + +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 2} +old, replaced := xmap.Update(m, "b", 20) +// old=2, replaced=true, m is now {"a": 1, "b": 20} +old, replaced = xmap.Update(m, "c", 3) +// old=0, replaced=false, m is now {"a": 1, "b": 20, "c": 3} +``` + + +## func [UpdateIf]() + +```go +func UpdateIf[K comparable, V any](m map[K]V, k K, v V, c func(K, V) bool) (old V, replaced bool) +``` + +UpdateIf updates the value for a key in the map only if the condition function returns true. It returns the old value and whether it was replaced. If the key exists and the condition is true, it updates the value and returns the old value with replaced=true. Otherwise, it returns the zero value with replaced=false. + +EXAMPLE: + +``` +m := map[string]int{"a": 1, "b": 2} +old, replaced := xmap.UpdateIf(m, "b", 20, func(k string, v int) bool { return v > 1 }) +// old=2, replaced=true, m is now {"a": 1, "b": 20} +old, replaced = xmap.UpdateIf(m, "b", 200, func(k string, v int) bool { return v > 50 }) +// old=0, replaced=false, m remains {"a": 1, "b": 20} +old, replaced = xmap.UpdateIf(m, "c", 3, func(k string, v int) bool { return v > 0 }) +// old=0, replaced=false (key doesn't exist), m remains {"a": 1, "b": 20} +``` + ## func [Values]() diff --git a/pkg/xslice/README.md b/pkg/xslice/README.md index 07395fc..4778f98 100644 --- a/pkg/xslice/README.md +++ b/pkg/xslice/README.md @@ -30,6 +30,7 @@ import "github.com/dashjay/xiter/pkg/xslice" - [func FindO\[T any\]\(in \[\]T, f func\(T\) bool\) optional.O\[T\]](<#FindO>) - [func First\[T any, Slice \~\[\]T\]\(in Slice\) \(T, bool\)](<#First>) - [func FirstO\[T any, Slice \~\[\]T\]\(in Slice\) optional.O\[T\]](<#FirstO>) +- [func Flatten\[T any\]\(in \[\]\[\]T\) \[\]T](<#Flatten>) - [func ForEach\[T any\]\(in \[\]T, f func\(T\) bool\)](<#ForEach>) - [func ForEachIdx\[T any\]\(in \[\]T, f func\(idx int, v T\) bool\)](<#ForEachIdx>) - [func GroupBy\[T any, K comparable, Slice \~\[\]T\]\(in Slice, f func\(T\) K\) map\[K\]Slice](<#GroupBy>) @@ -48,12 +49,15 @@ import "github.com/dashjay/xiter/pkg/xslice" - [func Min\[T constraints.Ordered\]\(in \[\]T\) optional.O\[T\]](<#Min>) - [func MinBy\[T constraints.Ordered\]\(in \[\]T, f func\(T, T\) bool\) optional.O\[T\]](<#MinBy>) - [func MinN\[T constraints.Ordered\]\(in ...T\) optional.O\[T\]](<#MinN>) +- [func RandomElement\[T any, Slice \~\[\]T\]\(in Slice\) optional.O\[T\]](<#RandomElement>) +- [func Remove\[T comparable, Slice \~\[\]T\]\(in Slice, wantToRemove ...T\) Slice](<#Remove>) - [func Repeat\[T any, Slice \~\[\]T\]\(in Slice, count int\) Slice](<#Repeat>) - [func RepeatBy\[T any\]\(n int, f func\(i int\) T\) \[\]T](<#RepeatBy>) - [func Replace\[T comparable, Slice \~\[\]T\]\(in Slice, from, to T, count int\) \[\]T](<#Replace>) - [func ReplaceAll\[T comparable, Slice \~\[\]T\]\(in Slice, from, to T\) \[\]T](<#ReplaceAll>) - [func Reverse\[T any, Slice \~\[\]T\]\(in Slice\)](<#Reverse>) - [func ReverseClone\[T any, Slice \~\[\]T\]\(in Slice\) Slice](<#ReverseClone>) +- [func Sample\[T any, Slice \~\[\]T\]\(in Slice, n int\) Slice](<#Sample>) - [func Shuffle\[T any, Slice \~\[\]T\]\(in Slice\) Slice](<#Shuffle>) - [func ShuffleInPlace\[T any, Slice \~\[\]T\]\(in Slice\)](<#ShuffleInPlace>) - [func Subset\[T any, Slice \~\[\]T\]\(in Slice, start, count int\) Slice](<#Subset>) @@ -61,6 +65,7 @@ import "github.com/dashjay/xiter/pkg/xslice" - [func Sum\[T constraints.Number, Slice \~\[\]T\]\(in Slice\) T](<#Sum>) - [func SumBy\[T any, R constraints.Number, Slice \~\[\]T\]\(in Slice, f func\(T\) R\) R](<#SumBy>) - [func SumN\[T constraints.Number\]\(in ...T\) T](<#SumN>) +- [func ToMap\[T comparable, U any\]\(in \[\]T, f func\(T\) U\) map\[T\]U](<#ToMap>) - [func Union\[T comparable, Slice \~\[\]T\]\(left, right Slice\) Slice](<#Union>) - [func Uniq\[T comparable, Slice \~\[\]T\]\(in Slice\) Slice](<#Uniq>) @@ -424,6 +429,24 @@ xslice.FirstO([]int{1, 2, 3}) 👉 1 xslice.FirstO([]int{}) 👉 0 ``` + +## func [Flatten]() + +```go +func Flatten[T any](in [][]T) []T +``` + +Flatten returns a new slice with all nested slices flattened into a single slice. + +EXAMPLE: + +``` +xslice.Flatten([][]int{{1, 2}, {3, 4}, {5}}) 👉 [1, 2, 3, 4, 5] +xslice.Flatten([][]int{{1, 2}, {}, {3, 4}}) 👉 [1, 2, 3, 4] +xslice.Flatten([][]int{}) 👉 []int{} +xslice.Flatten([][]int{{}, {}, {}}) 👉 []int{} +``` + ## func [ForEach]() @@ -715,6 +738,40 @@ EXAMPLE: xslice.MinN(1, 2, 3) 👉 1 ``` + +## func [RandomElement]() + +```go +func RandomElement[T any, Slice ~[]T](in Slice) optional.O[T] +``` + +RandomElement returns a random element from the slice as an optional.O\[T\]. If the slice is empty, it returns an optional.O\[T\] with Ok\(\) == false. + +EXAMPLE: + +``` +xslice.RandomElement([]int{1, 2, 3, 4, 5}) 👉 3 (random element) +xslice.RandomElement([]int{42}) 👉 42 (always returns the only element) +xslice.RandomElement([]int{}).Ok() 👉 false +``` + + +## func [Remove]() + +```go +func Remove[T comparable, Slice ~[]T](in Slice, wantToRemove ...T) Slice +``` + +Remove returns a slice that remove all elements in wantToRemove + +EXAMPLE: + +``` +arr := []int{1, 2, 3, 4} +arr1 := xslice.Remove(arr, 1) +fmt.Println(arr1) // [2, 3, 4] +``` + ## func [Repeat]() @@ -812,6 +869,24 @@ xslice.ReverseClone([]int{}) 👉 []int{} xslice.ReverseClone([]int{3, 2, 1}) 👉 [1, 2, 3] ``` + +## func [Sample]() + +```go +func Sample[T any, Slice ~[]T](in Slice, n int) Slice +``` + +Sample returns a new slice with n randomly selected elements from the input slice. If n is greater than the length of the slice, it returns all elements in random order. If n is less than or equal to 0, it returns an empty slice. + +EXAMPLE: + +``` +xslice.Sample([]int{1, 2, 3, 4, 5}, 3) 👉 [3, 1, 5] (random order, 3 elements) +xslice.Sample([]int{1, 2, 3}, 5) 👉 [2, 1, 3] (random order, all elements) +xslice.Sample([]int{1, 2, 3}, 0) 👉 []int{} +xslice.Sample([]int{}, 3) 👉 []int{} +``` + ## func [Shuffle]() @@ -925,6 +1000,24 @@ xslice.SumN(1, 2, 3) 👉 6 xslice.SumN() 👉 0 ``` + +## func [ToMap]() + +```go +func ToMap[T comparable, U any](in []T, f func(T) U) map[T]U +``` + +ToMap returns a map where keys are elements from the slice and values are the result of applying f to each element. If there are duplicate keys in the slice, only the last element with that key will be present in the map. + +EXAMPLE: + +``` +xslice.ToMap([]string{"a", "b", "c"}, func(s string) int { return len(s) }) 👉 map[a:1 b:1 c:1] +xslice.ToMap([]int{1, 2, 3}, func(i int) string { return fmt.Sprintf("num_%d", i) }) 👉 map[1:num_1 2:num_2 3:num_3] +xslice.ToMap([]int{1, 2, 1, 3}, func(i int) string { return fmt.Sprintf("val_%d", i) }) 👉 map[1:val_1 2:val_2 3:val_3] (note: key 1 has "val_1" from the last occurrence) +xslice.ToMap([]int{}, func(i int) string { return "" }) 👉 map[int]string{} +``` + ## func [Union]()