mparaz
mparaz

Reputation: 2069

What is a more functional way of creating a Map of List?

I have this working code to create a Map between the characters in a String, and a List containing the indexes.

scala> "Lollipop".zipWithIndex.foldLeft(Map[Char, List[Int]]())((acc, t) => acc + (t._1 -> (acc.getOrElse(t._1, List[Int]()) :+ t._2)))
res122: scala.collection.immutable.Map[Char,List[Int]] = Map(i -> List(4), L -> List(0), l -> List(2, 3), p -> List(5, 7), o -> List(1, 6))

But the use of acc.getOrElse looks imperative. Is there a more functional way that hides this from the user?

Upvotes: 0

Views: 104

Answers (2)

om-nom-nom
om-nom-nom

Reputation: 62835

Alternative is to use default value for your map (rewritten code a little bit to be more explicit):

val empty = Map.empty[Char, List[Int]].withDefaultValue(List.empty)
"Lollipop".zipWithIndex.foldLeft(empty) {
  case (acc, (char, position)) => {
    val positions = acc(char) :+ position
    acc + (char -> positions)
  } 
}

Upvotes: 2

senia
senia

Reputation: 38045

for {
  (c, l) <- "Lollipop".zipWithIndex.groupBy{ _._1 }
} yield c -> l.map{ _._2 }
// Map(i -> Vector(4), L -> Vector(0), l -> Vector(2, 3), p -> Vector(5, 7), o -> Vector(1, 6))

After groupBy{ _._1 } you'll get a Map[Char, Seq[(Char, Int)]]. So you have to convert pairs (Char, Int) to Int, using p => p._2 or just _._2.

You could use mapValueslike this:

"Lollipop".zipWithIndex.groupBy{ _._1 }.mapValues{ _.map{_._2} }

But mapValues creates a lazy collection, so you could get a performance issue in case of multiple access to the same element by key.

Upvotes: 5

Related Questions