了解scala中的列表/集合

时间:2016-03-03 07:28:01

标签: scala collections scala-collections

这可能是一个天真的问题。我有一个案例类'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相关作者?

1 个答案:

答案 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会生成SetSet的{​​{1}}的平面映射也会生成Lists。实际上有更多的东西比它看起来更多(它可以追溯到类别理论,因为Set在这种情况下是一个monad而Set是它的自然变换之一,但那只是一个题外话。 / p>

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

P.S。

这表明for表达式的本质更接近函数式编程结构,如monad和functor(分别使用flatMap和map进行操作),而不是传统的for循环。与作为典型命令式编程构造的经典for循环不同,Scala中的表达式是完全功能构造,因为它们是高阶函数链SetmapflatMap和{{ 1}}。请注意,这意味着您可以使用不仅仅是集合的表达式,而是支持这些函数的任何内容(例如,您可以将它们与filterforeach一起使用)。这根本不是一个天真的问题,如果你到目前为止还没有意识到这一点,重要的是要知道这一点。当然,您不需要能够在半夜将任何表达式转换为map和flatMaps链,但是您应该意识到它在“引擎盖下”使用这些功能。