3

我有一个 iterable val pairs: Iterable[Pair[Key, Value]],它有一些 key=>value 对。

现在,我想Map[Key, Iterable[Value]]为每个键创建一个 ,它具有Iterable给定键 in 的所有值的一个pairs。(我实际上不需要 a Seq,任何Iterable都可以)。

我可以使用 mutableMap和/或使用 mutable来做到这一点ListBuffer

但是,每个人都告诉我,“正确的”scala 没有使用可变集合。那么,是否可以仅使用不可变集合来执行此操作?(例如,使用map,foldLeft等)

4

5 回答 5

5

我找到了一个非常简单的方法来做到这一点

pairs.groupBy{_._1}.mapValues{_.map{_._2}}

就是这样。

于 2012-07-02T21:47:01.563 回答
4

你可以用非循环可变数据结构做的任何事情,你也可以用不可变数据结构做。诀窍很简单:

loop -> recursion or fold
mutating operation -> new-copy-with-change-made operation

因此,例如,在您的情况下,您可能Iterable每次都在循环并添加一个值。如果我们应用我们得心应手的技巧,我们

def mkMap[K,V](data: Iterable[(K,V)]): Map[K, Iterable[V]] = {
  @annotation.tailrec def mkMapInner(
    data: Iterator[(K,V)],
    map: Map[K,Vector[V]] = Map.empty[K,Vector[V]]
  ): Map[K,Vector[V]] = {
    if (data.hasNext) {
      val (k,v) = data.next
      mkMapInner(data, map + (k -> map.get(k).map(_ :+ v).getOrElse(Vector(v))))
    }
    else map
  }
  mkMapInner(data.iterator)
}

在这里,我选择通过声明递归内部方法来实现循环替换(使用 @annotation.tailrec 检查递归是否优化为 while 循环,因此它不会破坏堆栈)

让我们测试一下:

val pairs = Iterable((1,"flounder"),(2,"salmon"),(1,"halibut"))
scala> mkMap(pairs)
res2: Map[Int,Iterable[java.lang.String]] = 
      Map(1 -> Vector(flounder, halibut), 2 -> Vector(salmon))

现在,事实证明 Scala 的集合库也包含一些有用的东西:

scala> pairs.groupBy(_._1).mapValues{ _.map{_._2 } }

作为groupBy关键方法,其余的将它产生的内容清理成你想要的形式。

于 2012-07-02T21:46:02.827 回答
3

作为记录,你可以用fold把它写得很干净。我将假设您Pair是标准库(又名Tuple2)中的那个:

pairs.foldLeft(Map.empty[Key, Seq[Value]]) {
  case (m, (k, v)) => m.updated(k, m.getOrElse(k, Seq.empty) :+ v)
}

当然,在这种情况下,这种groupBy方法更方便。

于 2012-07-02T22:11:30.893 回答
1
val ps = collection.mutable.ListBuffer(1 -> 2, 3 -> 4, 1 -> 5)

ps.groupBy(_._1).mapValues(_ map (_._2))
  // = Map(1 -> ListBuffer(2, 5), 3 -> ListBuffer(4))

这在输出映射中给出了一个可变的。 ListBuffer如果您希望您的输出是不可变的(不确定这是否完全符合您的要求),请使用collection.breakOut

ps.groupBy(_._1).mapValues(_.map(_._2)(collection.breakOut))
   // = Map(1 -> Vector(2, 5), 3 -> Vector(4))

似乎Vector是 的默认值breakOut,但可以肯定的是,您可以在左侧指定返回类型:val myMap: Map[Int,Vector[Int]] = ....

更多关于breakOut的信息在这里

作为一种方法:

def immutableGroup[A,B](xs: Traversable[(A,B)]): Map[A,Vector[B]] =
  xs.groupBy(_._1).mapValues(_.map(_._2)(collection.breakOut))
于 2012-07-03T02:43:17.580 回答
0

我经常执行此功能,以至于我有一个隐式的书面调用groupByKey,它正是这样做的:

class EnrichedWithGroupByKey[A, Repr <: Traversable[A]](self: TraversableLike[A, Repr]) {
  def groupByKey[T, U, That](implicit ev: A <:< (T, U), bf: CanBuildFrom[Repr, U, That]): Map[T, That] =
    self.groupBy(_._1).map { case (k, vs) => k -> (bf(self.asInstanceOf[Repr]) ++= vs.map(_._2)).result }
}
implicit def enrichWithGroupByKey[A, Repr <: Traversable[A]](self: TraversableLike[A, Repr]) = new EnrichedWithGroupByKey[A, Repr](self)

你像这样使用它:

scala> List(("a", 1), ("b", 2), ("b", 3), ("a", 4)).groupByKey
res0: Map[java.lang.String,List[Int]] = Map(a -> List(1, 4), b -> List(2, 3))

请注意,我使用.map { case (k, vs) => k -> ... }而不是mapValues因为mapValues创建了一个视图,而不是立即执行地图。如果您计划多次访问这些值,您将希望避免使用视图方法,因为这意味着.map(_._2)每次都重新计算。

于 2012-07-03T04:39:03.383 回答