除了 SQL,我所学习的编程语言几乎包含for循环,熟练合适地使用for循环,可以帮助我们完成复杂的任务。

1. 遍历集合

我们通过for循环来输出当前文件下所有文件(或文件夹):

  1. val filesHere = (new File(".")).listFiles
  2. for (file <- filesHere)
  3. println(file)

🍭 这里File也可以使用java.io.File来代替,listFiles也可以使用listFiles()

对于Range类型(类似于 Python 种的range函数),for循环也同样适用:

  1. for (i <- 1 to 4) println("Iteration: " + i)
  2. // Iteration: 1
  3. // Iteration: 2
  4. // Iteration: 3
  5. // Iteration: 4

🍭 如果不希望包含上边界 4,则可以使用 1 until 4 来代替to

2. for+if实现遍历过滤

我们可以在for循环的条件中添加if条件,对集合完成过滤(filter),从而得到想要的集合:

  1. val filesHere = (new File(".")).listFiles
  2. // 第 1 种方式
  3. for (file <- filesHere if file.getName.endsWith(".scala"))
  4. println(file)
  5. // 第 2 种方式
  6. for (file <- filesHere)
  7. if file.getName.endsWith(".scala") println(file)

当然,我们也可以在for循环种使用多个过滤器,使用;进行分隔(使用小括号时,分号时一定要加的):

  1. val filesHere = (new File(".")).listFiles
  2. for (
  3. file <- filesHere
  4. if file.isFile;
  5. if file.getName.endsWith(".scala")
  6. ) println(file)

如果for循环使用{}的话,可以不需要加分号,如下所示:

  1. val filesHere = (new File(".")).listFiles
  2. for {
  3. file <- filesHere
  4. if file.isFile
  5. if file.getName.endsWith(".scala")
  6. } println(file)

3. 嵌套循环

for循环中还可以继续嵌套循环,和上面多个过滤器一样,使用;进行分隔:

  1. import java.io.File
  2. import scala.io.Source
  3. val filesHere = (new File("./control_structure")).listFiles
  4. def fileLines(file: File) = Source.fromFile(file, "UTF-8").getLines().toList
  5. def grep(pattern: String) =
  6. for (
  7. file <- filesHere
  8. if file.getName.endsWith(".scala");
  9. line <- fileLines(file)
  10. if line.trim.matches(pattern)
  11. ) println(file + ": " + line.trim)
  12. grep(".*file.*")

细心的同学可以看出,这里line.trim计算了 2 次,如果只希望计算 1 次的话,我们也可以在嵌套循环中使用变量:

  1. def grep(pattern: String) =
  2. for (
  3. file <- filesHere
  4. if file.getName.endsWith(".scala");
  5. line <- fileLines(file)
  6. trimmed = line.trim
  7. if trimmed.matches(pattern)
  8. ) println(file + ": " + trimmed)

4. 生成新集合

可以通过对集合进行遍历,对集合中的元素进行操作,例如过滤、四则运算等,从而生成一个新的集合,学习过 Python 的同学将此过程理解为列表推导式

下面示例中,我们将学习一个新的知识yield,用于生成一个只包含后缀名为.scala的新集合,:

  1. val filesHere = (new File("./control_structure")).listFiles
  2. def scalaFiles =
  3. for {
  4. file <- filesHere
  5. if file.getName.endsWith(".scala")
  6. } yield file

注意:这里将def换成val也是可以的,且变量的类型都是一致的。

当然,我们的for循环可以更复杂一些,例如上面提到的嵌套循环:

  1. val forLineLengths =
  2. for {
  3. file <- filesHere
  4. if file.getName.endsWith(".scala");
  5. line <- fileLines(file)
  6. trimmed = line.trim
  7. if trimmed.matches(pattern)
  8. } yield trimmed.length

这里需要注意的是,正确的格式为如下,及yield一定是在外面,且for后面跟的是大括号。

  1. for (循环体) yield 表达式
  2. // 或者
  3. for {子句} yield 表达式

对于下面这个示例,则是错误的:

  1. for (file <- filesHere if file.getName.endsWith(".scala")) {
  2. yield file
  3. }

