scala函数式编程基础

函数式编程是一种编程范式,它强调使用函数来处理数据,而不是像面向对象编程那样使用对象。这种编程范式的核心概念是函数的纯函数和高阶函数。

纯函数是指函数只依赖于它的输入参数,而不依赖于其他状态或数据,它不会修改任何外部变量或状态,且每次相同的输入参数都会产生相同的输出值。这样的函数可以提高代码的可读性、复用性、测试性和并发性。

而高阶函数是指函数可以接受一个或多个函数作为参数或返回一个函数作为结果,它可以将函数作为一等公民来使用和组合。通过将函数作为参数传递,可以将不同的函数功能组合在一起,从而实现更复杂的逻辑处理。

函数式编程还包括使用不可变数据、避免副作用、使用管道和函数组合等其他概念。它能够简化代码,提高代码的可维护性和可复用性,适合处理大规模、高并发的数据处理问题。

1.函数的定义与使用

1.1函数的定义

函数的定义语法:

Scala中定义一个函数可以采用如下的语法:

def functionName ([参数名: 参数类型, ...]): 返回类型 = {
  // 函数体,包括对参数的处理和逻辑处理等
  return [返回值]
}
def 方法名(参数列表):结果类型={方法体}

其中,参数列表可以包含多个参数,每个参数由参数名和参数类型组成,用逗号分隔。返回类型的声明可以省略,Scala会自动推断出函数的返回类型。在函数体中,处理参数并实现函数逻辑的代码都应该放置在函数体内,最后通过return语句返回函数结果。如果返回类型是Unit,则可以省略return语句,或者直接使用空括号表示返回空值。例如:

def sayHello(name: String): Unit = {
  println("Hello, " + name)
}

这个函数的作用是打印出"Hello, ",以及传入的name参数。由于返回类型是Unit,可以省略return语句。

函数的类型和值:

  • 函数类型:函数类型指的是函数的参数类型和返回值类型的组合,可以将函数类型视为一种类型标识符。例如(String, Int) => Double表示接受一个字符串和一个整数参数,并返回一个双精度浮点数类型的函数类型。

  • 函数值:函数值指的是将函数存储在变量上,使得这个变量可以像其他值一样被操作。在Scala中,函数值是将函数作为一个对象进行封装的结果,可以将它作为参数传递,也可以从函数中返回它。

在Scala中,函数类型和函数值都与Lambda表达式密切相关。Lambda表达式是一种定义匿名函数的方式,它可以用于实现这两种概念。例如:

// 定义函数类型
val funcType: (String, Int) => Double = (str: String, i: Int) => str.toDouble + i.toDouble
// 定义函数变量
val funcValue: (Int) => Int = (i: Int) => i * i
// 从另一个函数中返回函数值
def getFuncValue(): (Int) => Int = (i: Int) => i * i

在这个例子中,分别定义了一个函数类型funcType和一个函数变量funcValue,并在一个函数中返回了一个函数值。在定义Lambda表达式时,指定了参数类型和返回值类型,然后实现了对应的函数逻辑。

头等公民:

在计算机科学中,头等公民(First-Class Citizen)指的是某种语言特性或概念在该语言中具有同等地位和权利,可以像其他类型一样自由使用、添加、修改和删除。在Scala中,以下几个被认为是头等公民:

  1. 值:Scala中所有值都是头等公民,包括基本类型、对象和函数。

  2. 函数类型:Scala中的函数类型是头等公民,可以被赋值给变量,作为参数传递,或者从函数返回。

  3. 集合类型:Scala中的集合类型(如List、Set、Map等)也是头等公民,可以像其他类型一样自由使用。

在Scala中,函数类型是一个非常重要的概念,函数类型的变量可以被用来存储函数或者Lambda表达式,从而可以像其他值一样传递和操作。这种能力非常强大,可以让写出更具表现力和灵活性的代码。

例如,以下代码展示了如何将一个函数类型传递给另一个函数作为参数:

