我想将两个
scalaz流与一个谓词结合起来,该谓词从任一流中选择下一个元素.例如,我希望这个测试通过:
val a = Process(1,2,5,8) val b = Process(3,4,7) choose(a,b)(_ < _).toList shouldEqual List(1,3,7,8)
正如您所看到的,我们不能像zip那样聪明地做一些事情并订购这两个元素,因为有时会连续选择其中一个过程.
我抓住了一个我认为会起作用的解决方案.它汇编了!但该死的,如果它什么都不做. JVM只是挂起:(
import scalaz.stream.Process._ import scalaz.stream._ object StreamStuff { def choose[F[_],I](a:Process[F,I],b:Process[F,I])(p: (I,I) => Boolean): Process[F,I] = (a.awaitOption zip b.awaitOption).flatMap { case (Some(ai),Some(bi)) => if(p(ai,bi)) emit(ai) ++ choose(a,emit(bi) ++ b)(p) else emit(bi) ++ choose(emit(ai) ++ a,b)(p) case (None,Some(bi)) => emit(bi) ++ b case (Some(ai),None) => emit(ai) ++ a case _ => halt } }
请注意,以上是我的第二次尝试.在我的第一次尝试中,我尝试创建一个Tee,但我无法弄清楚如何取消使用失败元素.我觉得我需要一些递归的东西,就像我在这里一样.
我正在使用流版本0.7.3a.
解决方法
我将在下面给出一些提示和实现,因此如果您想自己制定解决方案,可能需要覆盖屏幕.
免责声明:这只是我想到的第一种方法,而且我对scalaz-stream API的熟悉程度有点生疏,所以可能有更好的方法来实现这个操作,这可能是完全错误的,以某种可怕的方式,等等.
提示1
您可以在下一次递归调用中传递它们,而不是试图“消耗”丢失的元素.
提示2
您可以通过指示最后丢失哪一方来避免累积多个丢失元素.
提示3
当我使用Scalaz流时,我经常发现使用普通集合绘制实现更容易.这是我们列表需要的辅助方法:
/** * @param p if true,the first of the pair wins */ def mergeListsWithHeld[A](p: (A,A) => Boolean)(held: Either[A,A])( ls: List[A],rs: List[A] ): List[A] = held match { // Right is the current winner. case Left(l) => rs match { // ...but it's empty. case Nil => l :: ls // ...and it's still winning. case r :: rt if p(r,l) => r :: mergeListsWithHeld(p)(held)(ls,rt) // ...upset! case r :: rt => l :: mergeListsWithHeld(p)(Right(r))(ls,rt) } // Left is the current winner. case Right(r) => ls match { case Nil => r :: rs case l :: lt if p(l,r) => l :: mergeListsWithHeld(p)(held)(lt,rs) case l :: lt => r :: mergeListsWithHeld(p)(Left(l))(lt,rs) } }
假设我们已经掌握了一个失败的元素,但现在我们可以编写我们实际想要使用的方法:
def mergeListsWith[A](p: (A,A) => Boolean)(ls: List[A],rs: List[A]): List[A] = ls match { case Nil => rs case l :: lt => rs match { case Nil => ls case r :: rt if p(l,r) => l :: mergeListsWithHeld(p)(Right(r))(lt,rt) case r :: rt => r :: mergeListsWithHeld(p)(Left(l))(lt,rt) } }
然后:
scala> org.scalacheck.Prop.forAll { (ls: List[Int],rs: List[Int]) => | mergeListsWith[Int](_ < _)(ls.sorted,rs.sorted) == (ls ++ rs).sorted | }.check + OK,passed 100 tests.
好的,看起来很好.我们可以为列表编写更好的方法,但是这个实现与我们需要为Process做的形状相匹配.
履行
这里或多或少与scalaz-stream相当:
import scalaz.{ -\/,\/,\/- } import scalaz.stream.Process.{ awaitL,awaitR,emit } import scalaz.stream.{ Process,Tee,tee } def mergeWithHeld[A](p: (A,A) => Boolean)(held: A \/ A): Tee[A,A,A] = held.fold(_ => awaitR[A],_ => awaitL[A]).awaitOption.flatMap { case None => emit(held.merge) ++ held.fold(_ => tee.passL,_ => tee.passR) case Some(next) if p(next,held.merge) => emit(next) ++ mergeWithHeld(p)(held) case Some(next) => emit(held.merge) ++ mergeWithHeld(p)( held.fold(_ => \/-(next),_ => -\/(next)) ) } def mergeWith[A](p: (A,A) => Boolean): Tee[A,A] = awaitL[A].awaitOption.flatMap { case None => tee.passR case Some(l) => awaitR[A].awaitOption.flatMap { case None => emit(l) ++ tee.passL case Some(r) if p(l,r) => emit(l) ++ mergeWithHeld(p)(\/-(r)) case Some(r) => emit(r) ++ mergeWithHeld(p)(-\/(l)) } }
然后再次检查:
scala> org.scalacheck.Prop.forAll { (ls: List[Int],rs: List[Int]) => | Process.emitAll(ls.sorted).tee(Process.emitAll(rs.sorted))( | mergeWith(_ < _) | ).toList == (ls ++ rs).sorted | }.check + OK,passed 100 tests.
如果没有更多的测试,我不会把它投入生产,但它看起来很有效.