Dmitry  Meshkov
Dmitry Meshkov

Reputation: 931

Scala - How to define map, where value depends on key?

Is there a way to define a Map, where Map value depends on its key, like

Map(key -> f(key), key2 -> f(key2), ...).

Upvotes: 7

Views: 7132

Answers (4)

Kevin Wright
Kevin Wright

Reputation: 49695

You're looking at this the wrong way...

A Map[K,V] is also an instance of Partialfunction[K,V]. So change everywhere you're using your Map type (vals, method params, etc.) to be a PartialFunction.

Then you can just work with f directly, or supply a Map[K,V] as the instance wherever you don't have a simple algebraic relationship between keys and values.

e.g.

def methodUsingMapping(x: PartialFunction[Int,Boolean]) = ...

//then
val myMap = Map(1->true, 2->true, 3->false)
methodUsingMapping(myMap)


//or
val isEven = PartialFunction(n: Int => n % 2 == 0)
methodUsingMapping(isEven)

//or
//note: case statements in a block is the smart way
//      to define a partial function
//      In this version, the result isn't even defined for odd numbers
val isEven: PartialFunction[Int,Boolean] = {
  case n: Int if n % 2 == 0 => true
}
methodUsingMapping(isEven)

You also might also want to consider using (K) => Option[V], in which case you can supply an instance of the type via the lift method, which maps inherit from PartialFunction

e.g.

def methodUsingMapping(x: (Int)=>Option[Boolean]) = ...

//then
val myMap = Map(1->true, 2->true, 3->false)
methodUsingMapping(myMap.lift)

//or
def isEven(n: Int) = Some(n % 2 == 0)
methodUsingMapping(isEven)

//or
def isEven(n: Int) = n % 2 == 0
methodUsingMapping(x => Some(isEven(x)))

Upvotes: 10

Ben Reich
Ben Reich

Reputation: 16324

You could make an infinite map of squares using:

val mySquareMap = Map.empty[Int, Int].withDefault(d => d * d)

This map will still have +, get, iterator and other methods that won't work as desired, but if you need a read-only map that returns squares, this would work.

Of course, it would be more efficient and probably clearer to just use:

val mySquare = (d:Int) => d * d

as a function. However, the above Map might be useful if you need to use some API that requires that type.

To have a more full-fledged solution for this, you might be better off building your own class that extends Map[Int, Int] that overrides get to return the square of its argument.

Upvotes: 1

acjay
acjay

Reputation: 36491

Because you only need to define 4 methods to make a Map trait implementation, you could just roll your own:

trait MapWithRelationship[K, +V] extends Map[K, V] {
  self =>
  def pred: (K, Any) => Boolean
  def underlying: Map[K, V]
  def get(key: K): Option[V] = underlying.get(key)
  def iterator: Iterator[(K, V)] = underlying.iterator
  def + [V1 >: V](kv: (K, V1)): MapWithRelationship[K, V1] = {
    val (k, v) = kv
    if (pred(k, v)) {
      new MapWithRelationship[K, V1] {
        val pred = self.pred
        val underlying = self.underlying + kv
      }
    } else {
      throw new Exception(s"Key-value pair $kv failed MapWithRelationship predicate")
    }
  }
  def -(key: K): MapWithRelationship[K, V] =
    new MapWithRelationship[K, V] {
      val pred = self.pred
      val underlying = self.underlying - key
    }
}

object MapWithRelationship {
  def apply[K, V](rule: (K, Any) => Boolean)(pairs: (K, V)*) = {
    val empty = new MapWithRelationship[K, V] { 
      def pred = rule 
      def underlying = Map.empty[K, V]
    }
    pairs.foldLeft(empty)(_ + _)
  }
}

Then you can use as such:

scala> val x = MapWithRelationship[Int, Int]((k, v) => v == k * k)()
x: MapWithRelationship[Int,Int] = Map()

scala> val x2 = x + (1 -> 1)
x2: MapWithRelationship[Int,Int] = Map(1 -> 1)

scala> val x3 = x + (5 -> 25)
x3: MapWithRelationship[Int,Int] = Map(5 -> 25)

scala> val x4 = x + (6 -> "foo")
java.lang.Exception: Key-value pair (6,foo) failed MapWithRelationship predicate
  at MapWithRelationship$class.$plus(<console>:21)
  at MapWithRelationship$$anon$3.$plus(<console>:33)
  ... 32 elided

Upvotes: 4

sarveshseri
sarveshseri

Reputation: 13985

Lets say you have your keys in a list like this, and you want to convert it map with squares as values.

scala> val keyList = ( 1 to 10 ).toList
keyList: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> val doSquare = ( x: Int ) => x * x
doSquare: Int => Int = <function1>

// Convert it to the list of tuples - ( key, doSquare( key ) )
scala> val tupleList = keyList.map( key => ( key, doSquare( key ) ) )
tuple: List[(Int, Int)] = List((1,1), (2,4), (3,9), (4,16), (5,25), (6,36), (7,49), (8,64), (9,81), (10,100))

val keyMap = tuple.toMap
keyMap: scala.collection.immutable.Map[Int,Int] = Map(5 -> 25, 10 -> 100, 1 -> 1, 6 -> 36, 9 -> 81, 2 -> 4, 7 -> 49, 3 -> 9, 8 -> 64, 4 -> 16)

Or to do it in one line

( 1 to 10 ).toList.map( x => ( x, x * x ) ).toMap

Or... if you have just few keys... then you can write the specific code

Map( 1 -> doSquare( 1 ), 2 -> doSquare( 2 ) )

Upvotes: 6

Related Questions