def doSomething(f: Int => String): String = {
  f(10)
}
​
val myFunction: Int => String = (i: Int) => s"The number is $i"
​
println(doSomething(myFunction)) // 输出 The number is 10

在这个例子中,定义了一个函数doSomething,它接受一个函数类型参数f,并将整数10作为参数传递给这个函数。定义了一个函数类型变量myFunction,它将一个整数转换为一个字符串,然后将这个函数作为参数传递给了doSomething函数。由于函数类型是头等公民,所以可以像操作其他类型一样操作函数类型,这使得代码更加灵活和易于编写。

1.2 函数的使用

Scala中的函数是头等公民,可以像其他的数据类型一样传递、赋值、操作等。函数的定义使用关键字def,函数名,参数列表和函数体组成。以下是一个简单的Scala函数示例:

def add(a: Int, b: Int): Int = {
  a + b
}
​
// 调用函数
val result = add(2, 3)
println(result) // 输出 5

在该示例中,定义了一个add函数,接受两个整数类型的参数,然后返回这两个数的和。使用关键字val声明了一个变量result,并将add函数的结果赋给了这个变量。最后,使用println函数将结果打印到控制台。

Lambda表达式:

Lambda表达式,也称为匿名函数,是一种没有函数名称的简短函数定义。在Scala中,Lambda表达式使用箭头(=>)符号定义,类似于Java 8中的Lambda表达式。Lambda表达式可以作为任何接受函数类型参数的方法的参数传递,或者在需要函数时返回它们。

以下是一个使用Lambda表达式的简单Scala示例:

val add = (a: Int, b: Int) => a + b
​
// 调用Lambda函数
val result = add(2, 3)
println(result) // 输出 5

在这个例子中,定义了一个接受两个整数参数并返回它们之和的Lambda表达式,并且将其赋值给变量add。使用val关键字定义add变量,这意味着不能改变add变量所引用的Lambda表达式。使用add变量来调用Lambda函数,并将结果赋给result变量。最后,使用println函数将结果打印到控制台。

下划线:

当Lambda表达式只有一个参数时,在Scala中可以使用下划线符号(_)来代替这个参数。例如,以下是使用下划线符号的Lambda表达式示例:

val square = (x: Int) => x * x
​
// 等价于以下的定义
val square2: Int => Int = _ * _
​
// 调用Lambda函数
val result = square(5)
val result2 = square2(5)
println(result) // 输出 25
println(result2) // 输出 25

在这个例子中,定义了一个接受一个整数参数并返回该参数的平方的Lambda表达式。还定义了一个等效的Lambda表达式square2,使用下划线符号来代替参数。最后,使用squaresquare2变量来调用Lambda函数,并将结果赋给resultresult2变量。最后,使用println函数将结果打印到控制台。

2.高阶函数

当一个函数包含其它函数作为其参数或者返回结果为一个函数时,该函数被称为高阶函数。使用高阶函数可以让代码更加模块化和可重用,而不必重复编写相同的代码。

2.1高阶函数

假设需要分别计算从一个整数到另一个整数的“连加和”、“平方和”以及“2的幂次和”。

2.2闭包

在Scala中,闭包是指一个函数或Lambda表达式,它可以访问并操作在其创建时存在的非局部变量。这意味着,即使变量不再处于其作用域范围内,函数/表达式仍然可以访问它们。闭包在Scala中通常用于将状态保留在函数之外,以便在将来的调用中继续使用。

以下是一个使用闭包的简单Scala示例:

def multiply(m: Int): Int => Int = {
  (n: Int) => n * m
}

// 声明一个闭包
val multiplyByTwo = multiply(2)

// 使用闭包
val result = multiplyByTwo(5)
println(result) // 输出 10

