【问题标题】:Scala sum Map valuesScala sum Map 值
【发布时间】:2012-04-17 23:20:00
【问题描述】:

我有一个列表

    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。我如何让它发挥作用以获得我想要的结果?谢谢

【问题讨论】:

    标签: scala map casting


    【解决方案1】:
    m.foldLeft(0)(_+_._2)  
    

    这是一个非常清晰和简单的解决方案。 参考:http://ktuman.blogspot.com/2009/10/how-to-simply-sum-values-in-map-in.html

    【讨论】:

      【解决方案2】:

      你可以使用 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 进行更新。

      【讨论】:

      • @paradigmatic: 不是“完全”:Option[Any].getOrElse 你会得到Anymatch/case 你会得到你想要的类型。
      • @paradigmatic:你可以使用m.getOrElse("a", 0),而不是m.get("a").getOrElse(0),但你仍然会得到Any
      • 您可以使用collect获取所需的类型:m.get("a").collect{case x: Int =&gt; x}.getOrElse(0)
      【解决方案3】:

      首先,你完全忽略了模式匹配的意义

      {case i: Int => i
       case d: Double => d
       case _ => 0} 
      

      mapValues 中所有函数的正确替换。然而这不是问题所在,你的写作虽然复杂,但也有同样的作用。

      您在mapValues 中的函数返回Double(因为一些分支返回Int 而另一些返回Double,在这种情况下,Int 被提升为Double。如果不是,它会返回AnyVal)。 所以你会得到一个List[Map[String, Double]]。此时,您已经失去了 Ints。

      当您执行m.get("a") 时,将返回Option[Double]Option[A] has method getOrElse(default: A) : A(实际上是default: =&gt; X),但在这里没有区别)。

      如果你调用getOrElse(0.0) 而不是getOrElse(0),你会得到一个Double。您的代码仍然失败,因为您的折叠以(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])。如果XA 的祖先,则Option[A] 也是Option[X]。所以 Option[Double] 也是 Option[AnyVal]Option[Any]。如果选项被认为是Option[AnyVal],则调用getOrElse(0) 有效,结果为AnyVal(也适用于Any,但AnyVal 更精确,这是编译器选择的那个)。因为表达式按原样编译,所以无需将0 提升为0.0。因此m.get("a").getOrElse(0)AnyVal 类型,不能添加到t._1。这就是您的错误消息所说的。

      您知道“a”与 Int 相关联,“b”与 double 相关联,但您没有将这些知识传递给编译器。

      【讨论】:

        【解决方案4】:

        一个漂亮的单行:

        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)
        

        【讨论】:

          【解决方案5】:

          一般来说,如果您不知道键,但只想对值求和,您可以这样做

          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)
          

          【讨论】:

          • 虽然简洁,但使用此解决方案我只剩下所有双打了。有没有办法保留类型?
          • 在这种情况下,我只能考虑按某种类型过滤地图。
          【解决方案6】:

          我是否遗漏了什么或者你不能做:

          map.values.sum
          

          ?

          【讨论】:

          • 是的,你错过了一些东西。分别需要每个键的总和,并且值具有不同的类型
          猜你喜欢
          • 2018-03-25
          • 2023-02-24
          • 2023-02-05
          • 2012-02-26
          • 2021-05-30
          • 1970-01-01
          • 2020-12-11
          • 2012-01-26
          • 1970-01-01
          相关资源
          最近更新 更多