Scala count number of iterations of inner loop
If I wanted to enumerate the iterations of an inner loop in Scala, how would I approach this in a functional style?
E.g. how would I rewrite the following code:
val documents = List("a" :: "b" :: Nil, "aa" :: "bb" :: Nil, "aaa" :: Nil)
var currentPageNumber = 0
documents.foreach { doc =>
for (page <- doc) {
currentPageNumber += 1
println("%d: %s".format(currentPageNumber.head, page)
// do something with page
}
}
I could get rid of the var
by using val currentPageNumber = Iterator.from(0)
but this still means that I actually need to declare it outside of the loop.
Is there a trick which would not expose currentPageNumber
to the outer scope and – just like the zipWithIndex
counter – only exists inside the loop?
Edit:
I’ve also found a version using scanLeft
but I think it’s rather confusing. But maybe someone can optimise it somehow.
documents.scanLeft(0) { case (cnt, doc) =>
doc.zip(Iterator.from(cnt + 1).toIterable).map { case(page, cnt) =>
println("%d: %s".format(cnt, page))
cnt
} last
}
Own solution (for now):
Thanks everyone, I th开发者_JAVA百科ink what I had in mind was something like
documents.flatMap{ doc =>
for (page <- doc) yield {
currentPageNumber: Int =>
"%d: %s".format(currentPageNumber, page)
}}.zipWithIndex.map (t => t._1(t._2 + 1)) map(println)
so, the trick is to leave currentPageNumber
undecided until one can actually apply zipWithIndex
.
I think that the imperative version you're using is probably better, because the printf
has side effects anyway, but if you want something functional, you can do this:
documents.foldLeft(1){ (docStartingPage,doc) =>
doc.foldLeft(docStartingPage){ (currentPageNumber, page) =>
printf("%d: %s\n", currentPageNumber, page)
currentPageNumber + 1
}
}
Unfortunately, the number has to be available to the outer loop if it's going to appear correctly in the inner loop again.
You might also try the following, though you'll lose any information about where documents start and end.
for ( (page,pageNumber) <- documents.flatten.zipWithIndex )
printf("%d: %s\n", pageNumber + 1, page)
You mention you cannot flatten because it is not a list of list. However, if they support flatMap
and map
, then you can flatten. For instance:
documents.view.flatMap(_ map identity). // flatten
zipWithIndex. // get the index
map(t => t._2 + 1 -> t._1). // adjust page number
map(("%d: %s".format(_: Int, _: String)).tupled). // format output
foreach(println) // tah dah
Steps three and four can be joined into a single step. Actually, you could add step five there too, but this is a view we are talking about -- so I don't think it matters much.
If, however, you are limited to foreach
... well, then it's still a Traversable
. You can do a .toStream
and get all of the above (and remove the view
):
documents.toStream.flatMap(_.toStream map identity).
zipWithIndex.
map(t => t._2 + 1 -> t._1).
map(("%d: %s".format(_: Int, _: String)).tupled).
foreach(println)
If you want it to be fast, just add another scope by adding braces. You can return values from that scope also, so there are really no downsides:
val documents = List("a" :: "b" :: Nil, "aaa" :: Nil)
val lettercount = {
var currentPageNumber = 0
documents.map(_.map(s => {
currentPageNumber += 1
println("Page " + currentPageNumber + " is " + s)
s.length
}).sum).sum
}
// currentPageNumber = 7 // Uncommented, this would be an error
You can pull the initialization of currentPageNumber in the outer foreach:
val documents = List("a" :: "b" :: Nil, "aa" :: "bb" :: Nil, "aaa" :: Nil)
documents.foreach {
var currentPageNumber = 0
doc => for(page <- doc) {
currentPageNumber += 1
printf("%d: %s%n", currentPageNumber, page)
// do something with page
}
}
精彩评论