在这个例子中,定义了一个接受一个整数参数并返回一个函数的函数multiply。该函数返回一个Lambda表达式,它接受一个整数参数并返回两个参数的乘积。通过传递参数2调用multiply函数,并将返回的Lambda表达式赋值给变量multiplyByTwo。这创建了一个闭包,其中包含对变量m的引用。使用multiplyByTwo变量调用Lambda函数,并将结果赋给result变量。最后,使用println函数将结果打印到控制台。

闭包的使用可以让代码更加灵活和模块化。它们通常用于将状态保留在函数之外,以便在将来的调用中继续使用。但是,在使用闭包时,需要注意内存管理问题,因为闭包可能会导致一些对象保持活动状态并占用内存,直到程序终止。

2.3偏应用函数和carry化

偏应用函数:

偏应用函数(Partial Function)是指一个函数预定义了部分参数,生成了一个新的函数。具体来说,偏应用函数可以当做一种特殊的函数,它可以接收一部分参数,并且等待后续提供剩余参数后,生成一个新的完整的参数列表,并执行函数体。

Scala中可以通过下划线来指定需要预定义的参数,注意下划线的位置必须与预定义参数的位置一一对应。例如:

def add(x: Int, y: Int, z: Int) = x + y + z

// 定义一个偏应用函数
val partialAdd = add(_: Int, 2, _: Int)

// 调用偏应用函数
println(partialAdd(1,3)) // 输出 6

在这个例子中,定义了一个接收三个参数的add函数。然后,使用下划线来预定义第一个和第三个参数,生成了一个新的偏应用函数partialAdd。这样,partialAdd函数只需要提供第一个和第三个参数,即可调用add函数计算结果。

可以看到,偏应用函数可以简化代码,而且它支持更好的代码重用。因为可以将其存储在变量中,并随时调用。由于Scala中使用了函数式编程,因此HOF(higher order function)和偏应用函数是很常见的编程模式。

carry化:

Currying是一种将多个参数的函数转换为一系列单个参数函数的技术。这种技术被称为部分应用,可以按照需要将一个多参数函数部分应用于其中一些参数,以创建一个新函数。

在Scala中,可以使用“函数柯里化”(Function Currying)来实现这一点。可以将一个多参数函数转换为一系列专门接受一个参数的函数。例如,对于一个接受两个整数参数的函数,可以将其柯里化为两个接受一个整数参数的函数。

以下是一个使用柯里化的Scala示例:

def add(x: Int)(y: Int) = x + y

// 使用柯里化的函数
val result = add(3)(5)

println(result) // 输出 8

在这个例子中,定义了一个接收两个整数参数的add函数。然后,使用两个括号将该函数进行了柯里化。这样,现在需要先调用第一个函数,它只接受一个整数参数,并返回另一个函数(第二个括号)。第二个函数接受另一个整数参数,并将该参数添加到第一个参数中。最后,使用add(3)(5)来调用柯里化的函数,计算结果并打印到控制台。

可以看到,使用柯里化使得代码更加灵活,可读性更高。通过将函数的一个参数固定,在某些情况下可以减少代码冗余,同时增加代码的可复用性。

3.针对容器的操作

在Scala中,针对容器的操作是指对如List、Map、Set等集合类型进行的操作。这些操作通常包括对容器元素的遍历、过滤、映射、组合、排序、分组等各种类型的操作。

以下是一些Scala中常见的针对容器的操作:

  • 遍历:使用foreach、for、map等操作遍历集合元素。

  • 过滤:使用filter、takeWhile、dropWhile等操作过滤集合元素。

  • 映射:使用map、flatMap、collect等操作将集合元素转换为另一个集合。

  • 组合:使用zip、zipWithIndex、sliding等操作将两个或多个集合组合在一起。

  • 排序:使用sortBy、sortWith、sorted等操作对集合进行排序。

  • 分组:使用groupBy等操作将集合元素按照某些条件进行分组。

Scala提供了丰富的集合操作方法,可以根据具体场景选择适合的方法来处理容器。这些操作通常可以通过链式调用组合使用,以实现更加复杂的操作。此外,Scala的集合库不仅包括不可变集合,还包括可变集合和并发集合库,能够满足各种不同的使用场景。

