在奋斗了几天之后,在看起来很简单的任务上,我来找你们:)
想法很简单。我有两个流/可观察对象,“左”和“右”。
我希望项目从“右”缓存/收集/聚合到“左”中的“当前”项目。
因此,“左侧”中的每个项目都定义了一个新的“窗口”,而所有“右侧”项目都将绑定到该窗口,直到发出新的“左侧”项目为止。因此,可视化:
任务:
'left'::-A------B--C----|
'正确':|-1-2-3 -4-5-6---|
'result':|--------x---y----z | (Pair<Left, List<Right>>
)
其中: A,1 ; B,4 (so x); C (所以y)是在同一时间发射的
所以:x = Pair(A,[1,2,3]),y = Pair(B,[4,5])
And:Â'right'和'result'完成/终止于'left'发生
所以:z = Pair(C,[6])-由于“左”完成而发出
----
编辑2-最终解决方案!
为了将“正确”的项目与下一个“左侧”而不是先前的项目聚合在一起,我将代码更改为以下更短/更简单的项目:
fun <L, R> Observable<L>.rightGroupJoin(right: Observable<R>): Observable<Pair<L, List<R>>> {
return this.share().run {
zipWith(right.buffer(this), BiFunction { left, rightList ->
Pair(left, rightList)
})
}
}
编辑1-初始解决方案!
从下面的@Mark的(接受的)答案中摘录,这就是我的想法。
它被分成一些较小的方法,因为我也根据需要执行multiRightGroupJoin()
来加入尽可能多的(正确的)流。
fun <T, R> Observable<T>.rightGroupJoin(right: Observable<R>): Observable<Pair<T, List<R>>> {
return this.share().let { thisObservable -> //use 'share' to avoid multi-subscription complications, e.g. multi calls to **preceding** doOnComplete
thisObservable.flatMapSingle { t -> //treat each 'left' as a Single
bufferRightOnSingleLeft(thisObservable, t, right)
}
}
}
位置:
private fun <T, R> bufferRightOnSingleLeft(left: Observable<*>, leftSingleItem: T, right: Observable<R>)
: Single<Pair<T, MutableList<R>>> {
return right.buffer(left) //buffer 'right' until 'left' onNext() (for each 'left' Single)
.map { Pair(leftSingleItem, it) }
.first(Pair(leftSingleItem, emptyList())) //should be only 1 (list). THINK firstOrError
}
----
我到目前为止所得到的
经过大量阅读并了解开箱即用的解决方案后,我决定使用groupJoin
,主要使用this link,例如:(许多问题和地方在这里改进,不要使用此代码)
private fun <T, R> Observable<T>.rightGroupJoin(right: Observable<R>): Observable<Pair<T, List<R>>> {
var thisCompleted = false //THINK is it possible to make the groupJoin complete on the left(this)'s onComplete automatically?
val thisObservable = this.doOnComplete { thisCompleted = true }
.share() //avoid weird side-effects of multiple onSubscribe calls
//join/attach 'right/other' stream to windows (buffers), starting and ending on each 'this/left' onNext
return thisObservable.groupJoin(
//bind 'right/other' stream to 'this/left'
right.takeUntil { thisCompleted }//have an onComplete rule THINK add share() at the end?
//define when windows start/end ('this/left' onNext opens new window and closes prev)
, Function<T, ObservableSource<T>> { thisObservable }
//define 'right/other' stream to have no windows/intervals/aggregations by itself
// -> immediately bind each emitted item to a 'current' window(T) above
, Function<R, ObservableSource<R>> { Observable.empty() }
//collect the whole 'right' stream in 'current' ('left') window
, BiFunction<T, Observable<R>, Single<Pair<T, List<R>>>> { t, rObs ->
rObs.collect({ mutableListOf<R>() }) { acc, value ->
acc.add(value)
}.map { Pair(t, it.toList()) }
}).mergeAllSingles()
}
我还使用类似的用法来创建timedBuffer()
-与buffer(timeout)
相同,但是在每个缓冲区(List
)上都有时间戳,以了解何时开始。基本上是通过在Observable.interval(timeout)
(与“ left”)上运行相同的代码
问题/问题(从最简单到最困难的问题)
这种用法似乎弄乱了rx的顺序。查看代码并在下面打印:
leftObservable
.doOnComplete {
log("doOnComplete - before join")
}
.doOnComplete {
log("doOnComplete 2 - before join")
}
.rightGroupJoin(rightObservable)
.doOnComplete {
log("doOnComplete - after join")
}
打印(有时!看起来像是比赛条件)以下内容:
doOnComplete - before join
doOnComplete - after join
doOnComplete 2 - before join
在上述代码的第一次运行中,doOnComplete - after join
没有被调用,第二次被称为两次。第三次就像第一次,第四次就像第二次,等等...
3,4都使用此代码运行。可能与订阅{}的使用有关吗?请注意,我不拿着一次性用品。
由于我将GC的“左侧”可见,所以此流结束了
leftObservable.subscribeOn().observeOn()
.doOnComplete{log...}
.rightGroupJoin()
.doOnComplete{log...}
.subscribe {}
注意1:在.takeUntil { thisCompleted }
之后添加mergeAllSingles()
似乎可以解决#4。
注2:使用此方法加入多个流并应用“注1”后,很显然,onComplete(在groupJoin()调用!!!之前)将被调用与“正确”的Observable一样多的次数。表示原因是right.takeUntil { thisCompleted }
,关闭“正确”流是否真的很重要?
Note3:关于Note1,似乎与takeUntil与takeWhile有很大关系。使用takeWhile降低了doOnComplete调用,这在某种程度上是合乎逻辑的。仍在尝试更好地解决问题。
请问任何您喜欢的问题。我知道一个事实,那就是我对subscribe / disposable的使用和onComplete手册的使用不是这样,我只是不确定什么是..
答案 0 :(得分:1)
像这样简单的事情应该起作用:
@JvmStatic
fun main(string: Array<String>) {
val left = PublishSubject.create<String>()
val right = PublishSubject.create<Int>()
left.flatMapSingle { s -> right.buffer(left).map { Pair(s, it) }.firstOrError() }
.subscribe{ println("Group : Letter : ${it.first}, Elements : ${it.second}") }
left.onNext("A")
right.onNext(1)
right.onNext(2)
right.onNext(3)
left.onNext("B")
right.onNext(4)
right.onNext(5)
left.onNext("C")
right.onNext(6)
left.onComplete()
}
输出:
Group : Letter : A, Elements : [1, 2, 3]
Group : Letter : B, Elements : [4, 5]
Group : Letter : C, Elements : [6]
您感兴趣的Observable
是左边的,所以订阅它。然后,在左边可观察对象的下一个发射或完成处缓冲右边。您只对每个上游左发射的单个结果感兴趣,因此只需使用flatMapSingle
。我选择了firstOrError()
,但显然可以使用默认项或其他错误处理方法,甚至可以将flatMapMaybe
与firstElement()
修改
OP进行了进一步的问答,发现原来的问题和上述解决方案不是必须的行为,该问题可以用先前的左侧发射来缓冲右侧值,直到下一个左侧发射(如上所述)为止。新的必需行为是将右值缓冲到NEXT左发射,如下所示:
@JvmStatic
fun main(string: Array<String>) {
val left = PublishSubject.create<String>()
val right = PublishSubject.create<Int>()
left.zipWith (right.buffer(left),
BiFunction<String, List<Int>, Pair<String, List<Int>>> { t1, t2 -> Pair(t1, t2)
}).subscribe { println("Group : Letter : ${it.first}, Elements : ${it.second}") }
left.onNext("A")
right.onNext(1)
right.onNext(2)
right.onNext(3)
left.onNext("B")
right.onNext(4)
right.onNext(5)
left.onNext("C")
right.onNext(6)
left.onComplete()
}
将左值与先前的右值压缩在一起,直到下一个左发射(反向),这会产生不同的最终结果。
输出:
Group : Letter : A, Elements : []
Group : Letter : B, Elements : [1, 2, 3]
Group : Letter : C, Elements : [4, 5]
答案 1 :(得分:0)
乍一看,我会在这里使用2个scan
。示例:
data class Result(val left: Left?, val rightList: List<Right>) {
companion object {
val defaultInstance: Result = Result(null, listOf())
}
}
leftObservable.switchMap { left ->
rightObservable.scan(listOf()) {list, newRight -> list.plus(newRight)}
.map { rightsList -> Result(left, rightList) }
}
.scan(Pair(Result.defaultInstance, Result.defaultInstance)) { oldPair, newResult ->
Pair(oldPair.second, newResult)
}
.filter { it.first != it.second }
.map { it.first }
这里唯一的问题是处理onComplete
,不确定如何操作