Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala : transform a list into a map

I have an animal class defined as

case class Animal(name: String, properties: List[String])

Given a list of Animals, I want a map from property -> list of animals that satisfy that property

As an example, if I have input as, say,

List(
    Animal("Dog", 
           List("has tail",
                "can swim",
                "can bark",
                "can bite")),
    Animal("Tuna", 
           List("can swim",
                "has scales", 
                "is edible")),
    Animal("Black Mamba",
           List("has scales",
                "is venomous",
                "can bite"))
)

The output should be

Map(
  "has tail" -> List(Dog)
  "can swim" -> List(Tuna,Dog)
  "can bark" -> List(Dog)
  "has scales" -> List(Tuna,Snake)
  "is edible" -> List(Tuna)
  "is venomous" -> List(Snake)
  "can bite" -> List(Dog,Snake)
)

I am pretty new to functional programming. I can do this in an imperative manner, but have been struggling to come up with a functional solution to it. Any pointers are most welcome! :)

like image 309
iTwenty Avatar asked Mar 17 '23 05:03

iTwenty


1 Answers

You want to get a list of key-value pairs to start. We can start this problem by first seeing how we would covert a single Animal to a list of key-value pairs. You may have heard of the map function. That allows you to transform lists and other basic structures by applying a function to each element in the list. We can use it to good effect here:

animal.properties.map(property => (property, animal.name))

Here we're taking an animal's properties, and for each out, apply the anonymous function: property => (property, animal.name). This function creates a tuple (key-value pair in this case) of the property together with the animal's name.

Now we want to apply this to all the animals in the the list. That may sound like another map, but then we would have a list of lists of tuples, when really we just want a list of tuples. That's when you use flatMap which takes in a method that returns a list and applies it to each element, and flattens the list. So we just apply the above method to each element.

val kvps = animals.flatMap(animal => animal.properties.map(property => (property, animal.name))).toMap

Now we have a list of key-value pairs. Now we want to group them by their key. The groupBy method will return a list of tuples, where the left side is a key and the right side is a list of key-value pairs. This is almost what we want, but we just want the values on the right side. So we can do:

kvps.groupBy { case (key, value) => key }.toMap.mapValues(keyValues => keyValues.map { case (key, value) => value })

Altogether it might look like:

animals.flatMap { animal =>
    animal.properties map { property => (animal, property) }
}.groupBy { case (key, value) => key }.toMap mapValues { keyValues =>
    keyValues map { case (key, value) => value }
}

Of course, Scala has tons of syntactic sugar that can make this method very terse:

animals.flatMap(a => a.properties.map(_ -> a.name)).groupBy(_._1).toMap.mapValues(_.map(_._2))
like image 165
Ben Reich Avatar answered Mar 24 '23 17:03

Ben Reich