有没有办法在Scala for循环中以不同方式处理最后一种情况?

时间:2011-08-22 07:24:00

标签: scala

例如假设我有

  for (line <- myData) {
    println("}, {")
  }

有没有办法让最后一行打印

println("}")

6 个答案:

答案 0 :(得分:28)

您是否可以重构代码以利用内置的mkString

scala> List(1, 2, 3).mkString("{", "}, {", "}")
res1: String = {1}, {2}, {3}

答案 1 :(得分:19)

在进一步讨论之前,我建议您避免println进行理解。它有时可以用于跟踪集合中间发生的错误,但否则会导致代码难以重构和测试。

更一般地说,如果您可以限制任何类型的副作用发生的地方,生活通常会变得更容易。所以而不是:

for (line <- myData) {
  println("}, {")
}

你可以写:

val lines = for (line <- myData) yield "}, {"
println(lines mkString "\n")

我也想在这里猜测你想要输出中每一行的内容!

val lines = for (line <- myData) yield (line + "}, {")
println(lines mkString "\n")

如果你直接使用mkString,那么你还会感觉更好 - 这就是它的用途!

val lines = myData.mkString("{", "\n}, {", "}")
println(lines)

请注意我们如何首先生成String,然后在单个操作中打印它。这种方法可以很容易地分成单独的方法,用于在类上实现toString,或者在测试中检查生成的String。

答案 2 :(得分:13)

我完全赞同之前所说的关于使用mkstring的内容,并区分第一次迭代而不是最后一次迭代。你还需要区分最后一个,scala集合有一个init方法,它返回除了最后一个元素之外的所有元素。 所以你可以做到

for(x <- coll.init) workOnNonLast(x)
workOnLast(coll.last)

initlast与头部和尾部相反,它们是第一个,而且除了第一个之外。但是请注意,取决于结构,它们可能是昂贵的。在Vector,所有这些都很快。在List上,虽然头部和尾部基本上是免费的,但initlast在列表的长度上都是线性的。当集合可能为空时,headOptionlastOption可以帮助您,将workOnlast替换为

for (x <- coll.lastOption) workOnLast(x)

答案 3 :(得分:6)

您可以将TraversableOnce特征的addString函数作为示例。

def addString(b: StringBuilder, start: String, sep: String, end: String): StringBuilder = {
  var first = true

  b append start   
  for (x <- self) {
    if (first) {
      b append x
      first = false
    } else {
      b append sep
      b append x
    }
  }
  b append end

  b
}

在您的情况下,分隔符为}, {,结尾为}

答案 4 :(得分:2)

如果您不想使用内置的mkString函数,可以使用类似

的内容
for (line <- lines)
  if (line == lines.last) println("last")
  else println(line)

更新:正如评论中提到的didierd一样,这个解决方案是错误的,因为最后一个值可以多次出现,他在answer中提供了更好的解决方案。

对于Vectors来说很好,因为last函数对它们采用“有效恒定时间”,对于Lists,它需要线性时间,因此您可以使用模式匹配

@tailrec
def printLines[A](l: List[A]) {
  l match {
    case Nil => 
    case x :: Nil => println("last")
    case x :: xs => println(x); printLines(xs)
  }
}

答案 5 :(得分:1)

其他答案正确地指向mkString,对于正常数量的数据,我也会使用它。

但是,mkString通过StringBuilder在内存中构建(累积)最终结果。这并不总是可取的,这取决于我们拥有的数据量。

在这种情况下,如果我们想要的只是“打印”,我们不需要先构建大结果(也许我们甚至想避免这种情况)。

考虑这个辅助函数的实现:

def forEachIsLast[A](iterator: Iterator[A])(operation: (A, Boolean) => Unit): Unit = {
  while(iterator.hasNext) {
    val element = iterator.next()
    val isLast = !iterator.hasNext // if there is no "next", this is the last one
    operation(element, isLast)
  }
}    

迭代遍历所有元素,并使用布尔值依次调​​用operation依次传递每个元素。如果传递的元素是最后一个,则值为true

在你的情况下,它可以像这样使用:

forEachIsLast(myData) { (line, isLast) =>
  if(isLast)
    println("}")
  else
    println("}, {")
}

我们在这里有以下优势:

  • 它逐个对每个元素进行操作,而不必将结果累积到内存中(除非你想要)。
  • 因为它不需要将整个集合加载到内存中来检查它的大小,所以向Iterator询问它是否已经用完就足够了。您可以从大文件或网络等中读取数据。