Scala中的多值映射

Dav*_*haw 5 scala multimap scala-2.8

在Scala 2.8中,我有一个不可变的映射,每个键有多个值:

Map[T,Iterable[U]]
Run Code Online (Sandbox Code Playgroud)

有优越的代表性吗?其次,你将如何从中生成这样的地图

Iterable[(T,U)]
Run Code Online (Sandbox Code Playgroud)

?我目前正在使用:

def toGroupedMap[T,U](vals: Iterable[(T,U)]): Map[T,Iterable[U]] =
  vals.groupBy(_._1).map({ case (s,it) => (s,it.map(_._2)) }).toMap
Run Code Online (Sandbox Code Playgroud)

哪个有效,但感觉笨重.

编辑:我应该指定我正在使用不可变数据.是否存在与MultiMap不可变的等价物?

Rex*_*err 4

如果您真的不需要不变性,那么正如其他人所说,MultiMap这是正确的选择。如果您确实需要不变性,那么您所采取的方法就和其他方法一样简单;没有任何内置的东西(AFAIK),并且任何不可变的 MultiMap 的创建都将比您在那里的方法花费更多的工作。

表示是否优越取决于您的使用情况。您是否经常想要用一个键对应所有值来执行操作?您可以在地图中多次插入相同的值吗?如果两者都是,那么您的代表就是正确的。

如果您希望在一个键上最多插入一次相同的值,那么您应该使用Set[U]而不是(可以通过添加toIterable[U]轻松完成)。.toSetit.map(_._2)

如果你不喜欢处理集合/迭代并且只是忍受它(即你真的宁愿只拥有键值对而不是键集值对),那么你必须在映射周围编写一个包装类提供一个单一的映射接口,并且可以使用 +、- 和迭代器做正确的事情。

下面是一个比我预想的要长一些的示例(这里的格式是为了剪切并粘贴到 REPL 中):

import scala.collection._
class MapSet[A,B](
  val sets: Map[A,Set[B]] = Map[A,Set[B]]()
) extends Map[A,B] with MapLike[A,B,MapSet[A,B]] {
  def get(key: A) = sets.getOrElse(key,Set[B]()).headOption
  def iterator = new Iterator[(A,B)] {
    private val seti = sets.iterator
    private var thiskey:Option[A] = None
    private var singles:Iterator[B] = Nil.iterator
    private def readyNext {
      while (seti.hasNext && !singles.hasNext) {
        val kv = seti.next
        thiskey = Some(kv._1)
        singles = kv._2.iterator
      }
    }
    def hasNext = {
      if (singles.hasNext) true
      else {
        readyNext
        singles.hasNext
      }
    }
    def next = {
      if (singles.hasNext) (thiskey.get , singles.next)
      else {
        readyNext
        (thiskey.get , singles.next)
      }
    }
  }
  def +[B1 >: B](kv: (A,B1)):MapSet[A,B] = {
    val value:B = kv._2.asInstanceOf[B]
    new MapSet( sets + ((kv._1 , sets.getOrElse(kv._1,Set[B]()) + value)) )
  }
  def -(key: A):MapSet[A,B] = new MapSet( sets - key )
  def -(kv: (A,B)):MapSet[A,B] = {
    val got = sets.get(kv._1)
    if (got.isEmpty || !got.get.contains(kv._2)) this
    else new MapSet( sets + ((kv._1 , got.get - kv._2)) )
  }
  override def empty = new MapSet( Map[A,Set[B]]() )
}
Run Code Online (Sandbox Code Playgroud)

我们可以看到它按预期工作,如下所示:

scala> new MapSet() ++ List(1->"Hi",2->"there",1->"Hello",3->"Bye")
res0: scala.collection.Map[Int,java.lang.String] = Map(1 -> Hi, 1 -> Hello, 2 -> there, 3 -> Bye)

scala> res0 + (2->"ya")
res1: scala.collection.Map[Int,java.lang.String] = Map(1 -> Hi, 1 -> Hello, 2 -> there, 2 -> ya, 3 -> Bye)

scala> res1 - 1
res2: scala.collection.Map[Int,java.lang.String] = Map(2 -> there, 2 -> ya, 3 -> Bye)
Run Code Online (Sandbox Code Playgroud)

(尽管如果您想在 ++ 之后返回 MapSet,则需要重写 ++;Map 层次结构没有自己的构建器来处理此类事情)。