简体   繁体   English

如何将两个scalaz流与谓词选择器结合在一起?

[英]How can I combine two scalaz streams with a predicate selector?

I would like to combine two scalaz streams with a predicate which selects the next element from either stream. 我想将两个scalaz流与一个谓词结合起来,该谓词从任一流中选择下一个元素。 For instance, I would like this test to pass: 例如,我希望此测试通过:

val a = Process(1, 2, 5, 8)
val b = Process(3, 4, 5, 7)

choose(a, b)(_ < _).toList shouldEqual List(1, 2, 3, 4, 5, 5, 7, 8)

As you can see, we can't do something clever like zip and order the two elements because one of the processes may be selected consecutively at times. 如您所见,我们无法像zip一样巧妙地处理两个元素,因为有时可能会连续选择其中一个过程。

I took a stab at a solution that I thought would work. 我a了一个我认为可行的解决方案。 It compiled! 它编译了! But damn it if it doesn't do anything. 但是,如果它什么都不做,该死的。 The JVM just hangs :( 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
    }
}

Note that the above was my second attempt. 请注意,以上是我的第二次尝试。 In my first attempt I tried to create a Tee but I couldn't figure out how to un-consume the loser element. 在我的第一次尝试中,我尝试创建一个Tee但我不知道如何不使用失败者元素。 I felt that I needed something recursive like I have here. 我觉得我需要像这里一样递归的东西。

I am using streams version 0.7.3a . 我正在使用0.7.3a版本的0.7.3a

Any tips (including incremental hints because I'd like to simply learn how to figure these things out on my own) are greatly appreciated!! 非常感谢任何提示(包括渐进式提示,因为我想简单地学习如何自行解决这些问题)!!

I'll give a couple of hints and an implementation below, so you might want to cover the screen if you want to work out a solution yourself. 我将在下面给出一些提示和实现,因此,如果您想自己制定一个解决方案,则可能需要遮盖屏幕。

Disclaimer: this is just the first approach that came to mind, and my familiarity with the scalaz-stream API is a little rusty, so there may be nicer ways to implement this operation, this one might be totally wrong in some horrible way, etc. 免责声明:这只是我想到的第一种方法,我对scalaz-stream API的熟悉还有些生疏,因此可能会有更好的方法来实现此操作,以某种可怕的方式完全错误,等等。 。

Hint 1 提示1

Instead of trying to "unconsume" the losing elements, you can pass them along in the next recursive call. 您可以尝试在下一个递归调用中传递它们,而不是尝试“消耗”丢失的元素。

Hint 2 提示2

You can avoid having to accumulate more than one losing element by indicating which side lost last. 通过指示哪一方最后输了,您可以避免积累多个损失元素。

Hint 3 提示3

I often find it easier to sketch out an implementation using ordinary collections first when I'm working with Scalaz streams. 当我使用Scalaz流时,我通常会发现先使用普通集合来勾画出一个实现较为容易。 Here's the helper method we'll need for lists: 这是我们需要用于列表的辅助方法:

/**
 * @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)
  }
}

That assumes we've already got a losing element in hand, but now we can write the method we actually want to use: 假设我们已经有了一个丢失的元素,但是现在我们可以编写我们实际想要使用的方法了:

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

And then: 然后:

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.

Okay, looks fine. 好的,看起来不错。 There are nicer ways we could write this for lists, but this implementation matches the shape of what we'll need to do for Process . 我们可以使用更好的方法为列表编写代码,但是此实现与我们需要为Process进行Process的形状相匹配。

Implementation 实作

And here's more or less the equivalent with scalaz-stream: 这或多或少与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, 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))
    }
  }

And lets check it again: 并再次检查它:

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.

I wouldn't put this into production without some more testing, but it looks like it works. 如果没有更多测试,我不会将其投入生产,但看起来确实可行。

You have to implement a custom tee, as Travis Brown suggested. 正如Travis Brown建议的那样,您必须实现自定义T恤。 Here is my implementation of the tee: 这是我对tee的实现

/*
  A tee which sequentially compares elements from left and right
  and passes an element from left if predicate returns true, otherwise
  passes an element from right.
 */
def predicateTee[A](predicate: (A, A) => Boolean): Tee[A, A, A] = {

  def go(stack: Option[A \/ A]): Tee[A, A, A] = {
    def stackEither(l: A, r: A) =
      if (predicate(l, r)) emit(l) ++ go(\/-(r).some) else emit(r) ++ go(-\/(l).some)

    stack match {
      case None =>
        awaitL[A].awaitOption.flatMap { lo =>
          awaitR[A].awaitOption.flatMap { ro =>
            (lo, ro) match {
              case (Some(l), Some(r)) => stackEither(l, r)
              case (Some(l), None) => emit(l) ++ passL
              case (None, Some(r)) => emit(r) ++ passR
              case _ => halt
            }
          }
        }
      case Some(-\/(l)) => awaitR[A].awaitOption.flatMap {
        case Some(r) => stackEither(l, r)
        case None => emit(l) ++ passL
      }
      case Some(\/-(r)) => awaitL[A].awaitOption.flatMap {
        case Some(l) => stackEither(l, r)
        case None => emit(r) ++ passR
      }
    }
  }

  go(None)
}

val p1: Process[Task, Int] = Process(1, 2, 4, 5, 9, 10, 11)
val p2: Process[Task, Int] = Process(0, 3, 7, 8, 6)

p1.tee(p2)(predicateTee(_ < _)).runLog.run
//res0: IndexedSeq[Int] = Vector(0, 1, 2, 3, 4, 5, 7, 8, 6, 9, 10, 11)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM