Scala .map()信息丢失(由于返回类型与元素相同:Set)

时间:2019-06-19 17:40:59

标签: scala

我正在使用这段代码来获取元素映射的加权和:

val wSum = elements.map(element=>
        weights(element) * values(element))
      .sum

在测试时,如果我通过

weights = Map(1 -> 1.0, 2 -> 1.0, 3 -> 1.0)
values = Map(1 -> 0.2, 2 -> 0.6, 3 -> 0.4)

它按预期返回1.2。另一方面,如果我通过

weights = Map(1 -> 1.0, 2 -> 1.0, 3 -> 1.0)
values = Map(1 -> 0.5, 2 -> 0.5, 3 -> 0.5)

这将返回0.5。这似乎很危险,我是否错过了常用的替代方法?

3 个答案:

答案 0 :(得分:4)

代码的行为正确,所以这并不是真的错误,当然也没有危险。

如果您实际上是尝试为每个元素计算weights*values,然后对结果求和,则有两种常见的方法:

最简单的就是

elements.toList.map(element=>
    weights(element) * values(element))
  .sum

另一种选择是使用foldLeft一次计算总和:

elements.foldLeft(0.0){
  case (prev, element) => prev + weights(element) * values(element)
}

要了解原始代码中的行为,您需要了解map的作用:

  

集合上的map方法通过将函数应用于旧集合的每个元素并将其添加到新集合中,来创建相同类型的新集合。

关键点在于集合类型没有更改,因此适用于旧集合的任何规则也适用于新集合。如果对集合进行了订购,则保留订购。如果集合是自排序的,则将对新集合进行排序。如果该集合具有类似集合的属性,则不会将重复的元素添加到新集合中。

在此问题中,通过在Scala代码中使用Java类使问题变得更加复杂。 Java集合的行为可能与Scala行为不同,因此正确的方法是使用asScala中的JavaConverters将所有Java集合转换为Scala集合。

答案 1 :(得分:1)

因此在集合上映射会返回一个集合。对我来说似乎合乎逻辑。 IIRC保留原始集合的返回类型是scala集合库的重要组成部分。您的直接解决方法是在映射之前调用elements.toSeq

但是,我将使用一个case类来封装Elements:

case class Element(weight: Double, value: Double) {
  def weightedValue = weight * value
}

val elements = Map(1 -> Element(1.0, 0.5), 2 -> Element(1.0, 0.5))

elements.values.map(_.weightedValue).sum

答案 2 :(得分:0)

Set.map()调用产生的Set折叠了重复的值,就像Set应该做的一样。