Rx(RxKotlin)-使用groupJoin的rightGroupJoin-合并/合并两个不同类型的可观察对象

时间:2019-04-17 12:18:07

标签: rxjs rx-java reactive-programming rx-kotlin rx-kotlin2

在奋斗了几天之后,在看起来很简单的任务上,我来找你们:)

想法很简单。我有两个流/可观察对象,“左”和“右”。 我希望项目从“右”缓存/收集/聚合到“左”中的“当前”项目。
因此,“左侧”中的每个项目都定义了一个新的“窗口”,而所有“右侧”项目都将绑定到该窗口,直到发出新的“左侧”项目为止。因此,可视化:

任务:
'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”)上运行相同的代码

问题/问题(从最简单到最困难的问题)

  1. 这是做这样的事情的最好方法吗?这不是一种过度杀伤力吗?
  2. 完成“左”后是否有更好的方法(必须)来完成“结果”(和“右”)?没有这种丑陋的布尔逻辑?
  3. 这种用法似乎弄乱了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

  1. 在上述代码的第一次运行中,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调用,这在某种程度上是合乎逻辑的。仍在尝试更好地解决问题。

  1. 除了在groupJoin * rightObservablesCount上运行zip之外,您还能想到multiGroupJoin或本例中的multiRightGroupJoin吗?

请问任何您喜欢的问题。我知道一个事实,那就是我对subscribe / disposable的使用和onComplete手册的使用不是这样,我只是不确定什么是..

2 个答案:

答案 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(),但显然可以使用默认项或其他错误处理方法,甚至可以将flatMapMaybefirstElement()

结合使用

修改

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,不确定如何操作