Rx - 按条件

时间:2015-09-17 07:58:30

标签: java c# system.reactive reactive-programming rx-java

我有一个RX生产者,它创建一个像这样的字符串流(实际流的简化版本):

A1 A2 A3 B1 B2 C1 C2 C3 C4 C5 C6....

流是无穷无尽的,但有序。因此,以A开头的字符串用完后,B就会启动。当B用完时,C会启动...当Z用完时,我们会移至AA1等。A的数量不明, B等等,但每个字母通常为10-30个实例。

我正在寻找一种方法将此流划分为所有A的块:A1 A2 A3,所有B:B1 B2,所有C:C1 C2 C3 C4 C5 C6等。每个块可以是一个可观察的(我将变成一个列表)或只是一个列表。

我使用RxJava尝试了几种不同的方法,所有方法都失败了。其中不起作用的是:

  • 分组依据:由于流是无穷无尽的,因此每个字母的observable都无法完成。所以当A的用完和B的开始时,A的Observable没有完成。因此,观察数量越来越多。

  • 具有distinctUntilChanged的窗口/缓冲区 - 我在原始流上使用“distinctUntilChanged”来输出每个组的第一个项目(第一个A,第一个B等)。然后我将该流用作window的输入或“缓冲”运算符,以用作窗口/缓冲区之间的边界。这不起作用,我得到的只是空名单。

使用RX的正确解决方案是什么? 我更喜欢Java解决方案,但是其他RX实现中可以轻松转换为Java的解决方案也非常受欢迎。

4 个答案:

答案 0 :(得分:5)

您可以使用rxjava-extras .toListWhile

Observable<String> source = 
    Observable.just("A1", "A2", "A3", "B1", "B2", "B3", "C1", "D1");
source.compose(Transformers.<String> toListWhile(
            (list, t) -> list.isEmpty() 
                         || list.get(0).charAt(0) == t.charAt(0)))
      .forEach(System.out::println);

它完成了@akarnokd所做的事情,并经过单元测试。

答案 1 :(得分:4)

以下是我解决此问题的方法:

    public class ScannerDemo {

    public static void main(String[] args) {

        String s = "Hello World! 3 + 3.0 = 6.0 true ";
        Long l = 13964599874l;
        s = s + l;

        // create a new scanner with the specified String Object
        Scanner scanner = new Scanner(s);

        // find the next long token and print it
        // loop for the whole scanner
        while (scanner.hasNext()) {

            // if the next is a long, print found and the long
            // LINE 1
            if (scanner.hasNextLong()) {
                System.out.println("Found :" + scanner.nextLong());
            }
            // if no long is found, print "Not Found:" and the token
            // LINE 2
            System.out.println("Not Found :" + scanner.next());

        }
    }
}

这是它的工作原理:

  • 我使用defer是因为我们需要一个每用户缓冲区,而不是一个全局缓冲区
  • 如果源恰好是有限的,我将缓冲与最后一个缓冲区的发射连接起来
  • 我使用concatMap并添加到缓冲区,直到键发生变化,直到那时,我发出空的Observables。密钥更改后,我会发出缓冲区的内容并启动一个新内容。

答案 2 :(得分:0)

在查看akarnokd'sDave的答案后,我通过实施名为BufferWhile的自定义Rx运算符来提出自己的解决方案。它似乎和其他解决方案一样有效(如果我错了,有人请纠正我),但它似乎更直接:

public class RxBufferWhileOperator<T, U> implements Operator<List<T>, T>{

    private final Func1<? super T, ? extends U> keyGenerator;

    public RxBufferWhileOperator(Func1<? super T, ? extends U> keyGenerator) {
        this.keyGenerator = keyGenerator;
    }

    @Override
    public Subscriber<? super T> call(final Subscriber<? super List<T>> s) {
        return new Subscriber<T>(s) {

            private ArrayList<T> buffer = null;
            private U currentKey = null;

            @Override
            public void onCompleted() {
                submitAndClearBuffer();
                s.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                submitAndClearBuffer(); //Optional, remove if submitting partial buffers doesn't make sense in your case
                s.onError(e);
            }

            @Override
            public void onNext(T t) {
                if (currentKey == null || !currentKey.equals(keyGenerator.call(t))) {
                    currentKey = keyGenerator.call(t);
                    submitAndClearBuffer();
                    buffer.add(t);
                } else {
                    buffer.add(t);
                    request(1); // Request additional T since we "swallowed" the incoming result without calling subsequent subscribers
                }
            }

            private void submitAndClearBuffer() {
                if (buffer != null && buffer.size() > 0) {
                    s.onNext(buffer);
                }
                buffer = new ArrayList<>();
            }
        };
    }
}

我可以使用lift在原始observable上应用此运算符,并获取一个发出字符串列表的observable。

答案 3 :(得分:0)

假设我们有一个sourcestring和一个函数key,它会为每个string提取密钥,例如:

IObservable<string> source = ...;
Func<string, string> key = s => new string(s.TakeWhile(char.IsLetter).ToArray());

然后我们可以将Buffer与自定义结束选择器一起使用。

var query = source.Publish(o => o.Buffer(() =>
{
    var keys = o.Select(key);
    return Observable
        .CombineLatest(
            keys.Take(1),
            keys.Skip(1),
            (a, b) => a != b)
        .Where(x => x);
}));

当缓冲区中的第一项和我们考虑添加到缓冲区的当前项没有相同的键时,每个缓冲区都会结束。