【问题标题】:Why is immutable map size always zero?为什么不可变的地图大小总是零?
【发布时间】:2013-01-14 22:31:48
【问题描述】:

下面的 Scala 类使用 JDOM 解析文件并将文件中的值填充到 Scala 不可变 Map 中。在 Map 上使用 + 运算符似乎没有任何效果,因为 Map 始终为零。

import java.io.File
import org.jsoup.nodes.Document
import org.jsoup.Jsoup
import org.jsoup.select.Elements
import org.jsoup.nodes.Element
import scala.collection.immutable.TreeMap

class JdkElementDetail() {

  var fileLocation: String = _

  def this(fileLocation: String) = {
      this()
      this.fileLocation = fileLocation;
    }


  def parseFile : Map[String , String] = {

    val jdkElementsMap: Map[String, String] = new TreeMap[String , String];
    val input: File = new File(fileLocation);
    val doc: Document = Jsoup.parse(input, "UTF-8", "http://example.com/");
    val e: Elements = doc.getElementsByAttribute("href");

    val href: java.util.Iterator[Element] = e.iterator();
    while (href.hasNext()) {
      var objectName = href.next();
      var hrefValue = objectName.attr("href");
      var name = objectName.text();

      jdkElementsMap + name -> hrefValue
            println("size is "+jdkElementsMap.size)
    }

    jdkElementsMap
  }

}

println("size is "+jdkElementsMap.size) always prints "size is 0"

为什么大小总是零,我没有正确添加到地图吗?

解决此问题的唯一方法是将 jdkElementsMap 转换为 var 然后使用以下内容吗?

jdkElementsMap += name -> hrefValue

这里删除while循环是我更新的对象:

package com.parse

import java.io.File
import org.jsoup.nodes.Document
import org.jsoup.Jsoup
import org.jsoup.select.Elements
import org.jsoup.nodes.Element
import scala.collection.immutable.TreeMap
import scala.collection.JavaConverters._

class JdkElementDetail() {

  var fileLocation: String = _

  def this(fileLocation: String) = {
      this()
      this.fileLocation = fileLocation;
    }


  def parseFile : Map[String , String] = {

    var jdkElementsMap: Map[String, String] = new TreeMap[String , String];
    val input: File = new File(fileLocation);
    val doc: Document = Jsoup.parse(input, "UTF-8", "http://example.com/");
    val elements: Elements = doc.getElementsByAttribute("href");

    val elementsScalaIterator = elements.iterator().asScala

    elementsScalaIterator.foreach {
      keyVal => {
          var hrefValue = keyVal.attr("href");
          var name = keyVal.text();
          println("size is "+jdkElementsMap.size)
          jdkElementsMap += name -> hrefValue
       }
    }
    jdkElementsMap
  }

}

【问题讨论】:

  • x 是不可变对象时,x + zx 没有影响;如果它确实有效果,它就不会是不可变的!使用返回值,这是一个 new 不可变映射。
  • 嗯,不是“唯一”的方式,但它会起作用。一种选择是递归;最好以 TCO 友好的方式编写并使用@tailrec 进行验证。我相信有人会用一种巧妙的方法将循环(hasNext/next)包装到地图中(即for .. yield)或提及一些关于 monad 的事情:D
  • 我会看看stackoverflow.com/a/2709031/166390(提到JavaConversions)和stackoverflow.com/questions/8301947/…(较新的JavaConverters)——然后你可以用摆脱那个icky Java迭代器icky 程序循环并使用简单的地图操作,为什么要提出比其他选项“不那么可怕”的东西:D

标签: scala


【解决方案1】:

不可变的数据结构——无论是列表还是映射——就是这样:不可变的。您永远不会更改它们,而是根据对旧数据结构的更改创建数据结构。

如果您使用val x = jdkElementsMap + (name -> hrefValue),那么您将在x 上获得新地图,而jdkElementsMap 将保持不变。

如果您将jdkElementsMap 更改为var,那么您可以使用jdkEleemntsMap = jdkElementsMap + (name -> hrefValue),或者只是jdkElementsMap += (name -> hrefValue)。后者也适用于 可变 地图。

这是唯一的方法吗?不,但是您必须放弃while 循环才能实现相同的目标。您可以替换这些行:

val href: java.util.Iterator[Element] = e.iterator();
while (href.hasNext()) {
  var objectName = href.next();
  var hrefValue = objectName.attr("href");
  var name = objectName.text();

  jdkElementsMap + name -> hrefValue
        println("size is "+jdkElementsMap.size)
}

jdkElementsMap

带有折叠,例如:

import scala.collection.JavaConverters.asScalaIteratorConverter

e.iterator().asScala.foldLeft(jdkElementsMap) {
  case (accumulator, href) =>  // href here is not an iterator
    val objectName = href
    val hrefValue = objectName.attr("href")
    val name = objectName.text()

    val newAccumulator = accumulator + (name -> hrefValue)

    println("size is "+newAccumulator.size)

    newAccumulator
}

或者使用递归:

def createMap(hrefIterator: java.util.Iterator[Element],
              jdkElementsMap: Map[String, String]): Map[String, String] = {
  if (hrefIterator.hasNext()) {
    val objectName = hrefIterator.next()
    val hrefValue = objectName.attr("href")
    val name = objectName.text()

    val newMap = jdkElementsMap + name -> hrefValue

    println("size is "+newMap.size)

    createMap(hrefIterator, newMap)
  } else {
     jdkElementsMap
  }
}

createMap(e.iterator(), new TreeMap[String, String])

在性能方面,折叠会比较慢,而递归应该会稍微快一些。

请注意,Scala 确实提供了可变映射,而不仅仅是为了能够说它拥有它们:如果它们更适合您的问题,那么继续使用它们!如果你想学习如何使用不可变的,那么上面的两种方法是你应该学习的。

【讨论】:

    【解决方案2】:

    地图是不可变的,所以任何修改都会返回修改后的地图。 jdkElementsMap + (name -> hrefValue) 返回一个包含新对的新映射,但您将在创建后丢弃修改后的映射。

    编辑:看起来您可以将 Java 可迭代对象转换为 Scala 可迭代对象,因此您可以折叠生成的序列并累积映射:

    import scala.collection.JavaConverters._
    val e: Elements = doc.getElementsByAttribute("href");
    val jdkElementsMap = e.asScala
        .foldLeft(new TreeMap[String , String])((map, href) => map + (href.text() -> href.attr("href"))
    

    如果你不关心你创建什么样的地图,你可以使用toMap

    val jdkElementsMap = e.asScala
        .map(href => (href.text(), href.attr("href")))
        .toMap
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多