Skip to content

Commit

Permalink
add MergeInPlace, MapMap, and MapSlice
Browse files Browse the repository at this point in the history
  • Loading branch information
zostay committed Jul 14, 2023
1 parent b8b4cba commit 1fa352a
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 7 deletions.
6 changes: 6 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
WIP TBD

* Added maps.MergeInPlace.
* Added slices.MapSlice and slices.MapMap.
* maps.Merge now allocates room equal to the size of all input maps in the output map.

v0.3.0 2023-07-13

* Added slices.FirstIndex and slices.GrepIndex.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ off-by-one errors, so the following ops are provided:
* `Unshift(slice, value)`
* `Concat(slice...)`
* `Map(slice, mapper)`
* `MapSlice(slice, mapper)`
* `MapMap(slice, mapper)`
* `Reduce(slice, reducer)`
* `Reductions(slice, reducer)`
* `ReduceAcc(slice, start, reducer)`
Expand Down Expand Up @@ -87,6 +89,7 @@ There are a lot of common map operations that are missing from the built-in
standard library:

* `Merge(...maps)`
* `MergeInPlace(base, ...maps)`
* `Diff(a, b)`

## Generic Sets
Expand Down
26 changes: 20 additions & 6 deletions maps/merge.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
package maps

import "github.com/zostay/go-std/generic"
import (
"github.com/zostay/go-std/generic"
)

// Merge maps into a single map. Keys in maps later in the list will overwrite
// keys found in earlier maps.
// Merge maps many maps into a single map. Keys in maps later in the list will
// overwrite keys found in earlier maps. The returned map will be a newly
// allocated map.
func Merge[K comparable, V any](maps ...map[K]V) map[K]V {
out := map[K]V{}
size := 0
for _, m := range maps {
size += len(m)
}
return MergeInPlace(make(map[K]V, size), maps...)
}

// MergeInPlace maps many maps into a single map. The base map will be updated
// to include all the pairs in the merge maps. Each map will be itereated in
// order with values in later maps overwriting those in early maps. When
// complete, the base map is returned.
func MergeInPlace[K comparable, V any](base map[K]V, merge ...map[K]V) map[K]V {
for _, m := range merge {
for k, v := range m {
out[k] = v
base[k] = v
}
}
return out
return base
}

// Diff returns three maps from two. The first map returned is a map of
Expand Down
17 changes: 17 additions & 0 deletions maps/merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ func TestMerge(t *testing.T) {
}, maps.Merge(a, b, c))
}

func TestMergeInPlace(t *testing.T) {
a := map[string]int{"a": 1, "b": 2}
b := map[string]int{"c": 3, "d": 4}
c := map[string]int{"a": 5, "b": 6, "c": 7}

assert.Equal(t, map[string]int{}, maps.MergeInPlace(map[string]int{}))
assert.Equal(t, map[string]int{
"a": 1, "b": 2,
}, maps.MergeInPlace(a))
assert.Equal(t, map[string]int{
"a": 1, "b": 2, "c": 3, "d": 4,
}, maps.MergeInPlace(a, b))
assert.Equal(t, map[string]int{
"a": 5, "b": 6, "c": 7, "d": 4,
}, maps.MergeInPlace(a, c))
}

func TestDiff(t *testing.T) {
a := map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}
b := map[string]int{"one": 5, "three": 6, "five": 7, "seven": 8}
Expand Down
29 changes: 28 additions & 1 deletion slices/map.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package slices

import "github.com/zostay/go-std/generic"
import (
"github.com/zostay/go-std/generic"
"github.com/zostay/go-std/maps"
)

// Map transforms the input into the output value using the mapper function.
func Map[In, Out any](in []In, mapper func(in In) Out) []Out {
Expand All @@ -11,6 +14,30 @@ func Map[In, Out any](in []In, mapper func(in In) Out) []Out {
return out
}

// MapSlice transforms the input into an output slice using the mapper function.
// A single input value may map to multiple or zero output values.
func MapSlice[In, Out any](in []In, mapper func(in In) []Out) []Out {
out := make([]Out, 0, len(in))
for _, t := range in {
out = append(out, mapper(t)...)
}
return out
}

// MapMap transforms the input into an output map using the mapper function.
// This will allocate a master map. The values returned by each call to mapper
// for each item in the input argument named in will be added to that map. If
// keys are added later that have already been added to the map being built, the
// new values will overwrite the old.
func MapMap[In any, K comparable, V any](in []In, mapper func(in In) map[K]V) map[K]V {
out := make(map[K]V, len(in))
for _, t := range in {
outMap := mapper(t)
out = maps.Merge(out, outMap)
}
return out
}

// Reduce collapses the input slice into a single value using the reducer
// function.
func Reduce[In, Out any](in []In, reducer func(u Out, t In) Out) Out {
Expand Down
29 changes: 29 additions & 0 deletions slices/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,35 @@ func TestMap(t *testing.T) {
assert.Equal(t, []string{}, ss)
}

func TestMapSlice(t *testing.T) {
halveTheOdds := func(i int) []float64 {
if i%2 == 1 {
return []float64{float64(i) / 2.0}
} else {
return []float64{}
}
}

in := []int{1, 2, 3}
ss := slices.MapSlice(in, halveTheOdds)
assert.Equal(t, []float64{0.5, 1.5}, ss)
ss = slices.MapSlice([]int{}, halveTheOdds)
assert.Equal(t, []float64{}, ss)
}

func TestMapMap(t *testing.T) {
stringToInt := func(in int) map[string]int {
return map[string]int{
strconv.Itoa(in): in,
}
}
in := []int{1, 2, 3}
ss := slices.MapMap(in, stringToInt)
assert.Equal(t, map[string]int{"1": 1, "2": 2, "3": 3}, ss)
ss = slices.MapMap([]int{}, stringToInt)
assert.Equal(t, map[string]int{}, ss)
}

func TestReduce(t *testing.T) {
in := []int{1, 2, 3}
s := slices.Reduce(in, subtract)
Expand Down

0 comments on commit 1fa352a

Please sign in to comment.