30 votes

Éviter la suppression accidentelle de doublons lors du mappage d'un ensemble

J'aime vraiment les concepts de la programmation fonctionnelle, mais j'ai été mordu à deux reprises par le même gotcha, lors de la cartographie à travers une collection qui se trouve être une Set (c'est à dire supprime automatiquement les doublons). Le problème est que après avoir transformé les éléments de cet ensemble, le conteneur de sortie est aussi un ensemble, et donc supprime tous les doublons de la changée de sortie.

Une très brève de RÉPLICATION de session pour illustrer le problème:

scala> case class Person(name: String, age: Int)
defined class Person

scala> val students = Set(Person("Alice", 18), Person("Bob", 18), Person("Charles", 19))
students: scala.collection.immutable.Set[Person] = Set(Person(Alice,18), Person(Bob,18), Person(Charles,19))

scala> val totalAge = (students map (_.age)).sum
totalAge: Int = 37

Je serais bien sûr, s'attendre à la somme de l'âge à 18 + 18 + 19 = 55, mais parce que les étudiants ont été stockés dans un Set, les âges après la cartographie, ainsi, l'un de la 18s a disparu avant que les âges ont été additionnés.

Dans le code réel, c'est souvent plus insidieux et plus difficile à repérer, surtout si vous écrivez du code d'utilitaire qui prend tout simplement un Traversable et/ou de l'utilisation de la sortie de méthodes qui sont déclarées-retour d'un Traversable (la mise en œuvre de ce qui se trouve être un Jeu). Il me semble que ces situations sont presque impossible à repérer de manière fiable, jusqu'à ce que elles se manifestent comme un bug.

Donc, existe-il des pratiques exemplaires qui permettront de réduire mon exposition à ce problème? Suis-je tort de penser map-ping dessus d'une Traversable que, conceptuellement, la transformation de chaque élément en place, par opposition à l'ajout de la transformée éléments en une nouvelle collection? Dois-je appeler en .toStream sur tout avant de cartographie, si je veux garder ce modèle mental?

Des conseils/recommandations seraient grandement appréciés.

Mise à jour: la Plupart des réponses ont porté sur la mécanique de l', y compris les doublons dans la somme. Je suis plus intéressé par les pratiques en cause lors de l'écriture de code dans le cas général - avez-vous percé-vous toujours appel toList sur toutes les collections avant d'appeler map? Avez-vous méticuleusement vérifier les classes concrètes de toutes les collections dans votre application avant d'appeler des méthodes sur eux? Etc.

La fixation de quelque chose qui a déjà été identifié comme un problème est trivial - la partie la plus difficile est de prévenir ces erreurs rampant dans la première place.

19voto

oxbow_lakes Points 70013

Vous pourriez vouloir utiliser le scalaz foldMap à cette fin, il travaille sur quelque chose pour lequel il y a un Foldable typeclass disponibles. L'utilisation dans votre cas ressemble à ceci:

persons foldMap (_.age)

La signature de l' foldMap est comme suit:

trait MA[M[_], A] {
  val value: M[A]

  def foldMap[B](f: A => B)(implicit f: Foldable[M], m: Monoid[B])
}

Donc, tant que vous avez une collection CC[A]CC peut être plié (c'est à dire traversée), une fonction à partir d' A => B où B est un monoïde, vous pouvez accumuler un résultat.

11voto

Viktor Klang Points 14826

Pour ne pas faire glisser des dépendances supplémentaires dans l'image:

 (0 /: students) { case (sum, s) => sum + s.age }
 

3voto

Jon Freedman Points 4411

Vous pouvez breakOut le type de collection

scala> import collection.breakOut
import collection.breakOut

scala> val ages = students.map(_.age)(breakOut): List[Int]
ages: List[Int] = List(18, 18, 19)

Ensuite, vous pouvez somme comme prévu

Basé sur la mise à jour de la question, la meilleure pratique pour éviter ce type d'erreurs est une bonne couverture de tests unitaires avec un représentant de données, en collaboration avec des API couplé à la connaissance de la façon dont le compilateur scala maintient les types de sources à travers la carte/pour les générateurs etc. Si vous êtes de retour d'un Ensemble de quelque chose que vous devriez faire si évident, que le retour d'une Collection/Traversable se cache une mise en œuvre détaillée.

2voto

Marcin Points 25366

Vous pourriez utiliser l' toIterable ou toList méthodes pour convertir l'ensemble à un autre discbased premier. http://www.scala-lang.org/api/current/scala/collection/immutable/Set.html

(À noter qu' toIterable peut retourner tout Itératif, bien que l'implémentation de référence ne sera pas, selon liées à la documentation. @Debilski informe-moi dans les commentaires qu'il renvoie néanmoins un Ensemble.)

2voto

Dave Griffith Points 12923

Si vous vous retrouvez à plusieurs reprises à frapper la même erreur, votre premier problème n'est pas l'erreur, mais plutôt que vous vous répétez. map().sum est un cas d'utilisation assez courant (en particulier dans des contextes d'analyse de données) pour mériter sa propre méthode sur Traversable. De ma classe personnelle de proxénète Traversable.

   implicit def traversable2RichTraversable[A](t: Traversable[A]) = new {
///many many methods deleted

    def sumOf[C: Numeric](g: A => C): C = t.view.toList.map(g).sum

///many many more methods deleted

}
 

(Ce .view peut ne pas être nécessaire, mais ne peut pas faire de mal.)

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