这可能是一个天真的问题。我有一个案例类'Book',定义如下:
case class Book(title : String, authors : List[String])
在我的主要方法中,我定义了一些Book记录如下:
val books = List(
Book(title = "Book1", authors = List("Author1", "Author2")),
Book(title = "Book2", authors = List("Author3", "Author4")),
Book(title = "Book3", authors = List("Author2", "Author5")),
Book(title = "Book4", authors = List("Author6", "Author3")),
Book(title = "Book5", authors = List("Author7", "Author8")),
Book(title = "Book6", authors = List("Author5", "Author9"))
)
我正在编写一个查询来检索已创作多本书的作者姓名,我的查询如下:
val authorsWithMoreThanTwoBooks =
(for {
b1 <- books
b2 <- books
if b1.title != b2.title
a1 <- b1.authors
a2 <- b2.authors
if a1 == a2
} yield a1)
println(authorsWithMoreThanTwoBooks)
这会打印List(Author2, Author3, Author2, Author5, Author3, Author5)
(作者的名字出现两次是非常期待的,因为每一本书都会被拍两次,如(b1,b2)和(b2,b1))。
当然我可以使用distinct
来解决这个问题,但另一种方法是创建不在列表中但在集合中的记录:
val books = Set(
Book(title = "Book1", authors = List("Author1", "Author2")),
Book(title = "Book2", authors = List("Author3", "Author4")),
Book(title = "Book3", authors = List("Author2", "Author5")),
Book(title = "Book4", authors = List("Author6", "Author3")),
Book(title = "Book5", authors = List("Author7", "Author8")),
Book(title = "Book6", authors = List("Author5", "Author9"))
)
for
表达式和println
之后的输出:Set(Author5, Author2, Author3)
为什么会出现这种情况?为什么for
上的Set
表达式会生成另一个Set
而不是List
?如果需要,是否可以使用重复值获得List
相关作者?
答案 0 :(得分:3)
在Scala中,for
表达式实际上由编译器转换为高阶函数的组合:
使用产生某些内容的for
转换为地图, flatMap 和 withFilter 的组合
使用不会产生任何结果的for
会转换为 withFilter 和 foreach
如果您对此不熟悉,也许您应该在某处查找(here's一个选项)。
所以,你的构造
for {
b1 <- books
b2 <- books
if b1.title != b2.title
a1 <- b1.authors
a2 <- b2.authors
if a1 == a2
} yield a1
被翻译成
books.flatMap(b1 => books
.filter(b2 => b1.title != b2.title)
.flatMap(b2 => b1.authors
.flatMap(a1 => b2.authors
.filter(a2 => a1 == a2)
.map(a2 => a1))))
平面映射两个Sets
会生成Set
。 Set
的{{1}}的平面映射也会生成Lists
。实际上有更多的东西比它看起来更多(它可以追溯到类别理论,因为Set
在这种情况下是一个monad而Set
是它的自然变换之一,但那只是一个题外话。 / p>
无论如何,我不确定您的上一个问题是什么意思,但是如果您想将原始flatMap
集合保留为books
并使用Set
获取重复项表达式,您可以在操作它们之前简单地调用书籍上的for
。这样就可以在.toList
而不是List
上进行表达。
P.S。
这表明for表达式的本质更接近函数式编程结构,如monad和functor(分别使用flatMap和map进行操作),而不是传统的for循环。与作为典型命令式编程构造的经典for循环不同,Scala中的表达式是完全功能构造,因为它们是高阶函数链Set
,map
,flatMap
和{{ 1}}。请注意,这意味着您可以使用不仅仅是集合的表达式,而是支持这些函数的任何内容(例如,您可以将它们与filter
或foreach
一起使用)。这根本不是一个天真的问题,如果你到目前为止还没有意识到这一点,重要的是要知道这一点。当然,您不需要能够在半夜将任何表达式转换为map和flatMaps链,但是您应该意识到它在“引擎盖下”使用这些功能。