我有一个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的解决方案也非常受欢迎。
答案 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());
}
}
}
这是它的工作原理:
答案 2 :(得分:0)
在查看akarnokd's和Dave的答案后,我通过实施名为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)
假设我们有一个source
流string
和一个函数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);
}));
当缓冲区中的第一项和我们考虑添加到缓冲区的当前项没有相同的键时,每个缓冲区都会结束。