为什么不可变地图大小始终为零?

时间:2013-01-30 22:47:18

标签: scala

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
  }

}

2 个答案:

答案 0 :(得分:7)

不可变数据结构 - 无论是列表还是映射 - 只是:不可变的。您永远不会更改它们,您可以根据对旧数据结构的更改创建 new 数据结构。

如果您执行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确实提供了可变的地图,而不仅仅是说它有它们:如果它们更适合您的问题,那么请继续使用它们!如果你想学习如何使用不可变的那些,那么上面的两种方法就是你应该学习的方法。

答案 1 :(得分:3)

地图是不可变的,因此任何修改都将返回修改后的地图。 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