我正在使用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)
})
答案 0 :(得分:1)
完全不变的解决方案将非常缓慢。所以我会去
import collection.mutable.{ArrayBuffer, LinkedHashMap}
implicit class ExtraTraversableOps[A](seq: collection.TraversableOnce[A]) {
def orderedGroupBy[B](f: A => B): collection.Map[B, collection.Seq[A]] = {
val map = LinkedHashMap.empty[B, ArrayBuffer[A]]
for (x <- seq) {
val key = f(x)
map.getOrElseUpdate(key, ArrayBuffer.empty) += x
}
map
}
要使用,只需将代码中的.groupBy
更改为.orderedGroupBy
。
不能使用此类型对返回的Map
进行突变(尽管可以将其强制转换为mutable.Map
或mutable.LinkedHashMap
),因此足够安全出于大多数目的(如果需要,您可以在最后创建一个ListMap
。
答案 1 :(得分:1)
要在.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))
这不一定是一件坏事,因为ListMap
和last
操作O(1)的效率要比init
和{ head
,O(n)。
要按照从左到右的原始顺序处理tail
,可以ListMap
和.toList
。
.reverse