From d1889aa192d94cf20c818f784da029deb250348f Mon Sep 17 00:00:00 2001 From: Goreorto Date: Fri, 14 Nov 2025 05:24:38 -0600 Subject: [PATCH 1/2] Generic Map/Reduce functions --- sliceutil/sliceutil.go | 19 ++++++++--- sliceutil/sliceutil_test.go | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/sliceutil/sliceutil.go b/sliceutil/sliceutil.go index a4f1a40..abd01c2 100644 --- a/sliceutil/sliceutil.go +++ b/sliceutil/sliceutil.go @@ -162,8 +162,8 @@ func FilterEmpty[T comparable](t T) bool { } // Map returns a list where each item in list has been modified by fn -func Map[T comparable](tt []T, fn func(T) T) []T { - ret := make([]T, len(tt)) +func Map[InputTyp, OutputTyp any](tt []InputTyp, fn func(InputTyp) OutputTyp) []OutputTyp { + ret := make([]OutputTyp, len(tt)) for i, t := range tt { ret[i] = fn(t) } @@ -171,9 +171,18 @@ func Map[T comparable](tt []T, fn func(T) T) []T { return ret } -// InterfaceSliceTo converts []interface to any given slice. -// It will ~optimistically~ try to convert interface item to the dst item type -func InterfaceSliceTo(src []interface{}, dst interface{}) interface{} { +// Reduce returns an aggregation of tt +func Reduce[InputTyp, AccTyp any](tt []InputTyp, fn func(AccTyp, InputTyp) AccTyp, acc AccTyp) AccTyp { + for _, t := range tt { + acc = fn(acc, t) + } + + return acc +} + +// InterfaceSliceTo converts []any to any given slice. +// It will ~optimistically~ try to convert any item to the dst item type +func InterfaceSliceTo(src []any, dst any) any { dstt := reflect.TypeOf(dst) if dstt.Kind() != reflect.Slice { panic("`dst` is not an slice") diff --git a/sliceutil/sliceutil_test.go b/sliceutil/sliceutil_test.go index 6234e7a..fb8e7d2 100644 --- a/sliceutil/sliceutil_test.go +++ b/sliceutil/sliceutil_test.go @@ -576,6 +576,72 @@ func TestMap_String(t *testing.T) { } } +func TestMap_Int(t *testing.T) { + cases := []struct { + in []string + want []int + f func(string) int + }{ + { + in: []string{"1", "5", "2"}, + want: []int{1, 5, 2}, + f: func(s string) int { + n, _ := strconv.Atoi(s) // notlint: errcheck + return n + }, + }, + } + for _, tc := range cases { + t.Run("", func(t *testing.T) { + out := Map(tc.in, tc.f) + if !reflect.DeepEqual(tc.want, out) { + t.Errorf("\nout: %#v\nwant: %#v\n", out, tc.want) + } + }) + } +} + +func TestReduce_Int(t *testing.T) { + cases := []struct { + in []int + want int64 + f func(int64, int) int64 + }{ + { + in: []int{1, 5, 2}, + want: 8, + f: func(acc int64, i int) int64 { + return acc + int64(i) + }, + }, + } + for _, tc := range cases { + t.Run("", func(t *testing.T) { + out := Reduce(tc.in, tc.f, 0) + if !reflect.DeepEqual(tc.want, out) { + t.Errorf("\nout: %#v\nwant: %#v\n", out, tc.want) + } + }) + } +} + +func TestReduce_Struct(t *testing.T) { + type Acc struct { + acc int + } + + out := Reduce( + []int{1, 2, 3}, + func(out Acc, i int) Acc { + out.acc += i + return out + }, + Acc{}) + if out.acc != 6 { + t.Errorf("\nout: %#v\nwant: %#v\n", out.acc, 6) + } +} + func TestInterfaceSliceTo(t *testing.T) { { src := []interface{}{"1", "2", "3", "4", "5"} From 9538e75a6b1b787fedd24d04d32ac51e2c192b7f Mon Sep 17 00:00:00 2001 From: Goreorto Date: Fri, 14 Nov 2025 05:34:25 -0600 Subject: [PATCH 2/2] fix httputilx test --- httputilx/httputilx_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httputilx/httputilx_test.go b/httputilx/httputilx_test.go index 8bab181..d61b287 100644 --- a/httputilx/httputilx_test.go +++ b/httputilx/httputilx_test.go @@ -202,7 +202,7 @@ func TestFetch(t *testing.T) { cases := []struct { in, want, wantErr string }{ - {"http://example.com", "", ""}, + {"http://example.com", "