1

I have a Seq[(A, B)]. I wanted to add an implicit method to such collections so I can do .toMultiMap to get back a Map[A, Seq[B]].

This was my first attempt:

  implicit class PairsExtensions[A, B](t: Traversable[(A, B)]) {
    def toMultiMap: Map[A, Traversable[B]] = t.groupBy(_._1).mapValues(_.map(_._2))
  }

But, now the problem is I always get back a Traversable for the values. I want to get back a Map[A, Set[B]] if I do Set[(A, B)].toMultiMap.

So, then I tried something like this:

 implicit class PairsExtensions2[A, B, Repr[_] <: Traversable[(A, B)]](t: TraversableLike[(A, B), Repr[(A, B)]]) {
    def toMultiMap(implicit bf: CanBuild[B, Repr[B]]): Map[A, Repr[B]] = t.groupBy(_._1).mapValues(_.map(_._2))
  }

But, it does not work:

val m1: Map[Int, Set[String]] = Set.empty[(Int, String)]
val m2: Map[Int, List[String]] = List.empty[(Int, String)]

What is the way to do this?

Łukasz
  • 8,555
  • 2
  • 28
  • 51
pathikrit
  • 32,469
  • 37
  • 142
  • 221
  • @m-zL Sorry, to be clear, the first attempt compiles and works. Second snippet does not compile.. I was just posting my attempt. – pathikrit Mar 22 '16 at 01:15
  • Just my 2 cents: you might want to reconsider whether it really makes sense for `.toMultiMap` on a `Set` to give you a `Map[A, Set[B]`. Uniqueness in the former and latter collections will be quite different. – Owen Mar 22 '16 at 01:16
  • @Owen: I understand. The point of the question is not about doing `.toMultiMap` in particular but how to write generic utils for Scala collections. You can imagine, for `List`s I want `Map[A, List[B]]` back and for `Seq`s, I want `Map[A, Seq[B]]` – pathikrit Mar 22 '16 at 01:22
  • 1
    I recently stumbled into similar problem. [Ended up](https://gist.github.com/Aivean/41d12334a087a18ad23f) with `.genericBuilder[T]` and `.asInstanceOf[T]` in the end. Let's see if community comes up with better type safe approach. – Aivean Mar 22 '16 at 01:44
  • @pathikrit OK I understand. – Owen Mar 22 '16 at 17:34

1 Answers1

4

I think this might be what you're after.

import scala.collection.generic.CanBuildFrom
import scala.language.higherKinds

implicit class PairsExtensions[A, B, C[X] <: Traversable[X]](t: C[(A, B)]) {
  def toMultiMap(implicit cbf: CanBuildFrom[Nothing, B, C[B]]): Map[A, C[B]] =
    t.groupBy(_._1).mapValues(_.map(_._2).to[C])
}

This passes a few simple tests:

scala> val m1: Map[Int, Set[String]] = Set.empty[(Int, String)].toMultiMap
m1: Map[Int,Set[String]] = Map()

scala> val m2: Map[Int, List[String]] = List.empty[(Int, String)].toMultiMap
m2: Map[Int,List[String]] = Map()

scala> Seq(('c',4),('x',2),('c',5)).toMultiMap
res4: Map[Char,Seq[Int]] = Map(x -> Vector(2), c -> Vector(4, 5))
jwvh
  • 50,871
  • 7
  • 38
  • 64
  • Slightly tangential. Is there something similar for Maps e.g. I want to add an implicit `invert` that given a `C[K, V] <: Map[K, V]`, produces `C[V, Set[K]]`? – pathikrit Mar 22 '16 at 17:38
  • Specifically, I know how to produce a `Map[V, Set[K]]` but I could not get the `CanBuildFrom` to work for a `C` that takes 2 type parameters. – pathikrit Mar 22 '16 at 17:39
  • The comments section is a bad place to start a new (even if related) topic, but I do have a `Map` inverter. It turns a `Map[K, List[V]]` into a `Map[V, List[K]]`. For some Map `m`, try this: `m flatten {case(k, vs) => vs.map((_, k))} groupBy (_._1) mapValues {_.map(_._2)}` – jwvh Mar 23 '16 at 05:34
  • sorry I meant - I know how to invert a Map but not how to invert in such a way to get back the same type e.g. if I invert an immutable Map, I want to get an immutable Map back. If I invert a mutable Map, I want to get back a mutable map. – pathikrit Mar 23 '16 at 18:30