Johann Chang
Johann Chang

Reputation: 1391

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?

Upvotes: 11

Views: 9161

Answers (3)

Marcin Koziński
Marcin Koziński

Reputation: 11044

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.

Upvotes: 1

QFox
QFox

Reputation: 83

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(...)

Upvotes: 4

Andrew Chen
Andrew Chen

Reputation: 447

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.

Upvotes: 2

Related Questions