Layman
Layman

Reputation: 1056

Scala .map() Information Loss (Since return type is same as elements: Set)

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

Answers (3)

Tim
Tim

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

Denis Tulskiy
Denis Tulskiy

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

jwismar
jwismar

Reputation: 12258

The resulting Set from the Set.map() call is collapsing repeated values, like Sets are supposed to do...

Upvotes: 0

Related Questions