Oliver Schonrock Oliver Schonrock - 23 days ago 5
Scala Question

Scala build Immutable Map with for... yield comprehension, throw exception on duplicate key

Here is the challenge (Scala 2.11):

(for (i <- -3 to 3) yield (f(i), i)).toMap


f(): Int => Int is an unknown argument here, which is passed into my code. The resulting map will be the "inverse" of the function over the bounded space.

This will work fine if

f(i: Int) = i * 2


but it will NOT work fine if

f(i: Int) = i * i


because, eg f(-2) = 4 and f(2) = 4 which makes duplicate keys and the second overwrites the first

My question is: How can I throw and IllegalArgumentException when there are duplicate keys...(ie telling the user that his/her function is not "invertable" over the bounded space)

I know I could use a mutable.Map and write a loop, as opposed to a for comprehension (eg using tail recursion), which does a "map.get" on each iteration and if already exists, throw...., otherwise add to map.

Is there an immutable way of doing it?

For extra points I also need to check that f(i) is within the bounds (-3..3) and ignore if not...

Answer

Breaking it down into smaller steps, it becomes clear that the for-comprehension returns a vector. toMap is what creates the map. however, in your scenario foldLeft is more appropriate than toMap.

  val f = (i:Int) => i * i
  val pairs = for (i <- -3 to 3) yield f(i) -> i
  val result = pairs.foldLeft(Map.empty[Int, Int]) { case (map, (k, v)) =>
    if(map contains k)
      map //or throw IllegalArgumentException here
    else
      map + (k -> v)
  }
  println(result)

here's a bit more stepped out (but functionally identical) answer:

  val f = (i:Int) => i * i
  val pairs = for (i <- -3 to 3) yield f(i) -> i

  val zero = Map.empty[Int, Int]
  def putIfAbsent(map:Map[Int, Int], pair:(Int, Int)):Map[Int, Int] =
    pair match { case (k, v) =>
      if(map contains k)
        map
      else
        map + (k -> v)
    }

  val result = pairs.foldLeft(zero)(putIfAbsent)
  println(result)
Comments