diff --git a/README.md b/README.md index 21317510..e504ac7e 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Supported helpers for maps: - [MapKeys](#mapkeys) - [MapValues](#mapvalues) - [MapToSlice](#maptoslice) +- [MapEntries](#mapentries) Supported math helpers: @@ -1077,6 +1078,19 @@ s := lo.MapToSlice(m, func(k int, v int64) string { [[play](https://go.dev/play/p/ZuiCZpDt6LD)] +### MapEntries + +Manipulates a map entries and transforms it to a map of another type. + +```go +m1 := map[string]int{"foo": 1, "bar": 2} + +m2 := lo.MapEntries(m1, func(k string, v int) (int, string) { +return v,k +}) +// map[int]string{1: "foo", 2: "bar"} +``` + ### Range / RangeFrom / RangeWithSteps Creates an array of numbers (positive and/or negative) progressing from start up to, but not including end. diff --git a/map.go b/map.go index 7915e37c..1526662a 100644 --- a/map.go +++ b/map.go @@ -200,3 +200,16 @@ func MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(K, V) R) [ return result } + +// MapEntries manipulates a map entries and transforms it to a map of another type. +func MapEntries[K comparable, V any, S comparable, R any](in map[K]V, iteratee func(K, V) (S, R)) map[S]R { + entries := Entries(in) + result := make(map[S]R, len(entries)) + + for _, entry := range entries { + k, v := iteratee(entry.Key, entry.Value) + result[k] = v + } + + return result +} diff --git a/map_test.go b/map_test.go index 3abb36b4..a340becd 100644 --- a/map_test.go +++ b/map_test.go @@ -199,3 +199,87 @@ func TestMapToSlice(t *testing.T) { is.ElementsMatch(result1, []string{"1_5", "2_6", "3_7", "4_8"}) is.ElementsMatch(result2, []string{"1", "2", "3", "4"}) } + +func mapEntriesTest[I any, O any](t *testing.T, in map[string]I, iteratee func(string, I) (string, O), expected map[string]O) { + is := assert.New(t) + result := MapEntries(in, iteratee) + is.Equal(result, expected) +} + +func TestMapEntries(t *testing.T) { + mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, int) { + return k, v + 1 + }, map[string]int{"foo": 2, "bar": 3}) + mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, string) { + return k, k + strconv.Itoa(v) + }, map[string]string{"foo": "foo1", "bar": "bar2"}) + mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, string) { + return k, strconv.Itoa(v) + k + }, map[string]string{"foo": "1foo", "bar": "2bar"}) + + // NoMutation + { + is := assert.New(t) + r1 := map[string]int{"foo": 1, "bar": 2} + MapEntries(r1, func(k string, v int) (string, string) { + return k, strconv.Itoa(v) + "!!" + }) + is.Equal(r1, map[string]int{"foo": 1, "bar": 2}) + } + // EmptyInput + { + mapEntriesTest(t, map[string]int{}, func(k string, v int) (string, string) { + return k, strconv.Itoa(v) + "!!" + }, map[string]string{}) + + mapEntriesTest(t, map[string]any{}, func(k string, v any) (string, any) { + return k, v + }, map[string]any{}) + } + // Identity + { + mapEntriesTest(t, map[string]int{"foo": 1, "bar": 2}, func(k string, v int) (string, int) { + return k, v + }, map[string]int{"foo": 1, "bar": 2}) + mapEntriesTest(t, map[string]any{"foo": 1, "bar": "2", "ccc": true}, func(k string, v any) (string, any) { + return k, v + }, map[string]any{"foo": 1, "bar": "2", "ccc": true}) + } + // ToConstantEntry + { + mapEntriesTest(t, map[string]any{"foo": 1, "bar": "2", "ccc": true}, func(k string, v any) (string, any) { + return "key", "value" + }, map[string]any{"key": "value"}) + mapEntriesTest(t, map[string]any{"foo": 1, "bar": "2", "ccc": true}, func(k string, v any) (string, any) { + return "b", 5 + }, map[string]any{"b": 5}) + } + + //// OverlappingKeys + //// because using range over map, the order is not guaranteed + //// this test is not deterministic + //{ + // mapEntriesTest(t, map[string]any{"foo": 1, "foo2": 2, "Foo": 2, "Foo2": "2", "bar": "2", "ccc": true}, func(k string, v any) (string, any) { + // return string(k[0]), v + // }, map[string]any{"F": "2", "b": "2", "c": true, "f": 2}) + // mapEntriesTest(t, map[string]string{"foo": "1", "foo2": "2", "Foo": "2", "Foo2": "2", "bar": "2", "ccc": "true"}, func(k string, v string) (string, string) { + // return v, k + // }, map[string]string{"1": "foo", "2": "bar", "true": "ccc"}) + //} + //NormalMappers + { + mapEntriesTest(t, map[string]string{"foo": "1", "foo2": "2", "Foo": "2", "Foo2": "2", "bar": "2", "ccc": "true"}, func(k string, v string) (string, string) { + return k, k + v + }, map[string]string{"Foo": "Foo2", "Foo2": "Foo22", "bar": "bar2", "ccc": "ccctrue", "foo": "foo1", "foo2": "foo22"}) + + mapEntriesTest(t, map[string]struct { + name string + age int + }{"1-11-1": {name: "foo", age: 1}, "2-22-2": {name: "bar", age: 2}}, func(k string, v struct { + name string + age int + }) (string, string) { + return v.name, k + }, map[string]string{"bar": "2-22-2", "foo": "1-11-1"}) + } +}