开发者

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
  }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