【问题标题】:for comprehension on Lists/Sets in scala理解 scala 中的列表/集合
【发布时间】:2016-06-16 09:35:18
【问题描述】:

这可能是一个幼稚的问题。我有一个案例类“书”,定义如下:

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 来解决这个问题,但另一种方法是不在列表中创建记录,而是在集合中创建记录:

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

【问题讨论】:

  • 集合如何包含重复项?
  • 他们不能,这就是我的意思。我错过了什么吗?
  • 我的问题是,为什么我们从“书籍”的后一个定义的 for 表达式中得到一个集合,而不是一个列表。相应地更新了我的问题。
  • 你解决了吗?如果您觉得我没有真正回答您的问题,请随时解释您仍然困惑的问题。
  • 我确实设法解决了它,忘记了问题,感谢您的帮助。

标签: scala collections scala-collections


【解决方案1】:

在 Scala 中,for 表达式实际上被编译器翻译成高阶函数的组合:

  • 使用产生某些东西的 for 被转换为 ma​​pflatMapwithFilter

    的组合
  • 使用不会产生任何内容的 for 被转换为 withFilterforeach

如果您对此不熟悉,也许您应该在某个地方查找它(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 会生成一个SetLists 的平面映射 Set 也会生成一个 Set。这实际上比看起来要多得多(它可以追溯到范畴论,因为在这种情况下Set 是一个单子,flatMap 是它的自然转换之一,但这只是题外话)。

无论如何,我不确定你的最后一个问题是什么意思,但如果你想将原始的 books 集合保留为 Set 并使用 for 表达式获取重复项,你可以简单地调用.toList 在对它们进行操作之前先查看它们。这样,整个 for 表达式都可以在 List 而不是 Set 上工作。

附言

这表明 for 表达式的性质更接近于函数式编程结构,例如 monad 和 functors(分别使用 flatMap 和 map 进行操作),而不是经典的 for 循环。与作为典型命令式编程构造的经典 for 循环不同,Scala 中的 for 表达式是完全函数式构造,因为它们是高阶函数链 mapflatMapfilterforeach。请注意,这意味着您不仅可以使用带有集合的表达式,还可以使用任何支持这些函数的表达式(例如,您可以将它们与 OptionFuture 一起使用)。这根本不是一个天真的问题,如果您到目前为止还没有意识到这一点,那么了解这一点很重要。当然,您不需要能够在半夜将任何给定的表达式转换为地图链和 flatMaps,但您应该知道它“在后台”使用这些函数的事实。

【讨论】:

    猜你喜欢
    • 2012-02-19
    • 2012-11-01
    • 1970-01-01
    • 2015-10-21
    • 2021-08-10
    • 2019-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多