Skip to content

Commit d6b151a

Browse files
authored
GroupByDynamic operator
1 parent b8221f3 commit d6b151a

9 files changed

+173
-26
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ How to use the [assert API](doc/assert.md) to write unit tests while using RxGo.
438438
* [Buffer](doc/buffer.md) — periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time
439439
* [FlatMap](doc/flatmap.md) — transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
440440
* [GroupBy](doc/groupby.md) — divide an Observable into a set of Observables that each emit a different group of items from the original Observable, organized by key
441+
* [GroupByDynamic](doc/groupbydynamic.md) — divide an Observable into a dynamic set of Observables that each emit GroupedObservables from the original Observable, organized by key
441442
* [Map](doc/map.md) — transform the items emitted by an Observable by applying a function to each item
442443
* [Marshal](doc/marshal.md) — transform the items emitted by an Observable by applying a marshalling function to each item
443444
* [Scan](doc/scan.md) — apply a function to each item emitted by an Observable, sequentially, and emit each successive value

doc/groupby.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
Divide an Observable into a set of Observables that each emit a different group of items from the original Observable, organized by key.
66

7+
It requires to pass the length of the set. If we require a dynamic set, we need to use [GroupByDynamic](groupbydynamic.md) instead.
8+
79
![](http://reactivex.io/documentation/operators/images/groupBy.c.png)
810

911
## Example

doc/groupbydynamic.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# GroupBy Operator
2+
3+
## Overview
4+
5+
Divides an Observable into a dynamic set of Observables that each emit GroupedObservable from the original Observable, organized by key.
6+
7+
`GroupByDyDynamic` differs from [GroupBy](groupby.md) in the sense that it does not require to pass the set length.
8+
9+
![](http://reactivex.io/documentation/operators/images/groupBy.c.png)
10+
11+
## Example
12+
13+
```go
14+
count := 3
15+
observable := rxgo.Range(0, 10).GroupByDynamic(func(item rxgo.Item) int {
16+
return item.V.(int) % count
17+
}, rxgo.WithBufferedChannel(10))
18+
19+
for i := range observable.Observe() {
20+
groupedObservable := i.V.(rxgo.GroupedObservable)
21+
fmt.Printf("New observable: %d\n", groupedObservable.Key)
22+
23+
for i := range groupedObservable.Observe() {
24+
fmt.Printf("item: %v\n", i.V)
25+
}
26+
}
27+
```
28+
29+
Output:
30+
31+
```
32+
New observable: 0
33+
item: 0
34+
item: 3
35+
item: 6
36+
item: 9
37+
New observable: 1
38+
item: 1
39+
item: 4
40+
item: 7
41+
item: 10
42+
New observable: 2
43+
item: 2
44+
item: 5
45+
item: 8
46+
```
47+
48+
## Options
49+
50+
* [WithBufferedChannel](options.md#withbufferedchannel)
51+
52+
* [WithContext](options.md#withcontext)
53+
54+
* [WithObservationStrategy](options.md#withobservationstrategy)
55+
56+
* [WithErrorStrategy](options.md#witherrorstrategy)
57+
58+
* [WithPublishStrategy](options.md#withpublishstrategy)

go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
44
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
66
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
7+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
78
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
89
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
10+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
911
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1012
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1113
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -31,6 +33,7 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
3133
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
3234
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
3335
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
36+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
3437
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3538
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
3639
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

observable.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Package rxgo is the main RxGo package.
12
package rxgo
23

34
import (
@@ -44,6 +45,7 @@ type Observable interface {
4445
FlatMap(apply ItemToObservable, opts ...Option) Observable
4546
ForEach(nextFunc NextFunc, errFunc ErrFunc, completedFunc CompletedFunc, opts ...Option) Disposed
4647
GroupBy(length int, distribution func(Item) int, opts ...Option) Observable
48+
GroupByDynamic(distribution func(Item) int, opts ...Option) Observable
4749
IgnoreElements(opts ...Option) Observable
4850
Join(joiner Func2, right Observable, timeExtractor func(interface{}) time.Time, window Duration, opts ...Option) Observable
4951
Last(opts ...Option) OptionalSingle
@@ -179,7 +181,7 @@ func observable(iterable Iterable, operatorFactory func() operator, forceSeq, by
179181
runParallel(ctx, next, observe, operatorFactory, bypassGather, option, mergedOptions...)
180182
}
181183
}()
182-
runFirstItem(ctx, f, firstItemIDCh, observe, next, operatorFactory, bypassGather, option, mergedOptions...)
184+
runFirstItem(ctx, f, firstItemIDCh, observe, next, operatorFactory, option, mergedOptions...)
183185
return next
184186
}),
185187
}
@@ -383,7 +385,7 @@ func runParallel(ctx context.Context, next chan Item, observe <-chan Item, opera
383385
}()
384386
}
385387

386-
func runFirstItem(ctx context.Context, f func(interface{}) int, notif chan Item, observe <-chan Item, next chan Item, operatorFactory func() operator, bypassGather bool, option Option, opts ...Option) {
388+
func runFirstItem(ctx context.Context, f func(interface{}) int, notif chan Item, observe <-chan Item, next chan Item, operatorFactory func() operator, option Option, opts ...Option) {
387389
go func() {
388390
op := operatorFactory()
389391
stopped := false

observable_operator.go

+54-4
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,57 @@ func (o *ObservableImpl) GroupBy(length int, distribution func(Item) int, opts .
13201320
}
13211321
}
13221322

1323+
// GroupedObservable is the observable type emitted by the GroupByDynamic operator.
1324+
type GroupedObservable struct {
1325+
Observable
1326+
// Key is the distribution key
1327+
Key int
1328+
}
1329+
1330+
// GroupByDynamic divides an Observable into a dynamic set of Observables that each emit GroupedObservable from the original Observable, organized by key.
1331+
func (o *ObservableImpl) GroupByDynamic(distribution func(Item) int, opts ...Option) Observable {
1332+
option := parseOptions(opts...)
1333+
next := option.buildChannel()
1334+
ctx := option.buildContext()
1335+
chs := make(map[int]chan Item)
1336+
1337+
go func() {
1338+
observe := o.Observe(opts...)
1339+
loop:
1340+
for {
1341+
select {
1342+
case <-ctx.Done():
1343+
break loop
1344+
case i, ok := <-observe:
1345+
if !ok {
1346+
break loop
1347+
}
1348+
idx := distribution(i)
1349+
ch, contains := chs[idx]
1350+
if !contains {
1351+
ch = option.buildChannel()
1352+
chs[idx] = ch
1353+
Of(GroupedObservable{
1354+
Observable: &ObservableImpl{
1355+
iterable: newChannelIterable(ch),
1356+
},
1357+
Key: idx,
1358+
}).SendContext(ctx, next)
1359+
}
1360+
i.SendContext(ctx, ch)
1361+
}
1362+
}
1363+
for _, ch := range chs {
1364+
close(ch)
1365+
}
1366+
close(next)
1367+
}()
1368+
1369+
return &ObservableImpl{
1370+
iterable: newChannelIterable(next),
1371+
}
1372+
}
1373+
13231374
// Last returns a new Observable which emit only last item.
13241375
// Cannot be run in parallel.
13251376
func (o *ObservableImpl) Last(opts ...Option) OptionalSingle {
@@ -1682,7 +1733,6 @@ func (op *repeatOperator) end(ctx context.Context, dst chan<- Item) {
16821733
for {
16831734
select {
16841735
default:
1685-
break
16861736
case <-ctx.Done():
16871737
return
16881738
}
@@ -2241,7 +2291,7 @@ func (o *ObservableImpl) StartWith(iterable Iterable, opts ...Option) Observable
22412291

22422292
// SumFloat32 calculates the average of float32 emitted by an Observable and emits a float32.
22432293
func (o *ObservableImpl) SumFloat32(opts ...Option) OptionalSingle {
2244-
return o.Reduce(func(_ context.Context, acc interface{}, elem interface{}) (interface{}, error) {
2294+
return o.Reduce(func(_ context.Context, acc, elem interface{}) (interface{}, error) {
22452295
if acc == nil {
22462296
acc = float32(0)
22472297
}
@@ -2267,7 +2317,7 @@ func (o *ObservableImpl) SumFloat32(opts ...Option) OptionalSingle {
22672317

22682318
// SumFloat64 calculates the average of float64 emitted by an Observable and emits a float64.
22692319
func (o *ObservableImpl) SumFloat64(opts ...Option) OptionalSingle {
2270-
return o.Reduce(func(_ context.Context, acc interface{}, elem interface{}) (interface{}, error) {
2320+
return o.Reduce(func(_ context.Context, acc, elem interface{}) (interface{}, error) {
22712321
if acc == nil {
22722322
acc = float64(0)
22732323
}
@@ -2295,7 +2345,7 @@ func (o *ObservableImpl) SumFloat64(opts ...Option) OptionalSingle {
22952345

22962346
// SumInt64 calculates the average of integers emitted by an Observable and emits an int64.
22972347
func (o *ObservableImpl) SumInt64(opts ...Option) OptionalSingle {
2298-
return o.Reduce(func(_ context.Context, acc interface{}, elem interface{}) (interface{}, error) {
2348+
return o.Reduce(func(_ context.Context, acc, elem interface{}) (interface{}, error) {
22992349
if acc == nil {
23002350
acc = int64(0)
23012351
}

observable_operator_bench_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func Benchmark_Reduce_Sequential(b *testing.B) {
6363
for i := 0; i < b.N; i++ {
6464
b.StopTimer()
6565
obs := Range(0, benchNumberOfElementsSmall, WithBufferedChannel(benchChannelCap)).
66-
Reduce(func(_ context.Context, acc interface{}, elem interface{}) (interface{}, error) {
66+
Reduce(func(_ context.Context, acc, elem interface{}) (interface{}, error) {
6767
// Simulate a blocking IO call
6868
time.Sleep(5 * time.Millisecond)
6969
if a, ok := acc.(int); ok {
@@ -84,7 +84,7 @@ func Benchmark_Reduce_Parallel(b *testing.B) {
8484
for i := 0; i < b.N; i++ {
8585
b.StopTimer()
8686
obs := Range(0, benchNumberOfElementsSmall, WithBufferedChannel(benchChannelCap)).
87-
Reduce(func(_ context.Context, acc interface{}, elem interface{}) (interface{}, error) {
87+
Reduce(func(_ context.Context, acc, elem interface{}) (interface{}, error) {
8888
// Simulate a blocking IO call
8989
time.Sleep(5 * time.Millisecond)
9090
if a, ok := acc.(int); ok {

0 commit comments

Comments
 (0)