2017-09-25 2 views
1

J'ai classe avec champ de type Set[String]. En outre, j'ai la liste des objets de cette classe. Je voudrais rassembler toutes les chaînes de tous les ensembles de ces objets dans un ensemble. Voici comment je peux déjà le faire:Scala: liste à définir avec flatMap

case class MyClass(field: Set[String]) 

val list = List(
    MyClass(Set("123")), 
    MyClass(Set("456", "798")), 
    MyClass(Set("123", "798")) 
) 

list.flatMap(_.field).toSet // Set(123, 456, 798) 

Il fonctionne, mais je pense, je peux obtenir le même en utilisant uniquement flatMap, sans toSet invocation. J'ai essayé, mais il avait donné erreur de compilation:

// error: Cannot construct a collection of type Set[String] 
// with elements of type String based on a collection of type List[MyClass]. 
list.flatMap[String, Set[String]](_.field) 

Si je change le type de list-Set (à savoir, val list = Set(...)), alors une telle invocation flatMap fonctionne.

Alors, puis-je utiliser en quelque sorte Set.canBuildFrom ou tout autre objet CanBuildFrom d'invoquer flatMap sur l'objet List, de sorte que je vais Set en conséquence?

Répondre

5

L'instance CanBuildFrom que vous voulez est appelé breakOut et doit être fourni en tant que second paramètre:

import scala.collection.breakOut 

case class MyClass(field: Set[String]) 

val list = List(
    MyClass(Set("123")), 
    MyClass(Set("456", "798")), 
    MyClass(Set("123", "798")) 
) 

val s: Set[String] = list.flatMap(_.field)(breakOut) 

Notez que l'annotation de type explicite sur la variable s est obligatoire - voilà comment le type est choisi.

Edit:

Si vous utilisez Scalaz ou cats, vous pouvez utiliser foldMap ainsi:

import scalaz._, Scalaz._ 
list.foldMap(_.field) 

Cela fait essentiellement ce que la réponse de mdm propose, à l'exception des parties Set.empty et ++

1

Vous pourriez peut-être fournir un CanBuildFrom spécifique, mais pourquoi ne pas utiliser un pli à la place?

list.foldLeft(Set.empty[String]){case (set, myClass) => set ++ myClass.field} 

Encore un seul passage à travers la collection, et si vous êtes sûr que l'list est pas vide, vous pouvez même utilisateur reduceLeft à la place.

+0

Il n'est pas nécessaire d'utiliser 'case' ici car' foldLeft' accepte la fonction avec deux paramètres, à savoir '(_ ++ _.field)' –

+0

vous avez certainement raison @Oleg, mais je l'ai fait explicite juste pour des raisons de clarté. – mdm

+0

vous pouvez toujours omettre le mot-clé "case", puisque vous ne faites pas de correspondance significative –

4

Les travaux de flatMap façon à Scala est qu'il ne peut retirer une enveloppe pour le même type d'emballages iE List[List[String]] -> flatMap -> List[String]

si vous appliquez flatMap sur différents types de données d'emballage, vous obtiendrez toujours le résultat final que le niveau supérieur wrapper type de données à savoir List[Set[String]] -> flatMap -> List[String]

si vous voulez appliquer la flatMap sur différents types d'emballage c.-à-List[Set[String]] -> flatMap -> Set[String] en vous avez 2 options: -

  1. Explicitl y transtyper l'enveloppe de type de données en une autre, c'est-à-dire list.flatMap(_.field).toSet ou

  2. En fournissant un convertisseur implicite, c.-à-d. implicit def listToSet(list: List[String]): Set[String] = list.toSet et vous pouvez obtenir val set:Set[String] = list.flatMap(_.field)

alors seulement ce que vous essayez d'atteindre sera accompli.

Conclusion: - si vous appliquez flatMap sur 2 type de données enveloppées alors vous obtiendrez toujours le résultat final que le type d'op qui est au-dessus de wrapper type de données à savoir List[Set[String]] -> flatMap -> List[String] et si vous voulez convertir ou jeter à différents types de données alors soit vous devez implicitement ou explicitement le jeter.