Есть ли способ обработать последний случай по-другому в цикле Scala for? - PullRequest
16 голосов
/ 22 августа 2011

Например, предположим, у меня есть

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

Есть ли способ получить последнюю строку для печати

println("}")

Ответы [ 6 ]

28 голосов
/ 22 августа 2011

Можете ли вы реорганизовать свой код, чтобы воспользоваться встроенным mkString?

scala> List(1, 2, 3).mkString("{", "}, {", "}")
res1: String = {1}, {2}, {3}
19 голосов
/ 22 августа 2011

Прежде чем идти дальше, я бы порекомендовал вам избегать 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 в вашем классе или для проверки сгенерированной строки в тестах.

13 голосов
/ 22 августа 2011

Я полностью согласен с тем, что было сказано ранее об использовании mkstring и различении первой итерации, а не последней.Вам все еще нужно различать последние, в коллекциях scala есть метод init, который возвращает все элементы, кроме последнего.Таким образом, вы можете сделать

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

(init и last как бы противоположность головы и хвоста, которые являются первыми и все, кроме первого)Однако обратите внимание, что в зависимости от структуры они могут быть дорогостоящими.На Vector все они быстрые.На List, хотя голова и хвост в основном свободны, init и last являются линейными по длине списка.headOption и lastOption могут помочь вам, когда коллекция может быть пустой, заменив workOnlast на

for (x <- coll.lastOption) workOnLast(x)
6 голосов
/ 22 августа 2011

В качестве примера можно взять функцию addString черты TraversableOnce.

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
}

В вашем случае разделитель равен }, {, а конец равен }

3 голосов
/ 22 августа 2011

Если вы не хотите использовать встроенную функцию mkString, вы можете сделать что-то вроде

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

ОБНОВЛЕНИЕ: Поскольку didierd упомянуто в комментариях, это решение неверно, поскольку последнее значение может встречаться несколько раз, он предоставляет лучшее решение в своем ответе .

Это нормально для 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)
  }
}
1 голос
/ 01 февраля 2016

Другие ответы справедливо указываются на 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("}, {")
}

У нас есть следующееПреимущества здесь:

  • Он работает по каждому элементу, один за другим, без необходимости накапливать результат в памяти (если вы этого не хотите).
  • Поскольку ему не нужно загружатьВся коллекция в памяти, чтобы проверить ее размер, достаточно спросить у Итератора, исчерпан ли он или нет.Вы можете читать данные из большого файла или из сети и т. Д.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...