Scala循环选择:功能循环与传统for循环

Har*_*dya 3 scala

是否使用功能构造(map,foreach,flatMap等)更好地循环集合?作为一个虚拟问题,考虑我有一个字符串列表,我想按不同的标准过滤字符串,然后映射它们以获得一些价值.请考虑以下代码:

val x1 = list.filter(criteria1).map(do_something)
val x2 = list.filter(criteria2).map(do_something)
Run Code Online (Sandbox Code Playgroud)

假设我有5个这样不同的过滤条件然后通过这种方式我将循环遍历列表(可能很大)10次(一次使用过滤器,一次使用地图).

但是,我可以将所有这些组合成一个for循环并在单个迭代中返回/填充5个新列表,然后映射每个列表总共6个循环而不是10个循环.

for(i<- 0 to list.length-1){
  if(criteria1) //filter
  if(criteria2) //filter
}
Run Code Online (Sandbox Code Playgroud)

这段代码可能会迫使我使用可变列表,但从性能的角度来看,在这种情况下使用函数结构是否有意义.哪种方法更好?

注意:上面的代码/问题只是作为一个例子,我希望它能解释我所指的那种情况

Lui*_*hys 6

如果你想要过滤和映射,你可以使用withFilter而不是filter,这使得过滤器变得懒惰,这样你就不会多次遍历列表.for- 表达withFilter用于提高效率.您还可以查看views,它为其他操作提供类似的懒惰.

从问题中你想要做什么并不完全清楚,但我认为你想根据不同的过滤器和地图操作输出5个新列表.如果性能至关重要,那么使用像你建议的循环和可变构建器是一种合理的方法,这就是编程了多少个集合方法(检查源代码).不确定为什么你认为你需要过滤到5个列表然后遍历每个列表来进行映射 - 为什么不在构建新列表的同时做地图,方法是将函数应用于每个元素?例如

  def split[T](xs: Seq[T])(ops: (T => Boolean, T => T)*): Seq[Seq[T]] = {
    val (filters, maps) = ops.unzip
    val buffers = IndexedSeq.fill(ops.size)(ListBuffer.empty[T])
    for {
      x <- xs
      i <- buffers.indices
      if filters(i)(x)
    } buffers(i) += maps(i)(x)  
    buffers.map(_.toSeq)  // return to immutable-land
  }

  // demo: 
  val res = split(1 to 10)(
    (_ < 5, _ * 100),     // multiply everything under 5 by 100
    (_ % 2 == 1, 0 - _),  // negate all odd numbers
    (_ % 3 == 0, _ + 5)   // add 5 to numbers divisible by 3
  )

  println(res) 
  //Vector(List(100, 200, 300, 400), List(-1, -3, -5, -7, -9), List(8, 11, 14))
Run Code Online (Sandbox Code Playgroud)

我不认为有一种内置方法可以做你想做的事情(我想).请注意,如果使用递归,则可以定义不具有可变状态的构建器方法,但这是本地可变状态更简洁/可读的地方.

您的问题实际上取决于性能,并且很容易过早地进行优化.如果您确实遇到真正的性能问题,我建议您只执行上述操作.如果惯用/简单不够好,那么你可以调整一些东西来优化你的特定用例.它归结为这样一个事实:对于您可能想要做的所有事情,不能有内置的优化方法.


Dao*_*Wen 5

你也可以这样做:

val x1 = for(x <- list if criteria1) yield do_something(x)
Run Code Online (Sandbox Code Playgroud)

编译器实际上将其转换为val x1 = list.filter(criteria1).map(do_something)就像你上面一样.该for理解为只是一些漂亮的语法糖,让你打开一些顺序操作的复杂聚集到更多的东西可读.您可以阅读Odersky的书中的相关章节以获取更多详细信息.

回到你的问题.如果您尝试根据不同的过滤器和地图生成5个不同的列表,则可能应该列出列表.您可以使用for推导来循环每对转换函数的输入列表.

这将有助于您使代码更简单,但它实际上不会降低问题的算法复杂性(即您仍然在列表上迭代5次).

在这种情况下,我认为你是正确的,使用命令式循环会更有效率.用于构建列表的推荐数据结构是ListBuffer因为您可以在恒定时间内将元素添加到任一端 - 然后当您构建列表时,您可以将其转换为不可变列表(也在常量时间内).在Odersky的书中还有一小段关于使用ListBuffer的内容.这是我如何做到的:

import scala.collection.mutable.ListBuffer

val b1 = new ListBuffer[Int]
val b2 = new ListBuffer[Int]
// ... b3, b4, b5

for (x <- list) {
  val y = do_something(x)
  if (criteria1(x)) b1 += y
  if (criteria2(x)) b2 += y
  // ... criteria3, criteria4, criteria5
}

val x1 = b1.toList
val x2 = b2.toList
// ... x3, x4, x5
Run Code Online (Sandbox Code Playgroud)

因为它使用了一个可变的ListBuffer代码,所以这个代码不再是"纯粹的" - 但是由于你不再需要遍历整个列表5次,所以它可能值得加速长列表.

在这种情况下,我不会说一种方法比另一种方法好得多.ListBuffer使用突变的方式更快,但可能会使代码难以维护.相比之下,功能更强大的版本只是用来一再呼吁filtermap原来的列表中,这可能是更容易阅读(假设读者熟悉当然是惯用的Scala),更容易维护,但可能跑慢一点.选择实际上取决于您的目标.