仅使用不可变集合创建Iterables映射

时间:2012-07-02 21:26:25

标签: scala

我有一个可迭代的val pairs: Iterable[Pair[Key, Value]],它有一些key =>值对。

现在,我想创建一个Map[Key, Iterable[Value]],每个密钥对Iterable中给定密钥的所有值都有pairs。 (我实际上不需要Seq,任何Iterable都可以。)

我可以使用mutable Map和/或使用mutable ListBuffer来实现。

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

5 个答案:

答案 0 :(得分:5)

我发现了一种非常简单的方法

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

就是这样。

答案 1 :(得分: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为关键方法,其余人将其产生的内容清理成您想要的形式。

答案 2 :(得分:3)

为了记录,你可以用a fold写得非常干净。我假设你的Pair是标准库中的那个(又名Tuple2):

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

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

答案 3 :(得分: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))

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

有关breakOut here的更多信息。

作为一种方法:

def immutableGroup[A,B](xs: Traversable[(A,B)]): Map[A,Vector[B]] =
  xs.groupBy(_._1).mapValues(_.map(_._2)(collection.breakOut))

答案 4 :(得分: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)