我的课程类型为Set[String]
。另外,我有这个类的对象列表。我想将所有这些对象集中的所有字符串收集到一个集合中。我已经可以这样做了:
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)
它有效,但我认为,我只能使用flatMap
,而不需要toSet
调用来实现相同的功能。我试过这个,但它给出了编译错误:
// 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)
如果我将list
的类型更改为Set
(即val list = Set(...)
),则此类flatMap
调用有效。
那么,我可以使用某种方式Set.canBuildFrom
或任何其他CanBuildFrom
对象来调用flatMap
对象上的List
,这样我就会得到Set
结果?
答案 0 :(得分:5)
您想要的CanBuildFrom
实例名为breakOut
,必须作为第二个参数提供:
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)
请注意,变量s
上的显式类型注释是必需的 - 这就是选择类型的方式。
修改强>
如果您使用的是Scalaz
或cats
,则还可以使用foldMap
:
import scalaz._, Scalaz._
list.foldMap(_.field)
除了mdm
和Set.empty
部分已经出现之外,这基本上与++
的答案提出的内容基本相同。
答案 1 :(得分:4)
flatMap在Scala中的工作方式是它只能为相同类型的包装器移除一个包装器,即List[List[String]] -> flatMap -> List[String]
如果将flatMap应用于不同的包装器数据类型,那么您将始终获得最终结果作为更高级别的包装器数据类型,即List[Set[String]] -> flatMap -> List[String]
如果你想在不同的包装类型上应用flatMap,即List[Set[String]] -> flatMap -> Set[String]
,你有两个选择: -
将一个数据类型包装器显式强制转换为另一个,即list.flatMap(_.field).toSet
或
通过提供隐式转换器即。 implicit def listToSet(list: List[String]): Set[String] = list.toSet
您可以获得val set:Set[String] = list.flatMap(_.field)
只有这样你才能实现的目标。
结论: - 如果你在2个包装数据类型上应用flatMap,那么你总是得到最终结果作为op类型,它位于包装数据类型之上,即List[Set[String]] -> flatMap -> List[String]
,如果你想转换或转换为不同的数据类型,然后你需要隐式或显式地转换它。
答案 2 :(得分:1)
您可以提供具体的CanBuildFrom
,但为什么不使用折叠?
list.foldLeft(Set.empty[String]){case (set, myClass) => set ++ myClass.field}
仍然只是一次通过该集合,如果您确定list
不为空,您甚至可以改为使用reduceLeft
。