在 scala 中理解 Lists/Sets
for comprehension on Lists/Sets in scala
这可能是个幼稚的问题。我有一个案例 class 'Book',定义如下:
case class Book(title : String, authors : List[String])
在我的main方法中,我定义了一些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
来解决这个问题,但另一种方法是不在列表中而是在 Set 中创建记录:
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)
为什么会发生这种行为?为什么 Set
上的 for
表达式会生成另一个 Set
而不是 List
?如果需要,是否有可能获得具有重复值的相关作者的 List
?
在 Scala 中,for
表达式实际上被编译器翻译成高阶函数的组合:
using a for
产生的东西被翻译成 map、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
。 Lists
的平面映射 Set
也会生成 Set
。实际上这比看起来要多得多(它可以追溯到范畴论,因为 Set
在这种情况下是一个单子,而 flatMap
是它的自然转换之一,但这只是题外话)。
无论如何,我不确定你最后一个问题的意思,但是如果你想将原始 books
集合保留为 Set
并使用 for
表达式,您可以在对书籍进行操作之前简单地调用 .toList
。这样整个 for 表达式都在 List
而不是 Set
.
上工作
P.S.
这表明 for 表达式的性质更接近于函数式编程构造,例如 monad 和仿函数(分别使用 flatMap 和 map 进行操作),而不是经典的 for 循环。与典型的命令式编程构造的经典 for 循环不同,Scala 中的 for 表达式完全是函数式构造,因为它们是高阶函数链 map
、flatMap
、filter
和 foreach
。请注意,这意味着您不仅可以将 for 表达式用于集合,还可以用于任何支持这些函数的表达式(例如,您可以将它们与 Option
或 Future
一起使用)。这根本不是一个天真的问题,如果你到目前为止还没有意识到这一点,那么了解这一点很重要。当然,您不需要能够在半夜将任何给定的 for 表达式转换为 maps 和 flatMaps 链,但您应该意识到它使用了那些函数 "under the hood"。
这可能是个幼稚的问题。我有一个案例 class 'Book',定义如下:
case class Book(title : String, authors : List[String])
在我的main方法中,我定义了一些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
来解决这个问题,但另一种方法是不在列表中而是在 Set 中创建记录:
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)
为什么会发生这种行为?为什么 Set
上的 for
表达式会生成另一个 Set
而不是 List
?如果需要,是否有可能获得具有重复值的相关作者的 List
?
在 Scala 中,for
表达式实际上被编译器翻译成高阶函数的组合:
using a
for
产生的东西被翻译成 map、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
。 Lists
的平面映射 Set
也会生成 Set
。实际上这比看起来要多得多(它可以追溯到范畴论,因为 Set
在这种情况下是一个单子,而 flatMap
是它的自然转换之一,但这只是题外话)。
无论如何,我不确定你最后一个问题的意思,但是如果你想将原始 books
集合保留为 Set
并使用 for
表达式,您可以在对书籍进行操作之前简单地调用 .toList
。这样整个 for 表达式都在 List
而不是 Set
.
P.S.
这表明 for 表达式的性质更接近于函数式编程构造,例如 monad 和仿函数(分别使用 flatMap 和 map 进行操作),而不是经典的 for 循环。与典型的命令式编程构造的经典 for 循环不同,Scala 中的 for 表达式完全是函数式构造,因为它们是高阶函数链 map
、flatMap
、filter
和 foreach
。请注意,这意味着您不仅可以将 for 表达式用于集合,还可以用于任何支持这些函数的表达式(例如,您可以将它们与 Option
或 Future
一起使用)。这根本不是一个天真的问题,如果你到目前为止还没有意识到这一点,那么了解这一点很重要。当然,您不需要能够在半夜将任何给定的 for 表达式转换为 maps 和 flatMaps 链,但您应该意识到它使用了那些函数 "under the hood"。