除了 SQL,我所学习的编程语言几乎包含for循环,熟练合适地使用for循环,可以帮助我们完成复杂的任务。
1. 遍历集合
我们通过for循环来输出当前文件下所有文件(或文件夹):
val filesHere = (new File(".")).listFilesfor (file <- filesHere)println(file)
🍭 这里
File也可以使用java.io.File来代替,listFiles也可以使用listFiles()。
对于Range类型(类似于 Python 种的range函数),for循环也同样适用:
for (i <- 1 to 4) println("Iteration: " + i)// Iteration: 1// Iteration: 2// Iteration: 3// Iteration: 4
🍭 如果不希望包含上边界 4,则可以使用
1 until 4来代替to。
2. for+if实现遍历过滤
我们可以在for循环的条件中添加if条件,对集合完成过滤(filter),从而得到想要的集合:
val filesHere = (new File(".")).listFiles// 第 1 种方式for (file <- filesHere if file.getName.endsWith(".scala"))println(file)// 第 2 种方式for (file <- filesHere)if file.getName.endsWith(".scala") println(file)
当然,我们也可以在for循环种使用多个过滤器,使用;进行分隔(使用小括号时,分号时一定要加的):
val filesHere = (new File(".")).listFilesfor (file <- filesHereif file.isFile;if file.getName.endsWith(".scala")) println(file)
如果for循环使用{}的话,可以不需要加分号,如下所示:
val filesHere = (new File(".")).listFilesfor {file <- filesHereif file.isFileif file.getName.endsWith(".scala")} println(file)
3. 嵌套循环
for循环中还可以继续嵌套循环,和上面多个过滤器一样,使用;进行分隔:
import java.io.Fileimport scala.io.Sourceval filesHere = (new File("./control_structure")).listFilesdef fileLines(file: File) = Source.fromFile(file, "UTF-8").getLines().toListdef grep(pattern: String) =for (file <- filesHereif file.getName.endsWith(".scala");line <- fileLines(file)if line.trim.matches(pattern)) println(file + ": " + line.trim)grep(".*file.*")
细心的同学可以看出,这里line.trim计算了 2 次,如果只希望计算 1 次的话,我们也可以在嵌套循环中使用变量:
def grep(pattern: String) =for (file <- filesHereif file.getName.endsWith(".scala");line <- fileLines(file)trimmed = line.trimif trimmed.matches(pattern)) println(file + ": " + trimmed)
4. 生成新集合
可以通过对集合进行遍历,对集合中的元素进行操作,例如过滤、四则运算等,从而生成一个新的集合,学习过 Python 的同学将此过程理解为列表推导式。
下面示例中,我们将学习一个新的知识yield,用于生成一个只包含后缀名为.scala的新集合,:
val filesHere = (new File("./control_structure")).listFilesdef scalaFiles =for {file <- filesHereif file.getName.endsWith(".scala")} yield file
注意:这里将
def换成val也是可以的,且变量的类型都是一致的。
当然,我们的for循环可以更复杂一些,例如上面提到的嵌套循环:
val forLineLengths =for {file <- filesHereif file.getName.endsWith(".scala");line <- fileLines(file)trimmed = line.trimif trimmed.matches(pattern)} yield trimmed.length
这里需要注意的是,正确的格式为如下,及yield一定是在外面,且for后面跟的是大括号。
for (循环体) yield 表达式// 或者for {子句} yield 表达式
对于下面这个示例,则是错误的:
for (file <- filesHere if file.getName.endsWith(".scala")) {yield file}
5. foreach
除了for循环外,对于集合而言,还有一个foreach方法,与 Java 中非常类似:
val arr = 1 to 4arr.foreach(a => println(a * 2))arr.foreach(a => if (a == 2) println(a * 2) else println(a * 10))
我们可以给集合中元素添加类型名称,但通常不需要这样做:
arr.foreach((a: Int) => println(a * 2))
如果只需要打印出集合中的元素,则可以直接这样使用:
arr.foreach(println)
⌛ 注意:
foreach并不能用于创建新的集合
6. 高阶使用
对于更加复杂的数据结构,for循环的使用还可以有更多变化,在循环体中添加过滤器,从而实现查询功能。下面的示例为查询家庭成员中父亲及其子女:
case class Person(name: String,isMale: Boolean,children: Person*)val lara = Person("Lara", false)val bob = Person("Bob", true)val julie = Person("Julie", false, lara, bob)val persons = List(lara, bob, julie)val filter_persons = for (p <- persons; if !p.isMale; c <- p.children)yield (p.name, c.name)filter_persons.foreach(println)
如果使用filter、map和flatMap的话,上述还有一种非常复杂的写法。但是这种写法既不方便编写,也不方便阅读,还是推荐上面的写法:
val filter_persons = persons filter (p => !p.isMale) flatMap(p => (p.children map(c => (p.name, c.name))))filter_persons.foreach(println)
6.1 更多参考示例
/* case class Person(name: String,isMale: Boolean,children: Person*)val lara = Person("Lara", false)val bob = Person("Bob", true)val julie = Person("Julie", false, lara, bob)val persons = List(lara, bob, julie)val filter_person = for (p <- persons; if !p.isMale; c <- p.children)yield (p.name, c.name)filter_person.foreach(println)val filter_persons = persons filter (p => !p.isMale) flatMap(p => (p.children map(c => (p.name, c.name))))filter_persons.foreach(println)*/case class Book(title: String, authors: String*)val books: List[Book] = List (Book("Structure and Interpretation of Computer Programs","Abelson, Harold","Sussman, Gerald J."),Book("Principles of Compiler Design","Aho, Alfred", "Ullman, Jeffrey"),Book("Elements of ML Programming","Ullman, Jeffrey"),Book("The Java Language Specification","Gosling, James", "Joy, Bill", "Steele Guy", "Bracha, Gilad"))// 查询姓“Gosling”作者的书籍val res1 = for (b <- books; a <- b.authors if a startsWith("Gosling")) yield b.titleres1.foreach(println) // The Java Language Specification// 查询所有包含“Program”的书名val res2 = for (b <- books if (b.title indexOf "Program") >= 0) yield b.titleres2.foreach(println)// 查找至少编写了两本书的作者// 由于 res3 会导致同一作者多次出现,所以需要去重val res3 = for (b1 <- books; b2 <- books if b1 != b2;a1 <- b1.authors; a2 <- b2.authors if a1 == a2) yield a1res3.foreach(println)def removeDuplicates[A](xs: List[A]): List[A] = {if (xs.isEmpty) xselsexs.head :: removeDuplicates(for (x <- xs if x != xs.head) yield x)}val res4 = removeDuplicates(res3)res4.foreach(println)
