在列表中获取元素的功能方法,直到Scala中的限制

时间:2017-04-02 08:22:03

标签: scala functional-programming

该方法的目的是在列表中获取元素,直到达到限制。

e.g。

我提出了两种不同的实现

def take(l: List[Int], limit: Int): List[Int] = {
  var sum = 0
  l.takeWhile { e =>
    sum += e
    sum <= limit
  }
}

这很简单,但使用了可变状态。

def take(l: List[Int], limit: Int): List[Int] = {
  val summed = l.toStream.scanLeft(0) { case (e, sum) => sum + e }
  l.take(summed.indexWhere(_ > limit) - 1)
}

看起来更干净,但它更冗长,也许内存效率更低,因为需要一个流。

有更好的方法吗?

5 个答案:

答案 0 :(得分:4)

你也可以通过折叠一次性完成:

  def take(l: List[Int], limit: Int): List[Int] =
    l.fold((List.empty[Int], 0)) { case ((res, acc), next) =>
      if (acc + next > limit)
        (res, limit)
      else
        (next :: res, next + acc)
    }

因为标准列表不是懒惰,也不是折叠,所以这将始终遍历整个列表。另一种方法是使用cats' iteratorFoldM来代替达到限制后短路的实现。

您也可以使用尾递归直接编写短路折叠,这些就是这些:

def take(l: List[Int], limit: Int): List[Int] = {
  @annotation.tailrec
  def take0(list: List[Int], accList: List[Int], accSum: Int) : List[Int] =
    list match {
      case h :: t if accSum + h < limit =>  
        take0(t, h :: accList, h + accSum)
      case _ => accList
    }
  take0(l, Nil, 0).reverse
}

请注意,第二个解决方案可能更快,但也不那么优雅,因为它需要额外的努力来证明实现终止,这在使用折叠时是显而易见的。

答案 1 :(得分:2)

第一种方式非常好,因为你的功能的结果仍然完全不可变。

另一方面,这实际上是实现了scala集合库的多少个函数,它们创建了一个可变的构建器以提高效率并从中返回一个不可变的集合。

答案 2 :(得分:1)

一种功能性的方法是使用递归函数并确保它是堆栈安全的。

如果你只使用基本scala:

    import scala.annotation.tailrec
    def take(l: List[Int], limit: Int) : List[Int] = {
      @tailrec
      def takeHelper(l:List[Int], limit:Int, r:List[Int]):List[Int] =
        l match {
            case h::t if (h <= limit ) =>  takeHelper(t, limit-h, r:+h)
            case _ =>  r
      }
      takeHelper(l, limit, Nil)
    }

如果你可以使用scalaz Trampoline,它会更好一些:

import scalaz._
import scalaz.Scalaz._
import Free._
 def take(l: List[Int], limit: Int): Trampoline[List[Int]] = {

    l match {
      case h :: t if (h <= limit) => suspend(take(t, limit - h)).map(h :: _)


     case _                      => return_(Nil)
    }
  }
  println(take(List(1, 2, 3, 4, 0, 0, 1), 10).run)
  println(take(List.fill(10000)(1), 100000000).run)

答案 3 :(得分:0)

如果您想扩展自己的自定义方式,还可以使用以下内容:

def custom(con: => Boolean)(i: Int)(a: => List[Int])(body: => Unit): List[Int] = {
      if (con) {
        body
        custom(con)(i + 1)(a)(body)
      }
      else {
        a.slice(0, i)
      }
    }

然后这样称呼:

var j = 100
    val t = customTake(j > 80)(0)((0 to 99).toList) {
      j -= 1
    }
    println(t)

答案 4 :(得分:0)

我认为你的第二个版本已经相当不错了。您可以稍微调整一下,如下所示:

val sums = l.toStream.scanLeft(0){_ + _} drop 1
l zip sums takeWhile {_._2 <= limit} map (_._1)

这样你就不会处理索引,这通常更容易理解。