3.1遍历操作

遍历操作是指遍历容器中的每一个元素,执行特定的操作。

foreach方法:

在Scala中,foreach方法用于对集合中的每个元素应用指定的函数,以便遍历集合中的所有元素。

foreach方法的语法如下:

def foreach[U](f: (A) => U): Unit

其中,f是一个函数,接受当前集合迭代器中的一个元素作为参数。函数返回值类型为Unit,表示没有返回值。

举个例子,可以使用foreach方法打印出一个List中的所有元素:

val list = List(1, 2, 3, 4)

list.foreach(elem => println(elem))

在这个例子中,定义了一个包含四个整数的List,并使用foreach方法遍历该List。在遍历过程中,使用匿名函数打印每个元素的值。

值得注意的是,foreach方法返回类型为Unit,因此不能使用它来构造新的集合。相比之下,map方法将对每个元素应用指定的函数,并返回结果集合。

除了List,foreach方法也可以应用于Map、Set等不同类型的集合。例如,以下是一个使用foreach方法遍历Map的例子:

val map = Map("one" -> 1, "two" -> 2, "three" -> 3)

map.foreach{ case (key, value) =>
  println(key + " -> " + value)
}

在这个例子中,定义了一个包含三个键值对的Map,并使用foreach方法遍历该Map。在遍历过程中,使用解构(Destructuring)语法来取出Map中的键值对,并打印每个元素的键和值。

综上所述,foreach方法是Scala集合库中一个常用的方法,它可以用于对集合元素进行遍历、输出、计算等各种类型的操作。

foreach语句的模式匹配表达式:

在Scala中,foreach语句可以使用模式匹配表达式来遍历集合元素,并对元素进行分别处理。该语法通常使用case语句来进行模式匹配,类似于match表达式中的语法。

举例来说,假设有一个包含不同类型的元素的List集合,可以使用模式匹配语法在遍历过程中对每个元素进行分类处理,如下所示:

val list = List(1, "hello", true, 3.14)

list.foreach {
  case i: Int => println(s"An integer: $i")
  case s: String => println(s"A string: $s")
  case b: Boolean => println(s"A boolean: $b")
  case _ => println("Something else")
}

在上述代码中,首先定义了一个混合类型的List集合,包含整数、字符串、布尔值和浮点数等不同类型的元素。然后使用foreach语句对该集合进行遍历,并使用模式匹配表达式来对每个元素进行类型分类处理。

在这个示例中,定义了四个case分支分别对整数、字符串、布尔值和其他类型的元素进行处理。对于每个元素,如果其类型匹配上述任一种情况,则会打印相应的字符串提示信息,否则打印出 "Something else"。

这种使用模式匹配的方式使得可以更加灵活和精确地处理集合中的元素,并且代码更加简洁易读。但需要注意的是,在使用模式匹配时,需要确保每个元素都至少可以匹配到一个case语句,否则会抛出“MatchError”异常。

for循环:

在Scala中,可以使用for循环对集合进行遍历。for循环可以用于遍历任何遵循迭代器协议的集合类型,比如数组、列表、映射、集合等等。在for循环内部,可以通过val定义一个变量,用于依次采样集合中的每个元素。下面是一个简单的示例:

val list = List(1, 2, 3, 4)

for (i <- list) {
  println(i)
}

上面的代码遍历了一个整数列表,并打印了每个元素的值。输出结果为:

1
2
3
4

除了直接遍历集合外,还可以在for循环中使用if语句来过滤出符合要求的元素,如下所示:

val list = List(1, 2, 3, 4)

for (i <- list if i % 2 == 0) {
  println(i)
}

上面的代码遍历了一个整数列表,并只打印了其中偶数元素的值。输出结果为:

2
4

除此之外,还可以使用多个生成器来遍历多个集合,并在其中使用if语句对元素进行筛选和过滤。下面是一个示例代码:

