Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to chain multiple RxJava's groupBy() methods such as groupBy().groupBy()

Given input:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Group the numbers by odd or even and then by less than or greater than 5.

Expected output:

[[1, 3, 5], [2, 4], [6, 8, 10], [7, 9]]

The order of the output isn't restricted.

I'm now using the following approach:

Observable.range(1, 10)
    .groupBy(n -> n % 2 == 0)
    .flatMap((GroupedObservable<Boolean, Integer> g) -> {
        return Observable.just(g).flatMap(ObservableUtils.<Boolean, Integer>flatGroup()).groupBy(n -> n > 5);
    })
    .subscribe((final GroupedObservable<Boolean, Integer> g) -> {
        Observable.just(g).flatMap(ObservableUtils.<Boolean, Integer>flatGroup()).forEach(n -> println(g + ": " + n));
    });

Note that ObservableUtils is written by me to simplify the code.

But I'm not satisfied with it because it still not short enough to simply indicate the goal only.

What I expected is like the following:

Observable.range(1, 10)
    .groupBy(n -> n % 2 == 0)
    .groupBy(n -> n > 5)
    .subscribe(...);

For now I can only shrink it to:

Observable.range(1, 10)
    .lift(new OperatorGroupByGroup(n -> n % 2 == 0))
    .lift(new OperatorGroupByGroup(n -> n > 5))
    .subscribe(...);

I still have to write the OperatorGroupByGroup class which is a little bit complex. Any suggestion for improving?

like image 526
changyuheng Avatar asked Jan 30 '15 07:01

changyuheng


3 Answers

Try to do it in this way.

Observables.range(1,10)
  .groupBy( n -> n % 2 == 0)
  .flatMap( grp -> grp.groupBy( n -> n > 5).flatMap( grp2 -> grp2.toList()))
  .subscribe(...)
like image 169
QFox Avatar answered Nov 06 '22 16:11

QFox


I wrote a sample for OperatorGroupByGroup that based on OperatorGroupBy:

https://github.com/yongjhih/RxJava-GroupByTest

Usage:

git clone https://github.com/yongjhih/RxJava-GroupByTest.git
./gradlew execute

But I modified testing code due to my OperatorGroupByGroup implementation:

    Observable.range(1, 10)
    .lift(new OperatorGroupByGroup<Integer, Boolean, Integer>(n -> n % 2 == 0))
    .lift(new OperatorGroupByGroup<GroupedObservable<Boolean, Integer>, Boolean, Integer>(n -> n > 5))
    .subscribe((final GroupedObservable<Boolean, Integer> g) -> {
        Observable.just(g).flatMap(ObservableUtils.<Boolean, Integer>flatGroup()).forEach(n -> println(g + ": " + n));
    });

I think somebody would do better.

like image 37
Andrew Chen Avatar answered Nov 06 '22 15:11

Andrew Chen


I have two suggestions that I think are both pretty concise and elegant.

First:

Observable.range(1, 10)
    .groupBy(n -> n % 2 == 0)
    .flatMap(g -> g.groupBy(n -> n > 5))
    .subscribe(...);

Which is almost as good as your expectation, just one additional .flatMap(). The only problem? You lose the first key, but I'm not sure you're using those anyway.

Second needs declaring a simple Key class that can hold results of both your conditions and have proper equals() implementation. In other words a Pair. Then you can do:

Observable.range(1, 10)
    .groupBy(n -> new Key(n % 2 == 0, n > 5))
    .subscribe(...);

This has the disadvantage of being less composable, as you have both conditions in the same .groupBy() call instead of chained. The advantage is that you can use a combined key that holds results of both your conditions if you need them.

like image 36
Marcin Koziński Avatar answered Nov 06 '22 16:11

Marcin Koziński