102 votes

Une façon élégante d'inverser une carte en Scala

J'apprends actuellement Scala et j'ai besoin d'inverser une Map pour faire des recherches de valeurs->clés inversées. Je cherchais un moyen simple de le faire, mais j'ai trouvé seulement :

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

Quelqu'un a-t-il une approche plus élégante ?

185voto

Daniel C. Sobral Points 159554

En supposant que les valeurs soient uniques, cela fonctionne :

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

Avec Scala 2.8, cependant, c'est plus facile :

origMap.map(_.swap)

C'est en partie pour cette raison que Scala 2.8 dispose d'une nouvelle bibliothèque de collections.

1 votes

Attention ! Vous pouvez perdre des valeurs avec cette solution. Par exemple Map(1 -> "A", 2 -> "B", 3 -> "B").map(_.swap) se traduit par Map(A -> 1, B -> 3)

2 votes

@dev-null Je suis désolé, mais votre exemple ne correspond pas à l'hypothèse requise.

0 votes

Je suis d'accord. Désolé, je l'ai oublié.

47voto

Rok Kralj Points 11593

D'un point de vue mathématique, la cartographie peut ne pas être inversible (injective), par exemple, de Map[A,B] vous ne pouvez pas obtenir Map[B,A] mais vous obtenez plutôt Map[B,Set[A]] car il peut y avoir différentes clés associées aux mêmes valeurs. Si vous souhaitez connaître toutes les clés, voici le code :

val m = Map(1 -> "a", 2 -> "b", 4 -> "b")
m.groupBy(_._2).mapValues(_.keys)

res0: Map[String,Iterable[Int]] = Map(b -> Set(2, 4), a -> Set(1))

À partir de scala 2.13, cela devient encore plus simple et plus efficace :

m.groupMap(_._2)(_._1)

2 votes

.map(_._1) serait plus lisible en tant que .keys

0 votes

Aujourd'hui, grâce à vous, quelques caractères en moins. La différence, c'est que vous obtenez Set au lieu de List comme auparavant.

1 votes

Soyez prudent lorsque vous utilisez .mapValues car il renvoie une vue. Occasionnellement, c'est ce que vous voulez, mais si vous ne faites pas attention, cela peut consommer beaucoup de mémoire et de CPU. Pour le forcer à utiliser une carte, vous pouvez faire m.groupBy(_._2).mapVaues(_.keys).map(identity) ou vous pouvez remplacer l'appel à .mapValues(_.keys) con .map { case (k, v) => k -> v.keys } .

10voto

Lee Mighdoll Points 427

Il existe plusieurs façons d'éviter le problème du ._1 lors de l'itération.

En voici un exemple. Elle utilise une fonction partielle qui couvre le seul et unique cas

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

H

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

T

6voto

Eddie Carlson Points 1

Je suis venu ici à la recherche d'un moyen d'inverser une Carte de type Carte[A, Seq[B]] Carte[B, Seq[A]], où chaque B dans la nouvelle carte est associée à chaque Un dans la vieille carte pour lesquelles le B est l'Un des associés de la séquence.

E. g.,
Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
inverserait à
Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))

Voici ma solution :

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

où oldMap est de type Map[A, Seq[B]] et newMap est de type Map[B, Seq[A]]

Le imbriquée foldLefts me font grincer des dents un peu, mais c'est la façon la plus simple que j'ai pu trouver pour accomplir ce type d'inversion. Quelqu'un a une solution plus propre?

0 votes

Très belle solution ! @Rok, sa solution est un peu différente de la tienne je pense parce qu'il transforme : Map[A, Seq[B]] à Map[B, Seq[A]] où votre solution se transforme Map[A, Seq[B]] à Map[Seq[B], Seq[A]] .

1 votes

Dans ce cas, sans deux plis imbriqués et peut-être plus performant : a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))

1voto

Aleksey Izmailov Points 4514

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

Notez que les valeurs dupliquées seront écrasées par le dernier ajout à la carte :

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)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X