Scala集合类上的高阶方法

Scala集合的真正强大之处在于带来了其高阶方法。一个高阶方法使用一个函数作为其输入参数。需要特别注意的是,一个高阶方法并不改变集合。下面是Scala集合的一些最主要的高阶方法。

1、map

Scala 集合的map 方法将其输入函数应用到集合中所有元素上,并返回另一个集合。返回的集合具有与调用map 方法的那个集合相同数量的元素。不过,在返回的集合中的元素并不是原始集合中相同的类型。示例如下:

val xs = List(1,2,3,4);
val ys = xs.map((x:Int) => x*10.0);

在上面的代码中,xs 的类型是List[Int],而ys 的类型是List[Double]。

如果一个函数只有一个参数,那么圆括号可以被花括号替换。下面的两个语句等价:

val ys = xs.map((x:Int) => x*10.0);
val ys = xs.map{(x:Int) => x*10.0};

正如前面讲过的,Scala 允许使用运算符标记调用任何方法。要进一步提高可读性,前面的代码也可以写成下面这样:

val ys = xs map {(x:Int) => x*10.0};

Scala 可以从集合的类型推断出传入的参数类型,因此可以忽略掉参数类型。下面两个语句是等价的:

val ys = xs map {(x:Int) => x*10.0};
val ys = xs map {x => x*10.0};

如果一个函数字面量的输入参数只在函数体中使用一次,那么右箭头和该箭头的左侧可以从函数字面量中被删除。我们可以只编写函数字面量的函数体。下面的两个语句是等价的:

val ys = xs map {x => x*10.0};
val ys = xs map {_ * 10.0};

上面代码中,下划线(_)字符代表函数字面量的输入传给map 方法。上面的代码可以被理解为集合xs 中的每个元素乘以10。 总结来说,下面的代码分别代表了相同语句的冗长的版本和简洁的版本:

val ys = xs.map((x:Int) => x*10.0);
val ys = xs map {_ * 10.0};

下面是使用map方法的另一个示例:

// 快速产生0.1, 0.2, 0.3等方式的数字
(1 to 9).map(0.1 * _).foreach(println(_))

// 打印三角形
(1 to 9).map("*" * _).foreach(println(_))

2、flatMap

Scala 集合的flatMap将集合中的所有集合元素展开,并形成单个集合。flatMap 方法类似于map。它接收一个函数作为输入,并将该输入函数应用到集合中的每个元素,并返回另一个集合作为结果。不过,传递给flatMap 的函数为原始集合中的每个元素都生成一个集合。因此,应用该输入函数的结果是一个集合的集合。如果相同的输入函数被传递给map 方法,它将返回一个集合的集合。而flatMap 方法则返回一个flattened集合。

下面的例子描述了flatMap 的使用:

val numbersList = List(List(1,2,3), List(4,5,6), List(7,8,9))
numbersList.flatMap(list => list)

输出结果如下:

res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

请看下面的代码:

val line = "Scala is fun";
val SingleSpace = " ";
val words = line.split(SingleSpace);
val arrayOfChars = words flatMap {_.toList};

输出结果如下:

arrayOfChars: Array[Char] = Array(S, c, a, l, a, i, s, f, u, n)

集合的toList 方法创建集合中所有元素的一个列表。对于将一个字符串、数组、set、或任何其它集合类型转换为一个list,该方法很有用。

3、filter

方法filter 应用一个断言到集合中的每个元素,并返回另一个集合(由断言返回true 的元素组成)。一个断言是返回一个Boolean 值的函数。它返回true 或false。

val xs = (1 to 100).toList;
val even = xs filter {_%2 == 0};

使用filter方法,按照过滤条件,将原集合中不符合条件的数据过滤掉,输出所有匹配某个特定条件的元素,得到一个序列中的所有偶数:

(1 to 9).filter(line => line % 2 == 0).foreach(println(_))
(1 to 9).filter(_ % 2 ==0).foreach(println)     // 与上一句等价

4、foreach

Scala 集合的foreach 方法在集合的每个元素上调用其输入函数,但并不返回任何东西。这类似于map方法。这两个方法间的唯一区别在于map 返回一个集合,而foreach 并不返回任何东西。由于它的副作用,这是一个很少使用的方法。

val words = "Scala is fun".split(" ");
words.foreach(println);

5、reduce

方法reduce 返回单个值。正如其名所暗示的,它将一个集合归约为一个单个的值。方法reduce 的输入函数同时接收两个输入并返回一个值。本质上来说,输入函数是一个可组合和可交换的二元运算符。

请看下面的示例:

val xs = List(2,4,6,8,10);
val sum = xs reduce {(x,y) => x+y};
val product = xs reduce {(x,y) => x*y};
val max = xs reduce {(x,y) => if(x > y) x else y};
val min = xs reduce {(x,y) => if(x < y) x else y};

下面这个示例找出一个语句中最长的单词:

val words = "Scala is fun".split(" ");
val longestWord = words reduce {(w1,w2) => if(w1.length > w2.length) w1 else w2};

reduce和reduceLeft都是从左边的操作数开始,而reduceRight是从右边的操作数开始:

(1 to 9).reduce((v1:Int, v2:Int) => v1 + v2)
(1 to 9).reduce(_ + _)    

(1 to 9).reduceLeft(_ + _)

(1 to 9).reduceRight(_ + _)

6、sortWith

使用sortWith函数对集合进行排序:

(1 to 9).sortWith((v1:Int, v2:Int) => v1 > v2).foreach(println(_))

println("=======")
(1 to 9).sortWith(_ > _).foreach(println)

【示例】单词计数程序。

下面是一个Scala版本的单词计数程序,其中演示了集合类上的高阶函数用法。

import scala.io.Source

object WordCount {
  def main(args: Array[String]): Unit = {
    // 方式1
    //    val text = List("good good study","day day up")
    //    val words = text.flatMap(_.split(" "))
    //    val tuples = words.map((_,1))
    //    val grouped = tuples.groupBy(_._1)
    //    val sumed = grouped.mapValues(_.size)
    //    val sorted = sumed.toList.sortBy(_._2)
    //    val result = sorted.reverse
    //    result.foreach(println)

    // 方式2
    //    val text = List("good good study","day day up")
    //    text.flatMap(_.split(" "))
    //      .map((_,1))
    //      .groupBy(_._1)
    //      .mapValues(_.size)
    ////      .toList.sortBy(_._2)
    //      .toList.sortWith(_._2 < _._2)
    ////      .reverse
    //      .foreach(println)

    // 方式3:简洁写法
    val text = Source.fromFile("""word.txt""").getLines().toList
    text.flatMap(_.split(" "))
      .map((_,1))
      .groupBy(_._1)
      .mapValues(_.size)
      .toList.sortWith(_._2 < _._2)
      .foreach(println)
  }
}

《Flink原理深入与编程实战》