RxJava - 创建GroupByUntilChanged运算符?

时间:2016-09-06 17:09:34

标签: rx-java reactive-programming

我正在尝试创建自己的名为groupByUntilChanged()的RxJava运算符。它的作用类似于groupBy(),但假定排放量是基于密钥的顺序。因此,当键值发生变化时,它会完成GroupedObservable并继续前进到下一个GroupedObservable以获得下一个键。

到目前为止,这是我的工作。我使用每个String的第一个字母作为关键字。这似乎工作正常,直到我抛出一个" A"最后String

public class Test {
    public static void main(String[] args) {
        Observable<String> items =
                Observable.just("Alpha","Adam","Apple","Beta","Brick","Bridge","Bat","Gamma","Gorilla","Axe");

        Func1<String,String> keyExtractor = s -> s.substring(0,1);

        items.compose(orderedGroupBy(keyExtractor))
                .flatMap(grp -> grp.toList())
                .subscribe(System.out::println);

    }

    public static <T,K> Observable.Transformer<T,GroupedObservable<K,T>> orderedGroupBy(Func1<T,K> keySelector) {
        return obs -> obs.groupBy(keySelector)
                .map(grp ->
                      GroupedObservable.from(grp.getKey(),grp.takeWhile(t -> keySelector.call(t).equals(grp.getKey())))
                );
    }
}

我得到了这个输出:

[Alpha, Adam, Apple, Axe]
[Beta, Brick, Bridge, Bat]
[Gamma, Gorilla]

当我真的想要这个时:

[Alpha, Adam, Apple]
[Beta, Brick, Bridge, Bat]
[Gamma, Gorilla]
[Axe]

如果密钥发生变化,我可以做一些有序的排放onComplete() GroupedObservable

2 个答案:

答案 0 :(得分:1)

GroupedObservable运算符上协调完成groupBy是一件非常棘手的事情(尽管在您的情况下同步处理可能会启用其他解决方案)。因此,groupBy有一个重载,允许您指定mapFactory。如果你在CacheBuilder重载上按照javadoc使用Guava groupBy,那么你可以为地图指定最大大小为1并获得所需的行为结果:

Func1<String,String> keySelectory = s -> s.substring(0,1);
Func1<String,String> elementSelectory = s -> s;
Func1<Action1<String>, Map<String, String>> mapFactory =
   action -> 
     CacheBuilder.newBuilder()
       .maximumSize(1)
       .removalListener(notification ->
          action.call(notification.getKey()))
     .<String, String> build().asMap();
items.groupBy(keySelector, elementSelector, mapFactory)
            .flatMap(grp -> grp.toList())
            .subscribe(System.out::println);

答案 1 :(得分:1)

这些问题最好通过自定义运算符解决 - 依赖于状态的转换(此处,已处理的项及其键)不是反应方法的最佳目标,通常需要samtools view -1 -bS。对于内置运算符,冷可观察量的(详细)解决方案可以如下:

Subjects

测试
public static <K, T> Observable.Transformer<T, GroupedObservable<K, T>> groupByUntilChanged(Func1<? super T, ? extends K> keyExtractor) {
    return observable -> groupByUntilChanged(observable, keyExtractor);
}

static <K, T> Observable<GroupedObservable<K, T>> groupByUntilChanged(Observable<T> itemsStream,
                                                                      Func1<? super T, ? extends K> keyExtractor) {

    /*keys according to keyExtractor */
    Observable<K> keysStream = itemsStream.distinctUntilChanged(keyExtractor).map(keyExtractor);
    Func1<K, Func1<T, Boolean>> itemByKey = key -> item -> key.equals(keyExtractor.call(item));

    /*predicate functions to match sub stream specified by key extractor*/
    Observable<Func1<T, Boolean>> itemsByKeyFuncStream = keysStream.map(itemByKey);

    /*stream chunks are processed sequentially, some kind of state bookkeeping is needed: let it be the number of
      already processed items */
    BehaviorSubject<Integer> skipCountStream = BehaviorSubject.create(0);

    Observable<GroupedObservable<K, T>> groupByUntilChangedStream = itemsByKeyFuncStream.concatMap(takeF ->

            /*skip already processed items, take while key extractor predicate is true*/
            skipCountStream.first().map(count -> itemsStream.skip(count).takeWhile(takeF)))

            .doOnNext(subItems ->
                    /*once group is ready, increase number of already processed items*/
                    subItems.count()
                            .flatMap(subItemsSize -> skipCountStream.first().map(allSize -> allSize + subItemsSize))
                            .subscribe(skipCountStream::onNext))
             /*convert to GroupedObservable*/
            .zipWith(keysStream, (obs, key) -> GroupedObservable.from(key, obs));

    return groupByUntilChangedStream;
}

结果是

Observable<String> itemsStream =
            Observable.just("Alpha", "Adam", "Apple", "Beta", "Brick", "Bridge", "Bat", "Gamma", "Gorilla", "Axe");
    Func1<String, String> keyExtractor = s -> s.substring(0, 1);
    Observable<GroupedObservable<String, String>> groupByUntilChangedStream = itemsStream.compose(groupByUntilChanged(keyExtractor));

    /*groups starting with "A"*/
    groupByUntilChangedStream
            .filter(grouped -> grouped.getKey().equals("A"))
            .flatMap(Observable::toList)
            .defaultIfEmpty(Collections.emptyList())
            .subscribe(System.out::println);