Skip to content

Commit

Permalink
Merge pull request #98 from Primetalk/feat-extract-set
Browse files Browse the repository at this point in the history
fix: move set functions to a separate package
  • Loading branch information
Primetalk committed Sep 30, 2022
2 parents 1dfb49a + d81ff52 commit 67a9f19
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 38 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,17 +462,16 @@ We can convert a slice to a set:

- `slice.ToSet[A comparable](as []A)(s Set[A])`

Where the `Set` type is defined as follows:
### Set utilities

The `Set` type is defined as follows:

- `type Set[A comparable] map[A]struct{}`

And we can perform some operations with sets:

- `slice.SetSize[A comparable](s Set[A]) int` - SetSize returns the size of the set.

And some with arbitrary maps:

- `slice.MapValues[K comparable, A any, B any](m map[K]A, f func(A)B) (res map[K]B)` - MapValues converts values in the map using the provided function.
- `set.Contains[A comparable](set map[A]struct{}) func (A) bool` - Contains creates a predicate that will check if an element is in this set.
- `set.SetSize[A comparable](s Set[A]) int` - SetSize returns the size of the set.

### Slices of numbers

Expand All @@ -499,6 +498,7 @@ Some helper functions to deal with `map[K]V`.
- `maps.Keys[K comparable, V any](m map[K]V) (keys []K)` - Keys returns keys of the map
- `maps.Merge[K comparable, V any](m1 map[K]V, m2 map[K]V, combine func(V, V) V) (m map[K]V)` - Merge combines two maps. Function `combine` is invoked when the same key is available in both maps.
- `maps.MapKeys[K1 comparable, V any, K2 comparable](m1 map[K1]V, f func(K1) K2, combine func(V, V) V) (m2 map[K2]V)` - MapKeys converts original keys to new keys.
- `map.MapValues[K comparable, A any, B any](m map[K]A, f func(A)B) (res map[K]B)` - MapValues converts values in the map using the provided function.

## Performance considerations

Expand Down
11 changes: 10 additions & 1 deletion maps/maps.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package maps