val list1 = List(1, 2, 3)
val list2 = List("a", "b", "c")

for {
  i <- list1
  j <- list2 if i % 2 == 0
} {
  println(s"$i $j")
}

上面的代码中,使用两个生成器分别遍历了两个列表,并使用if语句过滤了其中的偶数元素。输出结果为:

2 a
2 b
2 c

通过这些示例,可以看到,for循环在遍历集合时非常方便,而且可以与其他语言的循环语句相比,使用起来更加简单易懂。

3.2映射操作

映射是指通过对容器中的元素进行某些运算来生成一个新的容器。两个典型的映射操作是map方法和flatMap方法。

map方法:

在Scala中,map()是一种函数式编程的高阶函数,它可以用于对集合中的每个元素进行映射操作,返回一个新的集合。map()方法接受一个函数作为参数,该函数接受一个元素并返回一个新的元素。map()方法会遍历集合中的每个元素,并将该元素作为参数传递给指定的函数,返回的结果将被收集到新的集合中。下面是一个简单的示例代码:

val list = List(1, 2, 3, 4)

val newList = list.map(_ * 2)

println(newList) // List(2, 4, 6, 8)

上面的代码中,使用map()方法将List中的每个元素都乘以2,返回一个新的List对象。这个新的List对象包含了原来List中的每个元素的倍数。在这个例子中,使用了一个匿名函数来作为map()方法的参数:

_ * 2

这是一个简单的乘法函数,它接受一个整数,并将其乘以2。在Scala中,经常使用这种匿名函数来简洁地描述一个操作。

除了List外,其他的集合类型(如Array、Set等)也支持map()方法。而且,map()方法不仅仅局限于数值类型的集合,它同样适用于任何类型的集合,包括自定义类型的集合。

flatMap方法:

在Scala中,flatMap()方法是一种高阶函数,用于对集合中的元素进行映射操作。这个方法与map()方法类似,但是flatMap()方法会把返回值展平成一个扁平化的集合。简单来说,在一个集合中,flatMap()方法对每个元素都应用一个函数,返回值是一个序列(sequence),然后把这些序列连接起来,得到一个新的集合。

下面是一个简单的示例代码,展示了flatMap()方法的使用:

val list = List("hello world", "scala programming", "flatMap example")
val newList = list.flatMap(_.split(" "))
println(newList) // List(hello, world, scala, programming, flatMap, example)

在这个例子中,使用flatMap()方法把每个字符串变成一个单词序列(split()方法使用空格分割字符串),然后把所有单词序列连接起来,得到了一个新的List对象。

需要注意的是,flatMap()方法返回的序列可以是任何类型的集合。例如,可以返回一个List对象,也可以返回一个Set对象或者一个数组。这主要取决于输入集合的类型和将要应用的函数的返回值类型。

另外,flatMap()方法还有一些其他的应用场景。例如,它可以用来展平多重序列中的元素,或者把序列中的元素转化为一个新的序列。总之,flatMap()方法是Scala中一个非常有用和强大的函数。

3.3过滤操作

过滤操作指的是从一个数据集合中筛选出满足特定条件的元素,并将它们组合成一个新的数据集合的过程。

过滤操作通常是在数组、列表、集合、字典等数据结构中进行的。在这些数据结构中,过滤操作通常都会返回一个新的集合,其中只包含满足指定条件的元素。这个过程可以帮助减少需要处理的数据量,从而提高程序的性能和效率。

filter方法:

在Scala中,filter方法是一种常用的集合操作,用于筛选出集合中符合条件的元素,并将它们组成一个新的集合返回。

filter方法接收一个谓词函数作为参数,该函数会被应用于集合中的每一个元素,如果该元素满足谓词函数的条件,则将其保留在结果集合中,否则将其过滤掉。

filter方法的语法如下:

def filter(p: (A) ⇒ Boolean): Repr

