แปลงรายการทูเพิลเป็นแผนที่ (และจัดการกับคีย์ที่ซ้ำกัน?)


92

ฉันคิดเกี่ยวกับวิธีที่ดีในการแปลงรายการของ tuple กับคีย์ซ้ำลงในแผนที่[("a","b"),("c","d"),("a","f")] ("a" -> ["b", "f"], "c" -> ["d"])โดยปกติ (ใน python) ฉันจะสร้างแผนที่ว่างและ for-loop ในรายการและตรวจหาคีย์ที่ซ้ำกัน แต่ฉันกำลังมองหาวิธีแก้ปัญหาที่ชาญฉลาดและน่ากลัวมากกว่าที่นี่

btw ประเภทคีย์ - ค่าจริงที่ฉันใช้ที่นี่คือ(Int, Node)และฉันต้องการเปลี่ยนเป็นแผนที่(Int -> NodeSeq)

คำตอบ:


79

จัดกลุ่มแล้วโครงการ:

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

วิธีใช้การพับแบบลวก ๆ เพิ่มเติมในลักษณะนี้ (ข้ามmap fขั้นตอน)


125

สำหรับ Googler ที่ไม่คาดว่าจะซ้ำกันหรือพอใจกับนโยบายการจัดการข้อมูลซ้ำที่เป็นค่าเริ่มต้น :

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

ณ วันที่ 2.12 นโยบายเริ่มต้นอ่าน:

คีย์ที่ซ้ำกันจะถูกเขียนทับโดยคีย์ในภายหลัง: หากนี่เป็นคอลเล็กชันที่ไม่มีการเรียงลำดับคีย์ใดที่อยู่ในแผนที่ผลลัพธ์จะไม่ได้กำหนดไว้


58

นี่เป็นทางเลือกอื่น:

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

นี่ทำให้เราMap[String, SeqView[String,Seq[_]]]... นี่คือเจตนา?
Luigi Plinge

1
@LuigiPlinge A SeqView[String,Seq[_]]ยังเป็นSeq[String]. ยังคงมองย้อนกลับไปฉันไม่คิดว่ามันคุ้มค่าดังนั้นฉันจึงลบไฟล์view. mapValuesจะทำการดูค่าต่อไป
Daniel C. Sobral

สิ่งนี้ทำได้ดีมากสำหรับกรณีของฉัน (การบ้าน coursera): lazy val dictionaryByOccurrences: Map [Occurrences, List [Word]] = {val pair = for (curWord <- dictionary) ให้ผลตอบแทน {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

1
อาจต้องการx.groupBy(_._1).mapValues(_.map(_._2)).map(identity)เนื่องจากmapValuesนิพจน์จะถูกคำนวณใหม่ทุกครั้งที่ใช้ ดูissue.scala-lang.org/browse/SI-7005
Jeffrey Aguilera

20

สำหรับ Googler ที่สนใจเกี่ยวกับรายการที่ซ้ำกัน:

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องค์ประกอบตามส่วนแรกของสิ่งที่เพิ่มขึ้น (ส่วนกลุ่มของแผนที่กลุ่ม )

  • mapค่าที่จัดกลุ่มโดยใช้ส่วนทูเปิลที่สอง (ส่วนแผนที่ของแผนที่กลุ่ม)

สิ่งนี้เทียบเท่าlist.groupBy(_._1).mapValues(_.map(_._2))แต่ดำเนินการในการส่งผ่านรายการเดียว


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
ทำไมคุณถึงคิดว่านี่เป็นสไตล์ Scala มากกว่าโซลูชัน groupBy-mapValue ที่มีให้ที่นี่
ทำให้ 42

คำสั่ง @ om-nom-nom "วิธีใช้การพับแบบปรับขนาดได้มากขึ้นในลักษณะนี้
cevaris

ฉันหวังว่าจะมีการโต้แย้งเชิงตรรกะ ;-) ทั้ง om-nom-nom หรือบทความที่เชื่อมโยงไม่ได้ให้หลักฐานสำหรับคำถามของฉัน (หรือว่าฉันพลาดไป)
ทำให้ 42

1
@ Make42 เป็นวิธีที่มากกว่า fp ในการจัดการกับสิ่งนี้เนื่องจาก monads ทั้งหมดเป็นแบบโมโนและ monoids ตามกฎหมายสามารถพับเก็บได้ ใน fp วัตถุและเหตุการณ์จะถูกจำลองเป็น monads และไม่ใช่ทุก monads ที่จะใช้ groupBy
soote

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 Variation - สำหรับชุดข้อมูลขนาดใหญ่ (การแปลงเป็น 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.