Scala'da bir haritayı ters çevirmenin zarif yolu


97

Şu anda Scala öğreniyor ve bazı tersine çevrilmiş değer-> anahtar aramaları yapmak için bir Haritayı ters çevirmek gerekiyor. Bunu yapmanın basit bir yolunu arıyordum ama sadece şunu buldum:

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

Daha zarif bir yaklaşımı olan var mı?

Yanıtlar:


174

Değerlerin benzersiz olduğunu varsayarsak, bu işe yarar:

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

Scala 2.8'de ise daha kolaydır:

origMap.map(_.swap)

Bunu yapabilmek, Scala 2.8'in yeni bir koleksiyon kitaplığına sahip olmasının nedenlerinden biridir.


1
Dikkatli! Bu çözümle değerleri kaybedebilirsiniz. Örneğin Map(1 -> "A", 2 -> "B", 3 -> "B").map(_.swap),Map(A -> 1, B -> 3)
dev-null

1
@ dev-null Özür dilerim, ancak örneğiniz gerekli varsayıma girmiyor.
Daniel C. Sobral

Katılıyorum. Üzgünüm, bunu gözden kaçırdım.
dev-null

45

Matematiksel olarak, eşleme tersine çevrilemeyebilir (enjekte edici), örneğin, Map[A,B]alamazsınız Map[B,A], daha çok elde edersiniz Map[B,Set[A]], çünkü aynı değerlerle ilişkili farklı anahtarlar olabilir. Yani, tüm anahtarları bilmekle ilgileniyorsanız, işte kod:

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)daha okunaklı olurdu.keys
cheezsteak

Şimdi senin sayende, hatta birkaç karakter daha kısa. Şimdi fark, eskisi gibi Sets yerine Lists almanızdır.
Rok Kralj

1
.mapValuesBir görünüm döndürdüğü için kullanırken dikkatli olun . Bazen istediğiniz şey budur, ancak dikkatli olmazsanız çok fazla bellek ve CPU tüketebilir. Onu bir haritaya zorlamak için yapabilirsin m.groupBy(_._2).mapVaues(_.keys).map(identity)veya çağrıyı .mapValues(_.keys)ile değiştirebilirsin .map { case (k, v) => k -> v.keys }.
Mark T.

10

Birkaç şekilde yineleme yaparken ._1 öğelerini önleyebilirsiniz.

İşte bir yol. Bu, harita için önemli olan tek ve tek durumu kapsayan kısmi bir işlev kullanır:

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

İşte başka bir yol:

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

Eşleme yinelemesi, iki öğeli bir diziye sahip bir işlevi çağırır ve anonim işlev iki parametre ister. Function.tupled çeviriyi yapar.


6

Buraya, Harita [A, Seq [B]] türündeki bir Haritayı, [B, Seq [A]] Haritasına çevirmenin bir yolunu aramaya geldim; burada yeni haritadaki her B, eski haritadaki her A ile ilişkilendirilir. ki B, A'nın ilişkili dizisinde yer alıyordu.

Örneğin,
Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
tersine çevirir
Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))

İşte benim çözümüm:

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'in türü Map[A, Seq[B]]ve newMap'in türü olduğuMap[B, Seq[A]]

İç içe kıvrım solları beni biraz rahatsız ediyor, ancak bu, bu tür bir ters çevirmeyi başarmak için bulabildiğim en basit yol. Daha temiz bir çözümü olan var mı?


çok güzel çözüm! : @Rok, onun çözüm seninkinin o dönüşümleri çünkü bence biraz daha nasılsa farklıdır Map[A, Seq[B]]için Map[B, Seq[A]]çözüm trasnforms nereye Map[A, Seq[B]]kadar Map[Seq[B], Seq[A]].
YH

1
Bu durumda, iki iç içe kıvrım olmadan ve daha iyi performans gösterme olasılığı vardır:a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))
Rok Kralj

6

Pekala, bu pek çok iyi cevabı olan çok eski bir soru, ama ben nihai, her şeyden önce, İsviçre çakısı, Mapinvertörü yaptım ve onu göndereceğim yer burası.

Aslında iki invertör. Bireysel değer unsurları için bir tane ...

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

... ve değer koleksiyonları için oldukça benzer bir başka.

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

kullanım:

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

Her iki yöntemin de aynı örtük sınıfta olmasını tercih ederim, ancak onu araştırmaya ne kadar çok zaman harcarsam, o kadar sorunlu göründü.


3

Şunları kullanarak bir haritayı ters çevirebilirsiniz:

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

Bu yaklaşımla ilgili sorun şu ki, haritanızda artık karma anahtarlar haline gelen değerleriniz benzersiz değilse, yinelenen değerleri bırakacaksınız. Göstermek için:

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)

Bundan kaçınmak için haritanızı önce bir tuple listesine dönüştürebilir, ardından ters çevirebilirsiniz, böylece yinelenen değerleri düşürmezsiniz:

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

Ölçeklendirmede 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)

Haritaya yapılan son eklemenin yinelenen değerlerin üzerine yazılacağını unutmayın:

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

Başlangıç ​​olarak Scala 2.13, aynı değerlerle ilişkili anahtarları kaybetmeden anahtar / değerleri takas etmek için , (adından da anlaşılacağı gibi) gruplanmış öğeler üzerinde a ve ping'e eşdeğer olan Mapyeni groupMap yöntemini kullanabiliriz .groupBymap

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

Bu:

  • groups öğeleri, ikinci tuple bölümlerine ( _._2) ( grup Haritasının grup bölümü ) göre

  • mapöğeleri ilk tuple bölümlerini alarak grupladılar ( _._1) (grup Haritasının harita bölümü )

Bu olarak görülebilir tek geçişli sürümü arasında map.groupBy(_._2).mapValues(_.map(_._1)).


Çok kötü dönüşümü olmaz Map[K, C[V]]içine Map[V, C[K]].
jwvh

0
  1. Ters, bu işlem için tersinden daha iyi bir addır ("matematiksel bir işlevin tersi" gibi)

  2. Bu ters dönüşümü genellikle yalnızca haritalarda değil, diğer (Seq dahil) koleksiyonlarda da yapıyorum. En iyisi, ters işlemimin tanımını bire bir haritalarla sınırlamamaktır. Haritalar için çalıştığım tanım şu şekildedir (lütfen uygulamam için iyileştirmeler önerin).

    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
    }

Bire bir haritaysa, Harita [B, Liste [A]] yerine önemsiz bir şekilde test edilebilen ve bir Haritaya [B, A] dönüştürülebilen tekli listeler elde edersiniz.


0

foldLeftÇarpışmaları giderecek ve haritayı tek geçişte ters çevirecek bu işlevi kullanmayı deneyebiliriz .

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))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.