其中,p是一个谓词函数,它的参数是集合中的每一个元素,返回值是一个Boolean类型的值。Repr表示的是与原始集合类型相同的新的集合类型。

下面是一些使用filter方法的示例:

// 过滤出偶数
val list = List(1, 2, 3, 4, 5, 6)
val evenList = list.filter(_ % 2 == 0) // List(2, 4, 6)

// 过滤出字符串中包含"o"的元素
val set = Set("apple", "orange", "pear", "banana")
val containOSet = set.filter(_.contains("o")) // Set("orange", "pear")

// 过滤出key为偶数的元素
val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
val evenKeyMap = map.filterKeys(_ % 2 == 0) // Map(2 -> "two")

需要注意的是,filter方法会返回一个新的集合,而不会修改原始集合。

filterNot方法:

在Scala中,filterNot方法是一种用于集合过滤的函数,它与filter方法的作用类似,不同之处在于filterNot方法会筛选出不符合指定条件的元素,并将其组成一个新的集合返回。

filterNot方法接收一个谓词函数作为参数,该函数会被应用于集合中的每一个元素,如果该元素不符合谓词函数的条件,则将其保留在结果集合中,否则将其过滤掉。

filterNot方法的语法如下:

def filterNot(p: (A) => Boolean): Repr

其中,p是一个谓词函数,它的参数是集合中的每一个元素,返回值是一个Boolean类型的值。Repr表示的是与原始集合类型相同的新的集合类型。

下面是一些使用filterNot方法的示例:

// 过滤出奇数
val list = List(1, 2, 3, 4, 5, 6)
val oddList = list.filterNot(_ % 2 == 0) // List(1, 3, 5)

// 过滤出字符串中不包含"o"的元素
val set = Set("apple", "orange", "pear", "banana")
val notContainOSet = set.filterNot(_.contains("o")) // Set("apple", "banana")

// 过滤出key不为偶数的元素
val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
val oddKeyMap = map.filterNot(_._1 % 2 == 0) // Map(1 -> "one", 3 -> "three")

需要注意的是,filterNot方法会返回一个新的集合,而不会修改原始集合。

3.4规约操作

在Scala中,规约操作(reduction)是一种集合操作,它将一个集合中的所有元素进行聚合(即规约)操作,并返回一个结果。

Scala标准库中提供了两种常用的规约

reduce方法:

在Scala中,reduce方法是一种集合规约操作,它对集合中的所有元素进行聚合操作,并返回一个结果。reduce方法没有指定初始值,而是使用集合中的第一个元素作为初始值,并将剩余的元素和初始值依次应用一个函数,将结果作为下一次计算的初始值,直到遍历完整个集合。最终的结果是最后一次计算的结果。

reduce方法的语法如下:

def reduce(op: (A, A) => A): A

其中,op是一个二元函数,它接收两个类型为A的参数,返回一个类型为A的值。A表示集合中元素的类型。

下面是使用reduce方法的示例:

// 使用reduce方法求和
val list = List(1, 2, 3, 4, 5)
val sum = list.reduce(_ + _) // 15

// 使用reduce方法求最大值
val max = list.reduce((a, b) => if (a > b) a else b) // 5

// 使用reduce方法求字符串连接
val words = List("Hello", "world", "!")
val sentence = words.reduce(_ + " " + _) // "Hello world !"

需要注意的是,reduce方法要求集合中至少有一个元素。如果集合为空,则会抛出UnsupportedOperationException异常。如果需要处理空集合的情况,可以使用reduceOption方法,该方法会返回一个Option类型的结果,如果集合为空,则返回None,否则返回Some

reduceLeft 和 reduceRight

reduceLeftreduceRight 是数组的两个高阶函数,它们都接受一个函数作为参数,并将该函数应用于数组中的元素来返回一个单一的值。

