タプルのリストをマップに変換します(重複キーを処理しますか?)


90

重複したキー[("a","b"),("c","d"),("a","f")]を持つタプルのリストをマップに変換するための素晴らしい方法を考えていました("a" -> ["b", "f"], "c" -> ["d"])。通常(Pythonの場合)、空のマップとfor-loopをリストに作成し、重複するキーをチェックします。しかし、私はここでもっとスカラっぽくて賢い解決策を探しています。

ところで、ここで使用するKey-Valueの実際のタイプは次のとおり(Int, Node)です。(Int -> NodeSeq)

回答:


78

グループ化してからプロジェクト:

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))

使用するのがよりscalish方法のような方法で、倍そこ(スキップmap fステップを)。


124

重複を想定していないか、デフォルトの重複処理ポリシーで問題がないGoogle社員の場合

List("a" -> 1, "b" -> 2).toMap
// Result: Map(a -> 1, c -> 2)

2.12以降、デフォルトのポリシーは次のとおりです。

重複するキーは、後のキーで上書きされます。これが順序付けられていないコレクションの場合、結果のマップにあるキーは未定義です。


56

ここに別の選択肢があります:

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

これは私たちにMap[String, SeqView[String,Seq[_]]]...意図的なものですか?
Luigi Plinge

1
@LuigiPlinge A SeqView[String,Seq[_]]Seq[String]です。まだ後回しなので、それは価値があるとは思わないので、を削除しましたviewmapValuesとにかく値を表示します。
ダニエルC.ソブラル

これは私のケース(courseraの宿題)で完全に機能しました:レイジーval dictionaryByOccurrences:Map [Occurrences、List [Word]] = {valペア= for(curWord <-辞書)の結果{val curWordOccurrences = wordOccurrences(curWord)(curWordOccurrences、 curWord)} pairs.groupBy(._1).mapValues( .map(_._ 2))}
JasonG

mapValuesは、新しいマップではなく、マップのビューを返しますscala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber

1
使用されるたびに式が再計算されるx.groupBy(_._1).mapValues(_.map(_._2)).map(identity)ため、おそらく必要ですmapValuesissues.scala-lang.org/browse/SI-7005を
ジェフリーアギレラ

20

重複を気にするGoogle社員の場合:

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")) 

12

以降Scala 2.13、ほとんどのコレクションにはgroupMapメソッドが提供されています。これは(名前が示すように)のgroupBy後に続くと同等(より効率的)ですmapValues

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

この:

  • groupタプルの最初の部分(グループマップのグループ部分)に基づくs要素

  • maps 2番目のタプル部分(グループMapのマップ部分)を使用してグループ化された値

これは同等ですlist.groupBy(_._1).mapValues(_.map(_._2))が、リストの1つのパスで実行されます。


4

これは、タプルのリストを重複キーを処理するマップに変換する、よりScalaの慣用的な方法です。折り目を使いたい。

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ステートメント "折りたたみをよりスケーラブルに使用する方法(マップのfステップをスキップ)。"
cevaris 2016年

私は論理的な議論を望んでいた;-)。om-nom-nomもリンクされた記事も、私の質問の証拠を提供しませんでした。(または、私はそれを逃したのですか?)
Make42

1
@ Make42すべてのモナドはモノイドであり、法律によるモノイドは折りたたみ可能であるため、これを処理するためのよりfpの方法です。fpでは、オブジェクトとイベントはモナドとしてモデル化され、すべてのモナドがgroupByを実装するわけではありません。
2016

4

以下にいくつかの解決策があります。(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およびRDDからプレーンマップへの変換)

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

2

あなたはこれを試すことができます

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.