除了 SQL,我所学习的编程语言几乎包含for
循环,熟练合适地使用for
循环,可以帮助我们完成复杂的任务。
1. 遍历集合
我们通过for
循环来输出当前文件下所有文件(或文件夹):
val filesHere = (new File(".")).listFiles
for (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(".")).listFiles
for (
file <- filesHere
if file.isFile;
if file.getName.endsWith(".scala")
) println(file)
如果for
循环使用{}
的话,可以不需要加分号,如下所示:
val filesHere = (new File(".")).listFiles
for {
file <- filesHere
if file.isFile
if file.getName.endsWith(".scala")
} println(file)
3. 嵌套循环
for
循环中还可以继续嵌套循环,和上面多个过滤器一样,使用;
进行分隔:
import java.io.File
import scala.io.Source
val filesHere = (new File("./control_structure")).listFiles
def fileLines(file: File) = Source.fromFile(file, "UTF-8").getLines().toList
def grep(pattern: String) =
for (
file <- filesHere
if 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 <- filesHere
if file.getName.endsWith(".scala");
line <- fileLines(file)
trimmed = line.trim
if trimmed.matches(pattern)
) println(file + ": " + trimmed)
4. 生成新集合
可以通过对集合进行遍历,对集合中的元素进行操作,例如过滤、四则运算等,从而生成一个新的集合,学习过 Python 的同学将此过程理解为列表推导式。
下面示例中,我们将学习一个新的知识yield
,用于生成一个只包含后缀名为.scala
的新集合,:
val filesHere = (new File("./control_structure")).listFiles
def scalaFiles =
for {
file <- filesHere
if file.getName.endsWith(".scala")
} yield file
注意:这里将
def
换成val
也是可以的,且变量的类型都是一致的。
当然,我们的for
循环可以更复杂一些,例如上面提到的嵌套循环:
val forLineLengths =
for {
file <- filesHere
if file.getName.endsWith(".scala");
line <- fileLines(file)
trimmed = line.trim
if 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 4
arr.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.title
res1.foreach(println) // The Java Language Specification
// 查询所有包含“Program”的书名
val res2 = for (b <- books if (b.title indexOf "Program") >= 0) yield b.title
res2.foreach(println)
// 查找至少编写了两本书的作者
// 由于 res3 会导致同一作者多次出现,所以需要去重
val res3 = for (b1 <- books; b2 <- books if b1 != b2;
a1 <- b1.authors; a2 <- b2.authors if a1 == a2) yield a1
res3.foreach(println)
def removeDuplicates[A](xs: List[A]): List[A] = {
if (xs.isEmpty) xs
else
xs.head :: removeDuplicates(
for (x <- xs if x != xs.head) yield x
)
}
val res4 = removeDuplicates(res3)
res4.foreach(println)