将元组列表转换为映射(并处理重复键?)

100

我在考虑一种将具有重复键的元组列表[("a","b"),("c","d"),("a","f")]转换成Map ("a" -> ["b", "f"], "c" -> ["d"])的好方法。通常(在Python中),我会创建一个空映射并在列表上进行循环,检查是否存在重复键。但是我正在寻找更Scala风格和聪明的解决方案。

顺便说一下,我在这里使用的键值对的实际类型是(Int, Node),我想将其转换为(Int -> NodeSeq)的映射。

8个回答

139
对于不希望出现重复内容或对默认的重复处理政策没有意见的人来说:
List("a" -> 1, "b" -> 2, "a" -> 3).toMap
// Result: Map(a -> 3, b -> 2)

从2.12版本开始,默认策略如下:

重复的键将被后面的键覆盖:如果这是一个无序集合,则结果映射中的键未定义。


85

分组,然后投影:

scala> val x = List("a" -> "b", "c" -> "d", "a" -> "f")
//x: List[(java.lang.String, java.lang.String)] = List((a,b), (c,d), (a,f))
scala> x.groupBy(_._1).map { case (k,v) => (k,v.map(_._2))}
//res1: scala.collection.immutable.Map[java.lang.String,List[java.lang.String]] = Map(c -> List(d), a -> List(b, f))

使用fold的更多Scala风格的方式,类似于这里(跳过map f步骤)。


59

这是另一种选择:

x.groupBy(_._1).mapValues(_.map(_._2))

这给了我们一个 Map[String, SeqView[String,Seq[_]]]... 这是有意为之的吗? - Luigi Plinge
1
@LuigiPlinge SeqView[String,Seq[_]] 也是一个 Seq[String]。但回想起来,我认为这并不值得,所以我删除了 viewmapValues 在值上会自动执行视图。 - Daniel C. Sobral
这段代码完美地解决了我的问题(Coursera作业):lazy val dictionaryByOccurrences: Map[Occurrences, List[Word]] = { val pairs = for (curWord <- dictionary) yield { val curWordOccurrences = wordOccurrences(curWord) (curWordOccurrences, curWord) } pairs.groupBy(.1).mapValues(.map(._2)) } - JasonG
mapValues 返回的是一个映射视图,而不是一个新的映射。http://www.scala-lang.org/api/current/index.html#scala.collection.Map - Max Heiber
1
可能需要使用 x.groupBy(_._1).mapValues(_.map(_._2)).map(identity),因为 mapValues 表达式每次使用时都会重新计算。请参见 https://issues.scala-lang.org/browse/SI-7005 - Jeffrey Aguilera

20

对于在意重复内容的谷歌员工:

implicit class Pairs[A, B](p: List[(A, B)]) {
  def toMultiMap: Map[A, List[B]] = p.groupBy(_._1).mapValues(_.map(_._2))
}

> List("a" -> "b", "a" -> "c", "d" -> "e").toMultiMap
> Map("a" -> List("b", "c"), "d" -> List("e")) 

20

从Scala 2.13开始,大多数集合都提供了groupMap方法,它是(顾名思义)groupBymapValues的等效(更高效)方法:

List("a" -> "b", "c" -> "d", "a" -> "f").groupMap(_._1)(_._2)
// Map[String,List[String]] = Map(a -> List(b, f), c -> List(d))

这个函数:

  • 根据元组的第一部分(groupMap的组部分)对元素进行分组

  • 通过获取它们的第二个元组部分(groupMap的映射部分),将已分组的值进行映射

这相当于使用list.groupBy(_._1).mapValues(_.map(_._2)),但是在列表中只执行一次


6

以下是几个解决方案。(GroupBy、FoldLeft、Aggregate、Spark)

val list: List[(String, String)] = List(("a","b"),("c","d"),("a","f"))

GroupBy 变体

list.groupBy(_._1).map(v => (v._1, v._2.map(_._2)))

左折叠变种

list.foldLeft[Map[String, List[String]]](Map())((acc, value) => {
  acc.get(value._1).fold(acc ++ Map(value._1 -> List(value._2))){ v =>
    acc ++ Map(value._1 -> (value._2 :: v))
  }
})

聚合变化 - 类似于左折叠

list.aggregate[Map[String, List[String]]](Map())(
  (acc, value) => acc.get(value._1).fold(acc ++ Map(value._1 -> 
    List(value._2))){ v =>
     acc ++ Map(value._1 -> (value._2 :: v))
  },
  (l, r) => l ++ r
)

Spark变换 - 适用于大数据集(从RDD转换为普通Map)

import org.apache.spark.rdd._
import org.apache.spark.{SparkContext, SparkConf}

val conf: SparkConf = new 
SparkConf().setAppName("Spark").setMaster("local")
val sc: SparkContext = new SparkContext (conf)

// This gives you a rdd of the same result
val rdd: RDD[(String, List[String])] = sc.parallelize(list).combineByKey(
   (value: String) => List(value),
   (acc: List[String], value) => value :: acc,
   (accLeft: List[String], accRight: List[String]) => accLeft ::: accRight
)

// To convert this RDD back to a Map[(String, List[String])] you can do the following
rdd.collect().toMap

5

这是一种更符合Scala习惯的方式,用于将元组列表转换为处理重复键的映射。您需要使用fold。

val x = List("a" -> "b", "c" -> "d", "a" -> "f")

x.foldLeft(Map.empty[String, Seq[String]]) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, Seq.empty[String]) ++ Seq(v))
}

res0: scala.collection.immutable.Map[String,Seq[String]] = Map(a -> List(b, f), c -> List(d))

1
你为什么认为这比这里提供的groupBy-mapValue解决方案更符合Scala风格? - Make42
@om-nom-nom语句:“使用折叠的更多Scalish方式,就像跳过map f步骤那样。” - cevaris
我希望得到一个有逻辑的论据 ;-). 无论是om-nom-nom还是链接的文章都没有为我的问题提供证据。 (或者我错过了吗?) - Make42
1
@Make42 这是一种更函数式的方法来处理这个问题,因为所有单子都是幺半群,而且幺半群根据法则都可以折叠。在函数式编程中,对象和事件被建模为单子,而不是所有单子都实现了 groupBy。 - soote

1
你可以尝试这个:

你可以尝试这个

scala> val b = new Array[Int](3)
// b: Array[Int] = Array(0, 0, 0)
scala> val c = b.map(x => (x -> x * 2))
// c: Array[(Int, Int)] = Array((1,2), (2,4), (3,6))
scala> val d = Map(c : _*)
// d: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 4, 3 -> 6)

这个程序如何“处理重复键”? - ksceriath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接