简体   繁体   English

将Haskell反转计数器移植到Scala

[英]Porting Haskell inversion counter to Scala

Here's relevant code from Haskell implementation of a routine that counts inversion in a list 这是Haskell实现例程的相关代码,该例程计算列表中的反转

mergeAndCount :: Ord a => [a] -> [a] -> (Int,[a])
mergeAndCount l@(x:xs) r@(y:ys) | x < y = let (inv, s) = mergeAndCount xs r in (inv, x:s)
                                | otherwise = let (inv, s) = mergeAndCount l ys in (inv + rest, y:s)
                                                where rest = length l
mergeAndCount l [] = (0, l)
mergeAndCount [] r = (0, r)

I've tried to write similar routine in Scala but it crashes with stack overflow (lazy sorting works, though). 我试图在Scala中编写类似的例程,但是由于堆栈溢出而崩溃(不过,延迟排序有效)。 Here's the the non-working version: 这是无效版本:

  def mergeAndCount(l: Stream[Int], r: Stream[Int]) : (Long, Stream[Int]) = {
    (l, r) match {
      case (x#::xs, Empty) => (0, l)
      case (Empty, y#::ys) => (0, r)
      case (x#::xs, y#::ys) => if(x < y) {
        lazy val (i, s) = mergeAndCount(xs, r)
        (i, x#::s)
      } else {
        lazy val (i, s) = mergeAndCount(l, ys)
        (i + l.length, y#::s)
      }
    }
  }

Any ideas on how to make Scala version to behave like the Haskell one? 关于如何使Scala版本像Haskell那样表现的任何想法吗?

You can trade heap for stack in a situation like this (where turning the recursive call into a tail call might be complicated) by using trampolines : 您可以使用蹦床在这种情况下(将递归调用转换为尾调用可能会很复杂),以堆换栈:

import Stream.Empty
import scalaz.std.tuple._
import scalaz.syntax.bifunctor._
import scalaz.Free.Trampoline, scalaz.Trampoline._

def mergeAndCount(
  l: Stream[Int],
  r: Stream[Int]
): Trampoline[(Long, Stream[Int])] = (l, r) match {
  case (_ #:: _, Empty) => done((0, l))
  case (Empty, _ #:: _) => done((0, r))
  case (x #:: xs, y #:: _) if x < y => suspend(
    mergeAndCount(xs, r).map(_.rightMap(x #:: _))
  )
  case (_, y #:: ys) => suspend(
    mergeAndCount(l, ys).map(_.bimap(_ + l.length, y #:: _))
  )
}

Note that I'm using Scalaz 's implementation here, since the standard library's is currently missing some pieces (although that will change soon ). 请注意,我在这里使用Scalaz的实现,因为标准库当前缺少一些内容(尽管很快就会改变 )。

Now you can write eg the following: 现在,您可以编写例如以下内容:

mergeAndCount((1 to 20000).toStream, (2 to 20001).toStream).run

Where this would almost certainly blow the stack if we weren't trampolining the tail calls. 如果我们不踩尾巴电话,这几乎肯定会打击筹码。

I'd leave this as a comment but unfortunately I don't have the reputation yet to do so... 我会发表评论,但不幸的是我还没有这样的声誉...

Anyway, you're likely to run into stack overflow errors if you recurse before the last call in your function - in Scala only tail recursion is optimised not to use the stack. 无论如何,如果在函数的最后一次调用之前进行递归操作,则很可能会遇到堆栈溢出错误-在Scala中,仅优化了尾递归而不使用堆栈。 If you can move the recursive call to the last line of each case (which would mean abandoning the laziness) then you might get better results. 如果您可以将递归调用移至每种情况的最后一行(这意味着放弃惰性),则可能会得到更好的结果。

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

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