我正在使用scala处理XML,并将XML转换为自己的数据结构。当前,我正在使用普通的Map实例来保存(子)元素,但是,这种方式丢失了XML中元素的顺序,因此无法再现原始XML。

因此,我想使用LinkedHashMap实例而不是Map,但是我在节点列表上使用groupBy,这会创建一个Map:

例如:

  def parse(n:Node): Unit =
  {
    val leaves:Map[String, Seq[XmlItem]] =
      n.child
        .filter(node => { ... })
        .groupBy(_.label)
        .map((tuple:Tuple2[String, Seq[Node]]) =>
        {
          val items = tuple._2.map(node =>
          {
            val attributes = ...

            if (node.text.nonEmpty)
              XmlItem(Some(node.text), attributes)
            else
              XmlItem(None, attributes)
          })

          (tuple._1, items)
        })

      ...
   }

在此示例中,我希望leaves的类型为LinkedHashMap,以保留n.child的顺序。我该如何实现?

注意:我按标签/标签名分组,因为元素可以多次出现,并且对于每个标签/标签名,我在数据结构中保留了元素列表。

解决方案
正如@jwvh回答的那样,我正在使用foldLeft代替groupBy。另外,我决定使用LinkedHashMap而不是ListMap
  def parse(n:Node): Unit =
  {
    val leaves:mutable.LinkedHashMap[String, Seq[XmlItem]] =
      n.child
        .filter(node => { ... })
        .foldLeft(mutable.LinkedHashMap.empty[String, Seq[Node]])((m, sn) =>
        {
          m.update(sn.label, m.getOrElse(sn.label, Seq.empty[Node]) ++ Seq(sn))
          m
        })
        .map((tuple:Tuple2[String, Seq[Node]]) =>
        {
          val items = tuple._2.map(node =>
          {
            val attributes = ...

            if (node.text.nonEmpty)
              XmlItem(Some(node.text), attributes)
            else
              XmlItem(None, attributes)
          })

          (tuple._1, items)
        })

最佳答案

要获得相当于.groupBy()中的ListMap的粗略等价形式,您可以对集合进行fold。问题是ListMap保留元素的顺序,而不是遇到它们。

import collection.immutable.ListMap

List('a','b','a','c').foldLeft(ListMap.empty[Char,Seq[Char]]){
  case (lm,c) => lm.updated(c, c +: lm.getOrElse(c, Seq()))
}
//res0: ListMap[Char,Seq[Char]] = ListMap(b -> Seq(b), a -> Seq(a, a), c -> Seq(c))

要解决此问题,您可以使用foldRight代替foldLeft。结果是遇到的元素的原始顺序(从左到右扫描),但顺序相反。
List('a','b','a','c').foldRight(ListMap.empty[Char,Seq[Char]]){
  case (c,lm) => lm.updated(c, c +: lm.getOrElse(c, Seq()))
}
//res1: ListMap[Char,Seq[Char]] = ListMap(c -> Seq(c), b -> Seq(b), a -> Seq(a, a))

这不一定是一件坏事,因为ListMaplast ops O(1)的initheadtail ops O(n)的效率更高。

要按原始的从左到右顺序处理ListMap,可以对其进行.toList.reverse的处理。
List('a','b','a','c').foldRight(ListMap.empty[Char,Seq[Char]]){
  case (c,lm) => lm.updated(c, c +: lm.getOrElse(c, Seq()))
}.toList.reverse
//res2: List[(Char, Seq[Char])] = List((a,Seq(a, a)), (b,Seq(b)), (c,Seq(c)))

07-27 23:23