Reputation: 1056
I am using this piece of code to get the weighted sum of a map of elements:
val wSum = elements.map(element=>
weights(element) * values(element))
.sum
While testing, if I pass
weights = Map(1 -> 1.0, 2 -> 1.0, 3 -> 1.0)
values = Map(1 -> 0.2, 2 -> 0.6, 3 -> 0.4)
It returns 1.2 as expected. On the other hand, if I pass
weights = Map(1 -> 1.0, 2 -> 1.0, 3 -> 1.0)
values = Map(1 -> 0.5, 2 -> 0.5, 3 -> 0.5)
This returns 0.5. This seems like dangerously wrong, am I missing a commonly used alternative instead?
Upvotes: 1
Views: 193
Reputation: 27356
The code is behaving correctly, so it isn't really wrong, and certainly not dangerously so.
If you are actually trying to compute weights*values
for each element and then sum the result, there are two common ways of doing this:
The simplest is just
elements.toList.map(element=>
weights(element) * values(element))
.sum
The other option is to compute the sum in one pass using foldLeft
:
elements.foldLeft(0.0){
case (prev, element) => prev + weights(element) * values(element)
}
To understand the behaviour in the original code you need to understand what map
does:
The
map
method on a collection creates a new collection of the same type by applying a function to each element of the old collection and adding it to the new collection.
The key point is that the collection type is not changed, so any rules that apply to the old collection also apply to the new collection. If the collection is ordered then the ordering is retained. If the collection is self-sorting then the new collection will be sorted. If the collection has set-like properties then duplicate elements will not be added to the new collection.
In this question the problem is made more complicated by using Java classes in Scala code. The behaviour of a Java collection might be different from the Scala behaviour, so the correct approach is to convert all Java collections to Scala collections using asScala
from JavaConverters
.
Upvotes: 4
Reputation: 19177
So mapping over a set returns a set. Seems logical to me. IIRC, preserving return type of the original collection was a big part of scala collections library. Your immediate fix would be to call elements.toSeq
before mapping.
However, I'd go with a case class to encapsulate 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
Upvotes: 1
Reputation: 12258
The resulting Set from the Set.map() call is collapsing repeated values, like Sets are supposed to do...
Upvotes: 0