【问题标题】:Scala count number of iterations of inner loopScala计算内循环的迭代次数
【发布时间】:2011-01-02 18:04:25
【问题描述】:

如果我想在 Scala 中枚举内部循环的迭代,我将如何以函数式风格处理它?

例如我将如何重写以下代码:

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

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

是否有一个技巧不会将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

【问题讨论】:

    标签: scala functional-programming iterator count immutability


    【解决方案1】:

    我认为您使用的命令式版本可能更好,因为 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)
    

    【讨论】:

    • 扁平化不会真正起作用,因为实际上它不是列表列表。
    • @Debilski:如果您有从文档到 Traversables 的隐式(或显式)转换,那么您可以使用flatten
    【解决方案2】:

    您提到您不能展平,因为它不是列表列表。但是,如果它们支持 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)                                  
    

    【讨论】:

    • 你是对的。我实际上可以使用flatten...我想我需要做一些不同的事情,但这或多或少是我想要的。从内部map 我返回一个匿名函数,以运行号作为参数,flattenzipWithIndex。然后最后在匿名函数上应用索引并执行它。
    【解决方案3】:

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

    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
    

    【讨论】:

    • 你是对的,当然。但这与速度无关。我只是想学习更多的迭代器技巧。
    • @Debilski - 那么肯的答案就是你想要的。 fold 是通用的“携带信息”操作。
    【解决方案4】:

    可以在外层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
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-10-11
      • 1970-01-01
      • 2012-10-27
      • 2016-12-01
      • 2011-09-07
      • 1970-01-01
      • 2016-05-20
      • 1970-01-01
      • 2023-03-21
      相关资源
      最近更新 更多