49 votes

Une façon idiomatique de mettre à jour une valeur dans une carte en fonction de la valeur précédente.

Disons que je stocke des informations sur les comptes bancaires dans une base de données immuable. Map :

val m = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)

et je veux retirer, disons, 50 $ du compte de Marc. Je peux le faire comme suit :

val m2 = m + ("Mark" -> (m("Mark") - 50))

Mais ce code me semble laid. Y a-t-il une meilleure façon de l'écrire ?

38voto

Travis Brown Points 56342

Il n'y a pas adjust dans le Map malheureusement. J'ai parfois utilisé une fonction comme la suivante (inspirée de la fonction Haskell Data.Map.adjust avec un ordre différent des arguments) :

def adjust[A, B](m: Map[A, B], k: A)(f: B => B) = m.updated(k, f(m(k)))

Maintenant adjust(m, "Mark")(_ - 50) fait ce que vous voulez. Vous pouvez également utiliser la fonction modèle "pimp-my-library pour obtenir le plus naturel m.adjust("Mark")(_ - 50) syntaxe, si tu voulais vraiment quelque chose de plus propre.

(Notez que la version courte ci-dessus lève une exception si k n'est pas dans la carte, ce qui est différent du comportement Haskell et probablement quelque chose que vous voudriez corriger dans le vrai code).

12voto

Dan Burton Points 26639

Cela pourrait être fait avec objectifs . L'idée même d'une lentille est de pouvoir zoomer sur une partie particulière d'une structure immuable, et de pouvoir 1) récupérer la petite partie d'une structure plus grande, ou 2) créer une nouvelle structure plus grande avec une petite partie modifiée. Dans ce cas, ce que vous souhaitez, c'est le point 2.

Tout d'abord, une mise en œuvre simple de Lens volé à cette réponse volé à scalaz :

case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
  def apply(whole: A): B   = get(whole)
  def updated(whole: A, part: B): A = set(whole, part) // like on immutable maps
  def mod(a: A)(f: B => B) = set(a, f(this(a)))
  def compose[C](that: Lens[C,A]) = Lens[C,B](
    c => this(that(c)),
    (c, b) => that.mod(c)(set(_, b))
  )
  def andThen[C](that: Lens[B,C]) = that compose this
}

Ensuite, un constructeur intelligent pour créer une lentille à partir d'une "structure plus grande". Map[A,B] à "partie inférieure". Option[B] . Nous indiquons quelle "petite partie" nous voulons regarder en fournissant une clé particulière. (Inspiré par ce dont je me souviens de Présentation d'Edward Kmett sur les lentilles en Scala ) :

def containsKey[A,B](k: A) = Lens[Map[A,B], Option[B]](
  get = (m:Map[A,B]) => m.get(k),
  set = (m:Map[A,B], opt: Option[B]) => opt match {
    case None => m - k
    case Some(v) => m + (k -> v)
  }
)

Maintenant, votre code peut être écrit :

val m2 = containsKey("Mark").mod(m)(_.map(_ - 50))

n.b. J'ai en fait changé mod de la réponse à laquelle je l'ai volée pour qu'elle prenne ses entrées au curry. Cela permet d'éviter les annotations de type supplémentaires. Notez également _.map parce que n'oubliez pas que notre objectif est de Map[A,B] a Option[B] . Cela signifie que la carte sera inchangée si elle ne contient pas la clé "Mark" . Sinon, cette solution finit par être très similaire à la solution adjust solution présentée par Travis.

10voto

Xavier Guihot Points 6414

Démarrage Scala 2.13 , Map#updatedWith sert exactement ce but :

// val map = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)
map.updatedWith("Mark") {
  case Some(money) => Some(money - 50)
  case None        => None
}
// Map("Mark" -> 50, "Jonathan" -> 350, "Bob" -> 65)

ou sous une forme plus compacte :

map.updatedWith("Mark")(_.map(_ - 50))

Notez que (en citant le doc ) si la fonction de remappage renvoie Some(v) la cartographie est mise à jour avec la nouvelle valeur v . Si la fonction de remappage renvoie None le mappage est supprimé (ou reste absent s'il était initialement absent).

def updatedWith[V1 > : V](key : K)(remappingFunction : (Option[V]) => Option[V1]) : Map[K, V1]

De cette façon, nous pouvons gérer de manière élégante les cas où la clé pour laquelle il faut mettre à jour la valeur n'existe pas :

Map("Jonathan" -> 350, "Bob" -> 65)
  .updatedWith("Mark")({ case None => Some(0) case Some(v) => Some(v - 50) })
// Map("Jonathan" -> 350, "Bob" -> 65, "Mark" -> 0)
Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)
  .updatedWith("Mark")({ case None => Some(0) case Some(v) => Some(v - 50) })
// Map("Mark" -> 50, "Jonathan" -> 350, "Bob" -> 65)

Map("Jonathan" -> 350, "Bob" -> 65)
  .updatedWith("Mark")({ case None => None case Some(v) => Some(v - 50) })
// Map("Jonathan" -> 350, "Bob" -> 65)

9voto

mucaho Points 95

Un site Réponse SO propose une autre alternative, en utilisant le |+| opérateur de scalaz

val m2 = m |+| Map("Mark" -> -50)

El |+| L'opérateur additionne les valeurs d'une clé existante, ou insère la valeur sous une nouvelle clé.

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