วิธีที่หรูหราในการกลับแผนที่ใน Scala


98

การเรียนรู้ Scala ในปัจจุบันและจำเป็นในการสลับแผนที่เพื่อทำการค้นหาคีย์กลับค่า ฉันกำลังมองหาวิธีง่ายๆในการทำสิ่งนี้ แต่มีเพียง:

(Map() ++ origMap.map(kvp=>(kvp._2->kvp._1)))

ใครมีแนวทางที่สง่างามกว่านี้?

คำตอบ:


176

สมมติว่าค่าไม่ซ้ำกันการทำงานนี้:

(Map() ++ origMap.map(_.swap))

อย่างไรก็ตามใน Scala 2.8 จะง่ายกว่า:

origMap.map(_.swap)

ความสามารถดังกล่าวเป็นส่วนหนึ่งของสาเหตุที่ Scala 2.8 มีไลบรารีคอลเลกชันใหม่


1
ระวัง! คุณสามารถหลวมค่าด้วยวิธีนี้ ตัวอย่างเช่นMap(1 -> "A", 2 -> "B", 3 -> "B").map(_.swap)ผลลัพธ์ในMap(A -> 1, B -> 3)
dev-null

1
@ dev-null ฉันขอโทษ แต่ตัวอย่างของคุณไม่อยู่ภายใต้สมมติฐานที่กำหนด
Daniel C. Sobral

ฉันเห็นด้วย. ขอโทษนะฉันมองข้ามเรื่องนั้นไปแล้ว
dev-null

46

ในทางคณิตศาสตร์การแมปอาจไม่สามารถกลับด้านได้ (แบบฉีด) เช่นจากMap[A,B]คุณไม่สามารถรับได้Map[B,A]แต่คุณจะได้รับMap[B,Set[A]]เนื่องจากอาจมีคีย์ที่แตกต่างกันที่เกี่ยวข้องกับค่าเดียวกัน ดังนั้นหากคุณสนใจที่จะรู้คีย์ทั้งหมดนี่คือรหัส:

scala> val m = Map(1 -> "a", 2 -> "b", 4 -> "b")
scala> m.groupBy(_._2).mapValues(_.keys)
res0: Map[String,Iterable[Int]] = Map(b -> Set(2, 4), a -> Set(1))

2
.map(_._1)จะถูกต้องตามกฎหมายมากกว่า.keys
cheezsteak

ตอนนี้ต้องขอบคุณคุณแม้แต่ตัวอักษรที่สั้นกว่าเล็กน้อย ความแตกต่างตอนนี้คือคุณได้Sets แทนที่จะListเป็น s เหมือนเมื่อก่อน
Rok Kralj

1
โปรดใช้ความระมัดระวังในการใช้.mapValuesเนื่องจากส่งคืนมุมมอง ในบางครั้งนี่คือสิ่งที่คุณต้องการ แต่ถ้าคุณไม่ระวังมันอาจใช้หน่วยความจำและ CPU จำนวนมาก เพื่อบังคับให้มันลงไปในแผนที่คุณสามารถทำm.groupBy(_._2).mapVaues(_.keys).map(identity)หรือคุณอาจจะเปลี่ยนเรียกร้องให้มี.mapValues(_.keys) .map { case (k, v) => k -> v.keys }
Mark T.

.mapValues(_.keySet)เป็นตัวเลือกที่ดีในการรับชุดแทนที่จะเป็นแบบทำ
ซ้ำได้

10

คุณสามารถหลีกเลี่ยงสิ่ง ._1 ในขณะที่ทำซ้ำได้ไม่กี่วิธี

นี่เป็นวิธีเดียว สิ่งนี้ใช้ฟังก์ชันบางส่วนที่ครอบคลุมกรณีเดียวที่สำคัญสำหรับแผนที่:

Map() ++ (origMap map {case (k,v) => (v,k)})

นี่เป็นอีกวิธีหนึ่ง:

import Function.tupled        
Map() ++ (origMap map tupled {(k,v) => (v,k)})

การวนซ้ำแผนที่เรียกฟังก์ชันที่มีทูเพิลสององค์ประกอบและฟังก์ชันที่ไม่ระบุชื่อต้องการสองพารามิเตอร์ Function.tupled ทำการแปล


6

ฉันมาที่นี่เพื่อหาวิธีเปลี่ยนแผนที่ประเภท Map [A, Seq [B]] ไปที่ Map [B, Seq [A]] โดยที่ B แต่ละอันในแผนที่ใหม่จะเชื่อมโยงกับทุก A ในแผนที่เก่าสำหรับ ซึ่ง B อยู่ในลำดับที่เกี่ยวข้องของ A

เช่น
Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
จะเปลี่ยนกลับเป็น
Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))

นี่คือวิธีแก้ปัญหาของฉัน:

val newMap = oldMap.foldLeft(Map[B, Seq[A]]().withDefaultValue(Seq())) {
  case (m, (a, bs)) => bs.foldLeft(m)((map, b) => map.updated(b, m(b) :+ a))
}

