Есть ли способ обработать последний случай по-разному в цикле Scala for?
Например, предположим, что у меня
for (line <- myData) {
println("}, {")
}
Есть ли способ получить последнюю строку для печати
println("}")
Ответы
Ответ 1
Прежде чем продолжить, я рекомендую вам избегать 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
Можете ли вы реорганизовать свой код, чтобы воспользоваться встроенным mkString
?
scala> List(1, 2, 3).mkString("{", "}, {", "}")
res1: String = {1}, {2}, {3}
Ответ 3
Я полностью согласен с тем, что было сказано ранее об использовании 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)
Ответ 4
В качестве примера вы можете использовать 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
}
В вашем случае разделитель }, {
, а конец }
Ответ 5
Если вы не хотите использовать встроенную функцию 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)
}
}
Ответ 6
Другие ответы по праву указаны на 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("}, {")
}
У нас есть следующие преимущества:
- Он работает по каждому элементу один за другим, не обязательно накапливая результат в памяти (если вы этого не хотите).
- Поскольку ему не нужно загружать всю коллекцию в память, чтобы проверить ее размер, достаточно спросить Итератора, если он исчерпан или нет. Вы можете читать данные из большого файла или из сети и т.д.