这两个函数的主要区别在于它们对于数组元素的处理方式:

  • reduceLeft 从数组的左侧开始,从第一个元素开始应用函数,将函数的结果与下一个元素应用函数的结果进行合并,直到到达数组的最后一个元素。

  • reduceRight 从数组的右侧开始,从最后一个元素开始应用函数,将函数的结果与前一个元素应用函数的结果进行合并,直到到达数组的第一个元素。

实例:使用 reduceLeftreduceRight 函数来迭代数组中的元素,并从中减去每个元素,将每个元素的差计算出来。

const numbers = [1, 2, 3, 4, 5];

// 使用 reduceLeft
const differenceLeft = numbers.reduce((acc, curr) => acc - curr);
console.log(differenceLeft); // -13     ((((1 - 2) - 3) - 4) - 5) = -13


// 使用 reduceRight
const differenceRight = numbers.reduceRight((acc, curr) => acc - curr);
console.log(differenceRight); // 3       (1 - (2 - (3 - (4 - 5)))) = 3

fold方法:

一个双参数列表的函数,从提供的初始值开始规约。第一个参数列表接受一个规约的初始值,第二个参数列表接受与reduce中一样的二元函数参数。

fold 方法是一种类似于 reduce 方法的函数,通常用于函数式编程中。与 reduce 不同的是,fold 方法需要一个初始值作为累加器的起点,而 reduce 方法则使用数组的第一个元素作为累加器的起点。

val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.fold(0)((acc, curr) => acc + curr)
println(sum) // 15

foldLeft和foldRight:前者从左到右进行遍历,后者从右到左进行遍历。

foldLeftfoldRight 是 Scala 集合类中的高阶函数,类似于 reducefold 方法。它们的作用是将集合中的元素按照某种方式组合起来,返回最终的结果。

foldLeft 方法将集合元素从左到右进行组合,即从第一个元素开始依次遍历集合,将每个元素作为函数的第二个参数进行累加计算,并将计算结果作为下一次调用函数的第一个参数。它的语法如下:

def foldLeft[B](z: B)(op: (B, A) => B): B

其中,z 是初始值,类型为 Bop 是函数,接受两个参数:第一个参数是类型为 B 的累加器,第二个参数是类型为 A 的集合元素。函数返回一个类型为 B 的值,作为下一次调用的累加器。最终返回的是类型为 B 的结果。

以下是一个使用 foldLeft 方法计算数组中所有元素的和的示例:

val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.foldLeft(0)((acc, curr) => acc + curr)
println(sum) // 15

在以上示例中,首先创建了一个包含整数的数组。然后,调用了 foldLeft 方法,将初始值设为 0foldLeft 方法接受一个函数作为参数,该函数接受两个参数:当前累加器的值 acc 和当前元素的值 curr。在这个例子中,使用一个 lambda 函数 ((acc, curr) => acc + curr),它返回两个参数的和。最终,foldLeft 方法返回了数组所有元素的和 15

foldLeft 不同,foldRight 方法将集合元素从右到左进行组合,即从最后一个元素开始依次遍历集合,将每个元素作为函数的第二个参数进行累加计算,并将计算结果作为下一次调用函数的第一个参数。它的语法如下:

def foldRight[B](z: B)(op: (A, B) => B): B

其中,z 是初始值,类型为 Bop 是函数,接受两个参数:第一个参数是类型为 A 的集合元素,第二个参数是类型为 B 的累加器。函数返回一个类型为 B 的值,作为下一次调用的累加器。最终返回的是类型为 B 的结果。

以下是一个使用 foldRight 方法计算数组中所有元素的和的示例:

val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.foldRight(0)((curr, acc) => curr + acc)
println(sum) // 15

在以上示例中,同样创建了一个包含整数的数组。然后,调用了 foldRight 方法,将初始值设为 0foldRight 方法接受一个函数作为参数,该函数接受两个参数:当前元素的值 curr 和当前累加器的值 acc。在这个例子中,同样使用一个 lambda 函数 ((curr, acc) => curr + acc),它返回两个参数的和。最终,foldRight 方法同样返回了数组所有元素的和 15

