2017-09-25 1 views
1

Ich habe Klasse mit Feld des Typs Set[String]. Außerdem habe ich eine Liste von Objekten dieser Klasse. Ich möchte alle Zeichenfolgen aus allen Mengen dieser Objekte in einem Satz sammeln. Hier ist, wie ich es schon tun können:Scala: Liste mit FlatMap setzen

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) 

Es funktioniert, aber ich denke, ich kann das gleiche mit nur flatMap, ohne toSet Aufruf erreichen. Ich habe versucht, diese, aber es war Kompilierungsfehler gegeben:

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

Wenn ich Art von list-Set ändern (das heißt, val list = Set(...)), dann solche flatMap Aufruf funktioniert.

Also, kann ich irgendwie Set.canBuildFrom oder andere CanBuildFrom Objekt flatMap auf List Objekt aufzurufen, so dass ich Set als Ergebnis zu bekommen?

Antwort

5

Die CanBuildFrom Instanz, die Sie wollen breakOut genannt wird und muss als zweiter Parameter angegeben:

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) 

Beachten Sie, dass explizite Typanmerkung auf variable s obligatorisch ist - das ist, wie der Typ gewählt wird.

Edit:

Wenn Sie Scalaz oder cats verwenden, können Sie foldMap auch verwenden:

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

Dies gilt im Wesentlichen, was mdm s Antwort schlägt, mit Ausnahme der Set.empty und ++ Teile sind bereits gebacken.

1

Sie könnten vielleicht eine spezifische CanBuildFrom angeben, aber warum nicht stattdessen eine Falte verwenden?

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

Immer noch nur ein Durchgang durch die Sammlung, und wenn Sie die list sicher sind, nicht leer ist, Sie reduceLeft stattdessen sogar Benutzer können.

+0

Es ist nicht notwendig, 'case' hier zu verwenden, da' foldLeft' die Funktion akzeptiert zwei Parameter, d. h. '(_ ++ _.field)' –

+0

Sie sind sicherlich richtig @Oleg, aber ich habe es nur aus Gründen der Klarheit explizit gemacht. – mdm

+0

Sie können immer noch das Schlüsselwort "case" weglassen, da Sie keinen signifikanten Mustervergleich durchführen –

4

Die Arbeit Art und Weise flatMap in Scala ist, dass es nur einen Wrapper für die gleiche Art von Wrapper dh List[List[String]] -> flatMap -> List[String]

wenn Sie flatMap Anwendung auf verschiedenen Wrapper-Datentypen entfernen, dann werden Sie als höhere Stufe das Endergebnis bekommen immer Wrapper-Datentyp dh List[Set[String]] -> flatMap -> List[String]

, wenn Sie die flatMap auf verschiedenen Wrapper dh List[Set[String]] -> flatMap -> Set[String] anwenden möchten in Sie haben 2 Möglichkeiten: -

  1. Explicitl y den einen Datentypwrapper auf einen anderen, d. h. list.flatMap(_.field).toSet oder

  2. umwandeln, indem ein impliziter Konverter, d.h. implicit def listToSet(list: List[String]): Set[String] = list.toSet und das können Sie val set:Set[String] = list.flatMap(_.field)

nur dann bekommen, was Sie versuchen, erreicht werden zu erreichen.

Fazit: - wenn Sie flatMap auf 2 ungeöffneten Datentyp gelten dann werden Sie immer das Endergebnis als op Art erhalten, die auf der Oberseite des Wrapper-Datentyp dh ist List[Set[String]] -> flatMap -> List[String] und wenn Sie auf verschiedene Datentypen zu konvertieren oder zu werfen wollen dann müssen Sie es entweder implizit oder explizit umwandeln.

Verwandte Themen