Rxjs Rx(RxKotlin)-使用groupJoin的rightGroupJoin-合并/组合两个不同类型的观察值

Rxjs Rx(RxKotlin)-使用groupJoin的rightGroupJoin-合并/组合两个不同类型的观察值,rxjs,rx-java,reactive-programming,rx-kotlin,rx-kotlin2,Rxjs,Rx Java,Reactive Programming,Rx Kotlin,Rx Kotlin2,经过几天的努力,这似乎是一项简单的任务,我来到你们这里: 这个想法很简单。我有两条流/可观察到的“左”和“右”。 我希望项目从“右”缓冲/收集/聚合到“左”中的“当前”项目。 因此,“left”中的每个项都定义了一个新的“窗口”,而所有“right”项都将绑定到该窗口,直到发出一个新的“left”项。因此,想象一下: 任务: ‘左’:|-A----B----C-----| “对”:-1-2-3-4-5-6---| ‘结果’:|--x----y----z |对 式中:A,1;B、 4sox;所以y

经过几天的努力,这似乎是一项简单的任务,我来到你们这里:

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

任务: ‘左’:|-A----B----C-----| “对”:-1-2-3-4-5-6---| ‘结果’:|--x----y----z |对 式中:A,1;B、 4sox;所以y是同时发射的 所以:x=PairA[1,2,3],y=PairB[4,5] 和:'right'和'result'完成/当'left'完成时终止 So:z=PairC,[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)
        })
    }
}  
其中:

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
}  
我还使用类似的用法创建了一个timedBuffer-与buffertimeout相同,但每个bufferList上都有一个时间戳,以便知道它何时启动。基本上通过在Observable.intervaltimeout上运行与“left”相同的代码

从最容易到最难的问题/问题

这是做那样事情的最好方式吗?这是不是太过分了? 当“左”完成时,是否有更好的方法来完成“结果”和“右”?没有这种丑陋的布尔逻辑? 这种用法似乎打乱了rx的顺序。请参见下面的代码和打印:

leftObservable
.doOnComplete {
    log("doOnComplete - before join")
 }
.doOnComplete {
    log("doOnComplete 2 - before join")
 }
.rightGroupJoin(rightObservable)
.doOnComplete {
    log("doOnComplete - after join")
 }
有时会打印!看起来像是一个竞赛条件,如下所示: doOnComplete-加入前 doOnComplete-加入后 doOnComplete 2-加入前

在上述代码的第一次运行中,不会调用doOnComplete-after连接,第二次调用它两次。第三次像第一次,第四次像第二次,等等。。。 这两个3,4都使用此代码运行。可能与subscribe{}的使用有关?注意,我没有拿一次性的。 这个流结束了,因为我看到了“左”的可观察对象

leftObservable.subscribeOn().observeOn()
.doOnComplete{log...}
.rightGroupJoin()
.doOnComplete{log...}
.subscribe {}  
注1:在mergeAllSingles之后添加.takeUntil{thisCompleted}似乎可以修复4

注2:使用此方法连接多个流并应用“Note1”后,很明显,在groupJoin调用之前,onComplete完成了!!!将被调用的次数与“正确”的可观察次数相同,可能意味着原因是正确的。在{thisCompleted}之前,关闭“正确”流真的很重要吗

注3:关于注1,它似乎与takeUntil和takeWhile非常相关。使用takeWhile可以降低doOnComplete调用,这在某种程度上是合乎逻辑的。仍然在努力更好地解决这个问题

除了在groupJoin*RightObservableCount上运行zip,您能想到一个multiGroupJoin,或者在我们的例子中是multiRightGroupJoin吗? 请随便问。事实上,我知道我使用的订阅/一次性和手册onComplete不是这样的,我只是不太确定什么是…

乍一看,我会在这里使用两次扫描。例如:

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,但不确定如何处理它

@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()
}
你感兴趣的对象是左边的,所以请订阅它。然后用左边的观察物的下一次发射或完成来缓冲右边。您只对每个上游左侧发射的单个结果感兴趣,所以只需使用flatMapSingle。我选择了firstOrError,但显然可能有一个默认项或其他错误处理,甚至可能有一个FlatMap与firstElement耦合

编辑

OP进行了进一步的问答,发现原来的问题和上面的解决方案将右值与前一个左发射进行缓冲,直到下一个左发射(如上所述)不是必需的行为。新的要求行为是将右值缓冲到下一个左发射,如下所示:

@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()
    }

谢谢!我们很快就会尝试:看起来简单完美。将很快实施,看看它是否适合,谢谢!执行val left=PublishSubject.create.publish.refCount有意义吗?否则任何前面的doOnComplete都会被调用两次我们在代码中传递“left”两次我使用的主题只是为了推送事件,你可以很容易地拥有一个发射器,或者自定义可观察对象。如果使用仅在订阅时才发出的ConnectableObservable,而不是热observable是有意义的,那么这就没关系了,因为在我看来这是一个实现细节的问题。doOn操作符应该只用于副作用或日志记录,这就是我假设您正在使用它们的方式。是的,我使用它们来记录,以便进行健全性检查。让他们多次打电话给rx,感觉是一种反模式,但我真的不知道这就是问题所在。非常感谢。在进一步的QA之后,很明显,此解决方案将“右”聚集在已发出的“左”单子上,这意味着“右”项与之前的“左”项配对,而不是o f下一个。解决方案是将flatMapSingle更改为zipWithright.bufferleft,…,以便在发出“left”时,已发出的“right”项与其配对。您能否编辑您的答案以添加此解决方案?我仍然希望它是被接受的。还使用新的解决方案编辑了我的问题
@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]
@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]