需要注意的是,foldLeftfoldRight 的执行顺序是不同的。foldLeft 从左向右执行,而 foldRight 从右向左执行。此外,由于计算顺序不同,对于非关联操作,foldLeftfoldRight 的结果也会不同。

3.5拆分操

在 Scala 中,可以使用 split 方法来拆分字符串,该方法有多种形式,最常用的是使用一个字符串作为参数来指定分隔符。例如:

val str = "hello,world"
val arr = str.split(",")

在上面的示例中,创建了一个字符串变量 str,它包含了一个逗号作为分隔符的两个单词。调用 split 方法,并将逗号作为参数传递给它,这样它就会将字符串拆分为两个部分,并返回一个包含这两个部分的数组。

如果想使用多个分隔符来拆分字符串,可以在参数中使用正则表达式。例如:

val str = "hello,world|foo.bar"
val arr = str.split("[,|.]")

在上面的示例中,使用正则表达式 "[,|.]" 来指定多个分隔符,包括逗号、竖线和句点。当 split 方法在这个字符串上被调用时,它会根据这些分隔符来拆分字符串,并返回一个包含所有子字符串的数组。

需要注意的是,如果要使用特殊字符作为分隔符,需要使用反斜杠对其进行转义。例如,如果要使用句点作为分隔符,则需要使用正则表达式 "\."

除了 split 方法外,Scala 还提供了其他用于字符串操作的方法,例如 substringindexOfreplaceAll 等,这些方法可以对字符串进行切割、查找、替换等操作。

4.函数式编程实例WordCount

源代码:

   import java.io.File
   import scala.io.Source
   import collection.mutable.Map
   object WordCount {
       def main(args: Array[String]) {
           val dirfile=new File("testfiles")
           val files  = dirfile.listFiles
           val results = Map.empty[String,Int]
           for(file <-files) {
              val data= Source.fromFile(file)
              val strs =data.getLines.flatMap{s =>s.split(" ")}
              strs foreach { word =>
                          if (results.contains(word))
                          results(word)+=1 else  results(word)=1
                              }
              }
          results foreach{case (k,v) => println(s"$k:$v")}
      }
  }
// 导入Java IO和Scala IO库,以及可变Map类
import java.io.File
import scala.io.Source
import collection.mutable.Map

// 定义WordCount对象
object WordCount {
    // 定义main函数,程序从这里开始执行
    def main(args: Array[String]) {
        // 定义表示目录的File类,以及该目录下的所有文件
        val dirfile=new File("testfiles") // 目录文件对象
        val files  = dirfile.listFiles // 目录下的所有文件对象列表

        // 定义结果Map,用于存储单词和其出现次数
        val results = Map.empty[String,Int] // 可变的Map对象

        // 遍历文件列表,逐个读取文件中的单词并统计
        for(file <- files) {
            // 读取文件内容,并将每一行按照空格切分成单词
            val data = Source.fromFile(file) // 读取文本文件内容
            val words = data.getLines.flatMap(line => line.split("\\W")) // 切分出单词列表

            // 遍历单词列表,将单词作为key,出现次数作为value存储到结果Map中
            words foreach { word =>
                if (results.contains(word))
                    results(word) += 1 // 该单词已存在,出现次数+1
                else  
                    results(word) = 1 // 该单词未存在,出现次数为1
            }
        }

        // 遍历结果Map,输出每个单词及其出现次数
        results foreach { case (word, count) =>
            println(s"$word: $count")
        }
    }
}

在命令行窗口中,使用以下命令生成一个文本文件,用于测试WordCount程序:

echo "hello world" > testfiles/test.txt
echo "hello scala" > testfiles/test2.txt
echo "hello world, hello scala" > testfiles/test3.txt

这些命令用于将三行文本输出到三个不同的文件中。请确保当前目录下存在名为"testfiles"的子目录,否则会出错。测试文件已经准备好了,可以运行WordCount程序进行统计和测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潜意识^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值