我有一个列表
val l : List[Map[String,Any]] = List(Map("a" -> 1, "b" -> 2.8), Map("a" -> 3, "c" -> 4), Map("c" -> 5, "d" -> "abc"))
我使用以下代码来查找键“a”(Int),“b”(Double)和“c”(Int)的总和。 “d”包含在噪音中。
l.map(n => n.mapValues( v => if (v.isInstanceOf[Number]) {v match {
case x:Int => x.asInstanceOf[Int]
case x:Double => x.asInstanceOf[Double]
}} else 0)).foldLeft((0,0.0,0))((t, m) => (
t._1 + m.get("a").getOrElse(0),
t._2 + m.get("b").getOrElse(0.0),
t._3 + m.get("c").getOrElse(0)))
我希望输出为(4,2.8,9)但是我被
删掉了<console>:10: error: overloaded method value + with alternatives:
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to (AnyVal)
我认为异常是试图告诉我'+'不能与AnyVal一起使用。我如何让这个工作来获得我想要的结果?感谢
答案 0 :(得分:4)
您可以使用foldLeft功能:
scala> val l : List[Map[String,Any]] = List(Map("a" -> 1, "b" -> 2.8), Map("a" -> 3, "c" -> 4), Map("c" -> 5, "d" -> "abc"))
l: List[Map[String,Any]] = List(Map(a -> 1, b -> 2.8), Map(a -> 3, c -> 4), Map(c -> 5, d -> abc))
scala> val (sa, sb, sc) = l.foldLeft((0: Int, 0: Double, 0: Int)){
| case ((a, b, c), m) => (
| a + m.get("a").collect{case i: Int => i}.getOrElse(0),
| b + m.get("b").collect{case i: Double => i}.getOrElse(0.),
| c + m.get("c").collect{case i: Int => i}.getOrElse(0)
| )
| }
sa: Int = 4
sb: Double = 2.8
sc: Int = 9
使用incrop的collect
代替match
的想法更新。
答案 1 :(得分:3)
首先,你完全错过了模式匹配的重点
{case i: Int => i
case d: Double => d
case _ => 0}
是mapValues
内所有功能的正确替换。然而,这不是问题,你的写作虽然复杂,却做同样的事情。
mapValues
中的函数返回Double
(因为某些分支返回Int
而其他分支返回Double
,在这种情况下,Int
会提升为Double
{1}}。如果不是,则会返回AnyVal
)。
所以你得到List[Map[String, Double]]
。此时,你已经失去了Ints。
执行m.get("a")
时,会返回Option[Double]
。 Option[A] has method getOrElse(default: A) : A
(实际上,default: => X
),但这里没有任何区别)。
如果您拨打getOrElse(0.0)
而非getOrElse(0)
,则会获得双倍优惠。您的代码仍然失败,因为您的折叠以(Int,Double,Double)开头,您将返回(Double,Double,Double)。如果你用(0.0,0.0,0.0)开始你的折叠,它可以工作,但你丢失了你的Ints,你得到(4.0,2.8,9.0)
现在,关于错误消息。您将Int传递给期望Double(getOrElse)的方法,Int通常应该转换为Double,就像您使用getOrElse(0.0)
调用一样。除了Option
是协变的(声明trait Option[+A]
)。如果X
是A
的祖先,则Option[A]
也是Option[X]
。所以O ption[Double]
也是Option[AnyVal]
和Option[Any]
。如果该选项被视为getOrElse(0)
,则调用Option[AnyVal]
有效,结果为AnyVal
(也适用于Any
,但AnyVal
更准确,这是编译器选择的那个)。由于表达式按原样编译,因此无需将0
提升为0.0
。因此m.get("a").getOrElse(0)
的类型为AnyVal
,无法添加到t._1
。这是您的错误消息所说的。
您知道“a”与Int相关联,“b”与double相关联,但您不会将此知识传递给编译器。
答案 2 :(得分:3)
m.foldLeft(0)(_+_._2)
这是一个非常简单明了的解决方案。 参考:http://ktuman.blogspot.com/2009/10/how-to-simply-sum-values-in-map-in.html
答案 3 :(得分:0)
一般情况下,如果您不知道密钥,但只想对值进行求和
val filtered = for {
map <- l
(k, v) <- map
if v.isInstanceOf[Number]
} yield k -> v.asInstanceOf[Number].doubleValue
val summed = filtered.groupBy(_._1) map { case (k, v) => k -> v.map(_._2).sum }
scala> l
res1: List[Map[String,Any]] = List(Map(a -> 1, b -> 2.8), Map(a -> 3, c -> 4), Map(c -> 5, d -> abc))
scala> filtered
res2: List[(String, Double)] = List((a,1.0), (b,2.8), (a,3.0), (c,4.0), (c,5.0))
scala> summed
res3: Map[String,Double] = Map(c -> 9.0, a -> 4.0, b -> 2.8)
您可以按所需类型过滤地图,例如
scala> val intMap = for (x <- l) yield x collect { case (k, v: Int) => k -> v }
intMap: List[scala.collection.immutable.Map[String,Int]] = List(Map(a -> 1), Map(a -> 3, c -> 4), Map(c -> 5))
然后求和值(见linked question)
scala> intMap reduce { _ |+| _ }
res4: scala.collection.immutable.Map[String,Int] = Map(a -> 4, c -> 9)
答案 4 :(得分:0)
漂亮的单行:
l.map(_.filterKeys(_ != "d")).flatten groupBy(_._1) map { case (k,v) => v map { case (k2,v2: Number) => v2.doubleValue} sum }
res0: scala.collection.immutable.Iterable[Double] = List(9.0, 4.0, 2.8)