代码之家  ›  专栏  ›  技术社区  ›  David Crawshaw

scala中的多值映射

  •  5
  • David Crawshaw  · 技术社区  · 15 年前

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

    Map[T,Iterable[U]]
    

    是否有上级代表?第二,你将如何从

    Iterable[(T,U)]
    

    ?我目前正在使用:

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

    这很有效,但感觉很笨拙。

    编辑: 我应该指定我正在处理不可变的数据。有不变等价于多重映射吗?

    3 回复  |  直到 15 年前
        1
  •  4
  •   Rex Kerr    15 年前

    如果你真的不需要不变性,那么正如其他人所说, MultiMap 是前进的道路。如果你确实需要不变性,那么你所采用的方法和其他方法一样简单;没有任何内置的东西(afaik),任何不可变多重映射的创建都将比你所采用的方法需要更多的工作。

    表示是否优越取决于您的用法。您经常想使用与一个键对应的所有值进行操作吗?可以在地图中多次插入相同的值吗?如果两者都是,则表示正确。

    如果希望在一个键上最多插入一次相同的值,则应使用 Set[U] 而不是 Iterable[U] (可以通过添加 .toSet it.map(_._2) )

    如果您不喜欢处理集合/iterables,并且只是忍受它(即,您实际上更愿意只使用键值对而不是键setofvalues对),那么您必须在映射周围编写一个包装类,该类表示一个映射接口,并使用+、-、迭代器执行正确的操作。

    下面是一个比我预期的要长一点的例子(这里格式化为剪切粘贴到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]]() )
    }
    

    我们可以看到,这是按预期工作的,就像这样:

    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)
    

    (尽管如果你想在++之后得到一个地图集,你需要覆盖++;地图层次结构没有自己的构建者来处理类似的事情)。

        2
  •  2
  •   Randall Schulz    15 年前

    查看多重映射混合以获得映射。

        3
  •  0
  •   David    15 年前

    多地图是你需要的。下面是一个创建一个然后从列表中添加条目的示例[(string,int)]。我相信有一种更漂亮的方式。

    scala> val a = new collection.mutable.HashMap[String, collection.mutable.Set[Int]]() with collection.mutable.MultiMap[String, Int]
    a: scala.collection.mutable.HashMap[String,scala.collection.mutable.Set[Int]] with scala.collection.mutable.MultiMap[String,Int] = Map()
    
    scala> List(("a", 1), ("a", 2), ("b", 3)).map(e => a.addBinding(e._1, e._2))                                                      
    res0: List[scala.collection.mutable.HashMap[String,scala.collection.mutable.Set[Int]] with scala.collection.mutable.MultiMap[String,Int]] = List(Map(a -> Set(1, 2), b -> Set(3)), Map(a -> Set(1, 2), b -> Set(3)), Map(a -> Set(1, 2), b -> Set(3)))
    
    scala> a("a")
    res2: scala.collection.mutable.Set[Int] = Set(1, 2)