5. foreach

除了for循环外,对于集合而言,还有一个foreach方法,与 Java 中非常类似:

  1. val arr = 1 to 4
  2. arr.foreach(a => println(a * 2))
  3. arr.foreach(a => if (a == 2) println(a * 2) else println(a * 10))

我们可以给集合中元素添加类型名称,但通常不需要这样做:

  1. arr.foreach((a: Int) => println(a * 2))

如果只需要打印出集合中的元素,则可以直接这样使用:

  1. arr.foreach(println)

注意foreach并不能用于创建新的集合

6. 高阶使用

对于更加复杂的数据结构,for循环的使用还可以有更多变化,在循环体中添加过滤器,从而实现查询功能。下面的示例为查询家庭成员中父亲及其子女:

  1. case class Person(
  2. name: String,
  3. isMale: Boolean,
  4. children: Person*
  5. )
  6. val lara = Person("Lara", false)
  7. val bob = Person("Bob", true)
  8. val julie = Person("Julie", false, lara, bob)
  9. val persons = List(lara, bob, julie)
  10. val filter_persons = for (p <- persons; if !p.isMale; c <- p.children)
  11. yield (p.name, c.name)
  12. filter_persons.foreach(println)

如果使用filtermapflatMap的话,上述还有一种非常复杂的写法。但是这种写法既不方便编写,也不方便阅读,还是推荐上面的写法:

  1. val filter_persons = persons filter (
  2. p => !p.isMale) flatMap(
  3. p => (p.children map(c => (p.name, c.name))))
  4. filter_persons.foreach(println)

6.1 更多参考示例

  1. /* case class Person(
  2. name: String,
  3. isMale: Boolean,
  4. children: Person*
  5. )
  6. val lara = Person("Lara", false)
  7. val bob = Person("Bob", true)
  8. val julie = Person("Julie", false, lara, bob)
  9. val persons = List(lara, bob, julie)
  10. val filter_person = for (p <- persons; if !p.isMale; c <- p.children)
  11. yield (p.name, c.name)
  12. filter_person.foreach(println)
  13. val filter_persons = persons filter (
  14. p => !p.isMale) flatMap(
  15. p => (p.children map(c => (p.name, c.name))))
  16. filter_persons.foreach(println)
  17. */
  18. case class Book(title: String, authors: String*)
  19. val books: List[Book] = List (
  20. Book(
  21. "Structure and Interpretation of Computer Programs",
  22. "Abelson, Harold",
  23. "Sussman, Gerald J."
  24. ),
  25. Book(
  26. "Principles of Compiler Design",
  27. "Aho, Alfred", "Ullman, Jeffrey"
  28. ),
  29. Book(
  30. "Elements of ML Programming",
  31. "Ullman, Jeffrey"
  32. ),
  33. Book(
  34. "The Java Language Specification",
  35. "Gosling, James", "Joy, Bill", "Steele Guy", "Bracha, Gilad"
  36. )
  37. )
  38. // 查询姓“Gosling”作者的书籍
  39. val res1 = for (b <- books; a <- b.authors if a startsWith("Gosling")) yield b.title
  40. res1.foreach(println) // The Java Language Specification
  41. // 查询所有包含“Program”的书名
  42. val res2 = for (b <- books if (b.title indexOf "Program") >= 0) yield b.title
  43. res2.foreach(println)
  44. // 查找至少编写了两本书的作者
  45. // 由于 res3 会导致同一作者多次出现,所以需要去重
  46. val res3 = for (b1 <- books; b2 <- books if b1 != b2;
  47. a1 <- b1.authors; a2 <- b2.authors if a1 == a2) yield a1
  48. res3.foreach(println)
  49. def removeDuplicates[A](xs: List[A]): List[A] = {
  50. if (xs.isEmpty) xs
  51. else
  52. xs.head :: removeDuplicates(
  53. for (x <- xs if x != xs.head) yield x
  54. )
  55. }
  56. val res4 = removeDuplicates(res3)
  57. res4.foreach(println)

for循环解决皇后问题