Reputation: 44051
For example suppose I have
for (line <- myData) {
println("}, {")
}
Is there a way to get the last line to print
println("}")
Upvotes: 18
Views: 5093
Reputation: 1991
Other answers are rightfully pointed to mkString
, and for a normal amount of data I would also use that.
However, mkString
builds (accumulates) the end-result in-memory through a StringBuilder
. This is not always desirable, depending on the amount of data we have.
In this case, if all we want is to "print" we don't need to build the big-result first (and maybe we even want to avoid this).
Consider the implementation of this helper function:
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)
}
}
It iterates over all elements and invokes operation
passing each element in turn, with a boolean value. The value is true
if the element passed is the last one.
In your case it could be used like this:
forEachIsLast(myData) { (line, isLast) =>
if(isLast)
println("}")
else
println("}, {")
}
We have the following advantages here:
Upvotes: 2
Reputation: 10776
If you don't want to use built-in mkString function, you can make something like
for (line <- lines)
if (line == lines.last) println("last")
else println(line)
UPDATE: As didierd mentioned in comments, this solution is wrong because last value can occurs several times, he provides better solution in his answer.
It is fine for Vectors
, because last
function takes "effectively constant time" for them, as for Lists
, it takes linear time, so you can use pattern matching
@tailrec
def printLines[A](l: List[A]) {
l match {
case Nil =>
case x :: Nil => println("last")
case x :: xs => println(x); printLines(xs)
}
}
Upvotes: 3
Reputation: 29528
I agree fully with what has been said before about using mkstring
, and distinguishing the first iteration rather than the last one. Would you still need to distinguish on the last, scala collections have an init
method, which return all elements but the last.
So you can do
for(x <- coll.init) workOnNonLast(x)
workOnLast(coll.last)
(init
and last
being sort of the opposite of head and tail, which are the first and and all but first). Note however than depending on the structure, they may be costly. On Vector
, all of them are fast. On List
, while head and tail are basically free, init
and last
are both linear in the length of the list. headOption
and lastOption
may help you when the collection may be empty, replacing workOnlast
by
for (x <- coll.lastOption) workOnLast(x)
Upvotes: 15
Reputation: 49705
Before going any further, I'd recommend you avoid println
in a for-comprehension. It can sometimes be useful for tracking down a bug that occurs in the middle of a collection, but otherwise leads to code that's harder to refactor and test.
More generally, life usually becomes easier if you can restrict where any sort of side-effect occurs. So instead of:
for (line <- myData) {
println("}, {")
}
You can write:
val lines = for (line <- myData) yield "}, {"
println(lines mkString "\n")
I'm also going to take a guess here that you wanted the content of each line in the output!
val lines = for (line <- myData) yield (line + "}, {")
println(lines mkString "\n")
Though you'd be better off still if you just used mkString
directly - that's what it's for!
val lines = myData.mkString("{", "\n}, {", "}")
println(lines)
Note how we're first producing a String
, then printing it in a single operation. This approach can easily be split into separate methods and used to implement toString
on your class, or to inspect the generated String in tests.
Upvotes: 21
Reputation: 340753
Can you refactor your code to take advantage of built-in mkString
?
scala> List(1, 2, 3).mkString("{", "}, {", "}")
res1: String = {1}, {2}, {3}
Upvotes: 29
Reputation: 24769
You may take the addString function of the TraversableOnce
trait as an example.
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
}
In your case, the separator is }, {
and the end is }
Upvotes: 6