โดย oldMap เป็นประเภทMap[A, Seq[B]]และ newMap เป็นประเภทMap[B, Seq[A]]

การพับที่ซ้อนกันด้านซ้ายทำให้ฉันประจบประแจงเล็กน้อย แต่นี่เป็นวิธีที่ตรงไปตรงมาที่สุดที่ฉันสามารถหาได้เพื่อบรรลุการผกผันประเภทนี้ ใครมีวิธีที่สะอาดกว่านี้


ทางออกที่ดีมาก! @ โร้กวิธีแก้ปัญหาของเขาแตกต่างจากของคุณเล็กน้อยฉันคิดว่าเพราะเขาเปลี่ยน: Map[A, Seq[B]]ไปMap[B, Seq[A]]ที่โซลูชันของคุณเป็นMap[A, Seq[B]]ไปMap[Seq[B], Seq[A]]ตามรูปแบบ
YH

1
ในกรณีนี้หากไม่มีการพับซ้อนกันสองครั้งและเป็นไปได้ที่จะมีประสิทธิภาพมากขึ้น:a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))
Rok Kralj

6

ตกลงนี่เป็นคำถามที่เก่ามากพร้อมคำตอบที่ดีมากมาย แต่ฉันได้สร้างสุดยอดมีดสวิส - กองทัพ - มีดMapอินเวอร์เตอร์และนี่คือสถานที่ที่จะโพสต์

จริงๆแล้วมันคืออินเวอร์เตอร์สองตัว หนึ่งสำหรับองค์ประกอบคุณค่าส่วนบุคคล ...

//from Map[K,V] to Map[V,Set[K]], traverse the input only once
implicit class MapInverterA[K,V](m :Map[K,V]) {
  def invert :Map[V,Set[K]] =
    m.foldLeft(Map.empty[V, Set[K]]) {
      case (acc,(k, v)) => acc + (v -> (acc.getOrElse(v,Set()) + k))
    }
}

... และอีกอย่างที่ค่อนข้างคล้ายกันสำหรับคอลเลกชันมูลค่า

import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.Builder
import scala.language.higherKinds

//from Map[K,C[V]] to Map[V,C[K]], traverse the input only once
implicit class MapInverterB[K,V,C[_]](m :Map[K,C[V]]
                                     )(implicit ev :C[V] => TraversableOnce[V]) {
  def invert(implicit bf :CanBuildFrom[Nothing,K,C[K]]) :Map[V,C[K]] =
    m.foldLeft(Map.empty[V, Builder[K,C[K]]]) {
      case (acc, (k, vs)) =>
        vs.foldLeft(acc) {
          case (a, v) => a + (v -> (a.getOrElse(v,bf()) += k))
        }
    }.mapValues(_.result())
}

การใช้งาน:

Map(2 -> Array('g','h'), 5 -> Array('g','y')).invert
//res0: Map(g -> Array(2, 5), h -> Array(2), y -> Array(5))

Map('q' -> 1.1F, 'b' -> 2.1F, 'c' -> 1.1F, 'g' -> 3F).invert
//res1: Map(1.1 -> Set(q, c), 2.1 -> Set(b), 3.0 -> Set(g))

Map(9 -> "this", 8 -> "that", 3 -> "thus", 2 -> "thus").invert
//res2: Map(this -> Set(9), that -> Set(8), thus -> Set(3, 2))

Map(1L -> Iterator(3,2), 5L -> Iterator(7,8,3)).invert
//res3: Map(3 -> Iterator(1, 5), 2 -> Iterator(1), 7 -> Iterator(5), 8 -> Iterator(5))

Map.empty[Unit,Boolean].invert
//res4: Map[Boolean,Set[Unit]] = Map()

ฉันอยากจะมีทั้งสองวิธีในระดับนัยเดียวกัน แต่ยิ่งฉันใช้เวลาในการตรวจสอบมากเท่าไหร่ก็ยิ่งมีปัญหามากขึ้นเท่านั้น


3

คุณสามารถพลิกกลับแผนที่โดยใช้:

val i = origMap.map({case(k, v) => v -> k})

ปัญหาในแนวทางนี้คือหากค่าของคุณซึ่งตอนนี้กลายเป็นกุญแจแฮชในแผนที่ของคุณไม่ซ้ำกันคุณจะทิ้งค่าที่ซ้ำกัน เพื่อเป็นตัวอย่าง:

scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 1)
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3, d -> 1)

// Notice that 1 -> a is not in our inverted map
scala> val i = m.map({ case(k , v) => v -> k})
i: scala.collection.immutable.Map[Int,String] = Map(1 -> d, 2 -> b, 3 -> c)

เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถแปลงแผนที่ของคุณเป็นรายการสิ่งที่เพิ่มขึ้นก่อนจากนั้นจึงกลับด้านเพื่อที่คุณจะไม่ทิ้งค่าที่ซ้ำกัน:

