我有一个可迭代的val pairs: Iterable[Pair[Key, Value]]
,它有一些key =>值对。
现在,我想创建一个Map[Key, Iterable[Value]]
,每个密钥对Iterable
中给定密钥的所有值都有pairs
。 (我实际上不需要Seq
,任何Iterable
都可以。)
我可以使用mutable Map
和/或使用mutable ListBuffer
来实现。
然而,每个人都告诉我,“正确”的scala没有使用可变集合。那么,是否可以仅使用不可变集合来执行此操作? (例如,使用map
,foldLeft
等)
答案 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))
似乎Vector
是breakOut
的默认值,但可以肯定的是,您可以在左侧指定返回类型: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)
。