Scala计数内循环的迭代次数

时间:2011-01-02 18:04:25

标签: scala functional-programming iterator count immutability

如果我想在Scala中枚举内部循环的迭代,我将如何以函数式方式处理它?<​​/ p>

E.g。我将如何重写以下代码:

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

我可以使用var摆脱val currentPageNumber = Iterator.from(0),但这仍然意味着我实际上需要在循环之外声明它。

是否存在不会将currentPageNumber暴露给外部范围的技巧 - 就像zipWithIndex计数器一样 - 只存在于循环内部?

修改

我还发现了一个使用scanLeft的版本,但我觉得它很混乱。但也许某人可以某种方式对其进行优化。

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
}

自己的解决方案(暂时):

谢谢大家,我想我的想法就像是

documents.flatMap{ doc =>
  for (page <- doc) yield {
    currentPageNumber: Int =>
      "%d: %s".format(currentPageNumber, page)
}}.zipWithIndex.map (t => t._1(t._2 + 1)) map(println)

所以,诀窍是让currentPageNumber未定,直到可以实际应用zipWithIndex

4 个答案:

答案 0 :(得分:6)

我认为你使用的命令式版本可能更好,因为printf无论如何都有副作用,但是如果你想要一些功能性的东西,你可以这样做:

documents.foldLeft(1){ (docStartingPage,doc) =>
   doc.foldLeft(docStartingPage){ (currentPageNumber, page) =>
      printf("%d: %s\n", currentPageNumber, page)
      currentPageNumber + 1
   }
}

不幸的是,如果外部循环中的数字将在内循环中正确显示,则该数字必须可用。

您可能还会尝试以下操作,但您将丢失有关文档开始和结束位置的任何信息。

for ( (page,pageNumber) <- documents.flatten.zipWithIndex )
  printf("%d: %s\n", pageNumber + 1, page)

答案 1 :(得分:4)

你提到你不能扁平化,因为它不是列表清单。但是,如果他们支持flatMapmap,那么可以展平。例如:

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

第三步和第四步可以合并为一步。实际上,你也可以在那里添加第五步,但这是我们正在谈论的观点 - 所以我认为这不重要。

但是,如果您仅限于foreach ......那么它仍然是Traversable。您可以执行.toStream并获取以上所有内容(并移除view):

documents.toStream.flatMap(_.toStream map identity).
zipWithIndex.                                     
map(t => t._2 + 1 -> t._1).                       
map(("%d: %s".format(_: Int, _: String)).tupled). 
foreach(println)                                  

答案 2 :(得分:1)

如果您希望它快,只需添加大括号即可添加另一个范围。您也可以从该范围返回值,因此确实没有缺点:

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

答案 3 :(得分:1)

您可以在外部foreach中拉出currentPageNumber的初始化:

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