scala> val i = m.toList.map({ case(k , v) => v -> k})
i: List[(Int, String)] = List((1,a), (2,b), (3,c), (1,d))

1

ใน scala REPL:

scala> val m = Map(1 -> "one", 2 -> "two")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two)

scala> val reversedM = m map { case (k, v) => (v, k) }
reversedM: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)

โปรดทราบว่าค่าที่ซ้ำกันจะถูกเขียนทับโดยการเพิ่มครั้งสุดท้ายของแผนที่:

scala> val m = Map(1 -> "one", 2 -> "two", 3 -> "one")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two, 3 -> one)

scala> val reversedM = m map { case (k, v) => (v, k) }
reversedM: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 3, two -> 2)

1

เริ่มต้นScala 2.13เพื่อที่จะสลับคีย์ / ค่าโดยไม่สูญเสียคีย์ที่เกี่ยวข้องกับค่าเดียวกันเราสามารถใช้เมธอดgroupMapMapใหม่ซึ่ง (ตามชื่อแนะนำ) เทียบเท่ากับ a และping เหนือรายการที่จัดกลุ่มgroupBymap

Map(1 -> "a", 2 -> "b", 4 -> "b").groupMap(_._2)(_._1)
// Map("b" -> List(2, 4), "a" -> List(1))

นี้:

  • groupองค์ประกอบตามส่วนทูเปิลที่สอง ( _._2) (ส่วนกลุ่มของแผนที่กลุ่ม )

  • maps จัดกลุ่มรายการโดยรับส่วนทูเปิลแรก ( _._1) (ส่วนแผนที่ของแผนที่กลุ่ม)

สิ่งนี้สามารถเห็นได้ว่าเป็นเวอร์ชันหนึ่งผ่านของmap.groupBy(_._2).mapValues(_.map(_._1))ไฟล์.


เลวร้ายเกินไปก็จะไม่เปลี่ยนเข้าไปMap[K, C[V]] Map[V, C[K]]
jwvh

0
  1. Inverse เป็นชื่อที่ดีกว่าสำหรับการดำเนินการนี้มากกว่าการย้อนกลับ (เช่นเดียวกับ "ผกผันของฟังก์ชันทางคณิตศาสตร์")

  2. ฉันมักจะทำการแปลงแบบผกผันนี้ไม่เพียง แต่บนแผนที่เท่านั้น แต่ยังรวมถึงคอลเล็กชันอื่น ๆ (รวมถึง Seq) ด้วย ฉันคิดว่าเป็นการดีที่สุดที่จะไม่ จำกัด คำจำกัดความของการดำเนินการผกผันของฉันไว้ที่แผนที่แบบหนึ่งต่อหนึ่ง นี่คือคำจำกัดความที่ฉันใช้กับแผนที่ (โปรดแนะนำการปรับปรุงการใช้งานของฉัน)

    def invertMap[A,B]( m: Map[A,B] ) : Map[B,List[A]] = {
      val k = ( ( m values ) toList ) distinct
      val v = k map { e => ( ( m keys ) toList ) filter { x => m(x) == e } }
      ( k zip v ) toMap
    }
    

หากเป็นแผนที่แบบตัวต่อตัวคุณจะได้รับรายชื่อซิงเกิลตันซึ่งสามารถทดสอบได้เล็กน้อยและเปลี่ยนเป็นแผนที่ [B, A] แทนที่จะเป็นแผนที่ [B, รายการ [A]]


0

เราสามารถลองใช้foldLeftฟังก์ชั่นนี้ที่จะดูแลการชนและพลิกกลับแผนที่ในการข้ามผ่านครั้งเดียว

scala> def invertMap[A, B](inputMap: Map[A, B]): Map[B, List[A]] = {
     |     inputMap.foldLeft(Map[B, List[A]]()) {
     |       case (mapAccumulator, (value, key)) =>
     |         if (mapAccumulator.contains(key)) {
     |           mapAccumulator.updated(key, mapAccumulator(key) :+ value)
     |         } else {
     |           mapAccumulator.updated(key, List(value))
     |         }
     |     }
     |   }
invertMap: [A, B](inputMap: Map[A,B])Map[B,List[A]]

scala> val map = Map(1 -> 2, 2 -> 2, 3 -> 3, 4 -> 3, 5 -> 5)
map: scala.collection.immutable.Map[Int,Int] = Map(5 -> 5, 1 -> 2, 2 -> 2, 3 -> 3, 4 -> 3)

scala> invertMap(map)
res0: Map[Int,List[Int]] = Map(5 -> List(5), 2 -> List(1, 2), 3 -> List(3, 4))

scala> val map = Map("A" -> "A", "B" -> "A", "C" -> "C", "D" -> "C", "E" -> "E")
map: scala.collection.immutable.Map[String,String] = Map(E -> E, A -> A, B -> A, C -> C, D -> C)

scala> invertMap(map)
res1: Map[String,List[String]] = Map(E -> List(E), A -> List(A, B), C -> List(C, D))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.