Skip to content

Commit

Permalink
feat: Add MapEntries function (#216)
Browse files Browse the repository at this point in the history
* feat: add MapEntries method

* fix: add out key type

* doc: add MapEntries doc

* test: change MapEntries test format

* fix: simplify MapEntries iteratee

* doc: fix MapEntries doc

Co-authored-by: Samuel Berthe <dev@samuel-berthe.fr>
  • Loading branch information
skimhugo and samber committed Oct 10, 2022
1 parent f44773e commit a28cc0b
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 0 deletions.
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -114,6 +114,7 @@ Supported helpers for maps:
- [MapKeys](#mapkeys)
- [MapValues](#mapvalues)
- [MapToSlice](#maptoslice)
- [MapEntries](#mapentries)

Supported math helpers:

Expand Down Expand Up @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions map.go
Expand Up @@ -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
}
84 changes: 84 additions & 0 deletions map_test.go
Expand Up @@ -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"})
}
}

0 comments on commit a28cc0b

Please sign in to comment.