// Keys returns keys of the map
// Keys returns keys of the map.
func Keys[K comparable, V any](m map[K]V) (keys []K) {
for k := range m {
keys = append(keys, k)
Expand Down Expand Up @@ -39,3 +39,12 @@ func MapKeys[K1 comparable, V any, K2 comparable](m1 map[K1]V, f func(K1) K2, co
}
return
}

// MapValues converts values in the map using the provided function.
func MapValues[K comparable, A any, B any](m map[K]A, f func(A) B) (res map[K]B) {
res = make(map[K]B, len(m))
for k, a := range m {
res[k] = f(a)
}
return
}
17 changes: 17 additions & 0 deletions set/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package set

// Set is a map with a dummy value.
type Set[A comparable] map[A]struct{}

// Contains creates a predicate that will check if an element is in this set.
func Contains[A comparable](set Set[A]) func(A) bool {
return func(a A) (ok bool) {
_, ok = set[a]
return
}
}

// SetSize returns the size of the set.
func SetSize[A comparable](s Set[A]) int {
return len(s)
}
26 changes: 26 additions & 0 deletions set/set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package set_test

import (
"testing"

"github.com/primetalk/goio/set"
"github.com/primetalk/goio/slice"
"github.com/stretchr/testify/assert"
)

var nats10Values = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

func TestSet(t *testing.T) {
intsDuplicated := slice.FlatMap(nats10Values, func(i int) []int {
return slice.Map(nats10Values, func(j int) int { return i + j })
})
intsSet := slice.ToSet(intsDuplicated)
assert.Equal(t, 19, set.SetSize(intsSet))
}

func TestFilter(t *testing.T) {
s123 := slice.ToSet([]int{1, 2, 3})
assert.True(t, set.Contains(s123)(3), "3 \\in {1,2,3}")
sl13 := slice.Filter([]int{1, 3, 5, 7}, set.Contains(s123))
assert.ElementsMatch(t, []int{1, 3}, sl13)
}
27 changes: 7 additions & 20 deletions slice/slice.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Package slice provides common utility functions to Go slices.
package slice

import "github.com/primetalk/goio/option"
import (
"github.com/primetalk/goio/maps"
"github.com/primetalk/goio/option"
"github.com/primetalk/goio/set"
)

// Map converts all values of a slice using the provided function.
func Map[A any, B any](as []A, f func(A) B) (bs []B) {
Expand Down Expand Up @@ -85,23 +89,15 @@ func AppendAll[A any](ass ...[]A) (aas []A) {
return Flatten(ass)
}

// Set is a way to represent sets in Go.
type Set[A comparable] map[A]struct{}

// ToSet converts a slice to a set.
func ToSet[A comparable](as []A) (s Set[A]) {
func ToSet[A comparable](as []A) (s set.Set[A]) {
s = make(map[A]struct{}, len(as))
for _, a := range as {
s[a] = struct{}{}
}
return
}

// SetSize returns the size of the set.
func SetSize[A comparable](s Set[A]) int {
return len(s)
}

// GroupBy groups elements by a function that returns a key.
func GroupBy[A any, K comparable](as []A, f func(A) K) (res map[K][]A) {
res = make(map[K][]A, len(as))
Expand All @@ -121,7 +117,7 @@ func GroupBy[A any, K comparable](as []A, f func(A) K) (res map[K][]A) {
// GroupByMap is a convenience function that groups and then maps the subslices.
func GroupByMap[A any, K comparable, B any](as []A, f func(A) K, g func([]A) B) (res map[K]B) {
intermediate := GroupBy(as, f)
return MapValues(intermediate, g)
return maps.MapValues(intermediate, g)
}

// GroupByMapCount for each key counts how often it is seen.
Expand Down Expand Up @@ -155,15 +151,6 @@ func Grouped[A any](as []A, size int) (res [][]A) {
return Sliding(as, size, size)
}

// MapValues converts values in the map using the provided function.
func MapValues[K comparable, A any, B any](m map[K]A, f func(A) B) (res map[K]B) {
res = make(map[K]B, len(m))
for k, a := range m {
res[k] = f(a)
}
return
}

// Len returns the length of the slice.
// This is a normal function that can be passed around unlike the built-in `len`.
func Len[A any](as []A) int {
Expand Down
8 changes: 0 additions & 8 deletions slice/slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@ func TestFlatten(t *testing.T) {
assert.Equal(t, float32(55+55*2), slice.Sum(floats))
}

func TestSet(t *testing.T) {
intsDuplicated := slice.FlatMap(nats10Values, func(i int) []int {
return slice.Map(nats10Values, func(j int) int { return i + j })
})
intsSet := slice.ToSet(intsDuplicated)
assert.Equal(t, 19, slice.SetSize(intsSet))
}

func TestGroupBy(t *testing.T) {
intsDuplicated := slice.FlatMap(nats10Values, func(i int) []int {
return slice.Map(nats10Values, func(j int) int { return i + j })
Expand Down
7 changes: 4 additions & 3 deletions stream/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/primetalk/goio/io"
"github.com/primetalk/goio/set"
"github.com/primetalk/goio/slice"
"github.com/primetalk/goio/stream"
"github.com/stretchr/testify/assert"
Expand All @@ -30,7 +31,7 @@ func TestPool(t *testing.T) {
start := time.Now()
results, err := io.UnsafeRunSync(resultsIO)
assert.NoError(t, err)
assert.Equal(t, 100, slice.SetSize(slice.ToSet(results)))
assert.Equal(t, 100, set.SetSize(slice.ToSet(results)))
assert.WithinDuration(t, start, time.Now(), 200*time.Millisecond)
}

Expand All @@ -53,7 +54,7 @@ func TestExecutionContext(t *testing.T) {
start := time.Now()
results, err := io.UnsafeRunSync(resultsIO)
assert.NoError(t, err)
assert.Equal(t, taskCount, slice.SetSize(slice.ToSet(results)))
assert.Equal(t, taskCount, set.SetSize(slice.ToSet(results)))
required_duration := 10*taskCount/concurrency + 50
assert.WithinDuration(t, start, time.Now(), time.Duration(required_duration)*time.Millisecond)
}
Expand Down Expand Up @@ -99,7 +100,7 @@ func TestThroughExecutionContextUnordered(t *testing.T) {

start := time.Now()
results := UnsafeIO(t, resultsIO)
assert.Equal(t, taskCount, slice.SetSize(slice.ToSet(results)))
assert.Equal(t, taskCount, set.SetSize(slice.ToSet(results)))
required_duration := durMs * taskCount / concurrency * 2
assert.WithinDuration(t, start, time.Now(), time.Duration(required_duration)*time.Millisecond)
}

0 comments on commit 67a9f19

Please sign in to comment.