Scala学习笔记——reduce、fold、scan

Scala学习笔记之reducefoldscan


1. reduce化简

概念与区别:

  • Scala中,我们可以使用reduce这种二元操作对集合中的元素进行归约。
  • reduce包含reduceLeftreduceRight两种操作,前者从集合的头部开始操作,后者从集合的尾部开始操作。
  • 特别的,如果我们不指定reduceleft还是right默认情况下会使用reduceLeft执行操作。

reducereduceLeftreduceRight源码:

//reduce
def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceLeft(op)

//reduceLeft
def reduceLeft[B >: A](op: (B, A) => B): B = {
    if (isEmpty)
        throw new UnsupportedOperationException("empty.reduceLeft")

    var first = true
    var acc: B = 0.asInstanceOf[B]

    for (x <- self) {
        if (first) {
        acc = x
        first = false
        }
        else acc = op(acc, x)
    }
    acc
}

//reduceRight
def reduceRight[B >: A](op: (A, B) => B): B = {
    if (isEmpty)
      throw new UnsupportedOperationException("empty.reduceRight")

    reversed.reduceLeft[B]((x, y) => op(y, x))
}

从源码可以看出,reduce默认就是从左向右归约,然后对这两个元素进行指定的操作(指定的操作即reduce传入的参数),如果集合为空会报错empty.reduceLeft,如果只有一个元素,则返回该元素。
reduceLeft就是从左向右归约,reduceRight就是从右向左归约。

仔细观察这三个方法,可以发现reducereduceLeft还是有区别的,reduce的参数中那个函数的两个参数类型是要一致的,而reduceLeft中的参数中那个函数的两个参数类型可以不一样,但第一个参数类型必须是第二个参数类型的父类(或本类)。

reducereduceLeft运行示意图(从左往右归约):

图1

reduceRight运行示意图(从右向左归约):

图2

示例:

1. 计算给定集合的元素和:

(一般这些元素满足运算交换律的话,使用reduceLeftreduceRight结果是一样的)

  def sum_reduce(seq: Seq[Int]) = {
    seq.reduce(_ + _) // 下划线是占位符,用来表示当前获取的两个元素,两个下划线之间的是操作符,表示对两个元素进行的操作,这里是加法操作(也可以使用乘法*或者减法-等其他操作)
  }

  def sum_reduceLeft(seq: Seq[Int]) = {
    seq.reduceLeft(_ + _)
  }

  def sum_reduceRight(seq: Seq[Int]) = {
    seq.reduceRight(_ + _)
  }

  println(sum_reduce(1 to 5)) // (((1 + 2) + 3) + 4) + 5 = 15
  println(sum_reduceLeft(1 to 5)) // (((1 + 2) + 3) + 4) + 5 = 15
  println(sum_reduceRight(1 to 5)) // 1 + (2 + (3 + (4 + 5))) = 15

  // 如果是减法,则reduceLeft和reduceRight结果不一样。
  // reduceLeft:(((1 - 2) - 3) - 4) - 5 = -13
  // reduceRight:1 - (2 - (3 - (4 - 5))) = 3
2. 计算n!
  def test(n: Int): Int = {
    1 to n reduce (_ * _) // 用reduceLeft、reduceRight结果是一样的
  }

  println(test(4)) // 24
  println(test(0)) // 报错

2. fold折叠

概念与区别:

foldreduce的区别就是fold给了初始值,运算步骤和reduce还是一样的。

上面的示例2n < 1时候就会报错了,因为没有元素,所以我们可以用fold进行改进,具体代码看下面示例。

另外,foldLeftfoldRight还有专属符号代替:

foldLeft    /:
foldRight   :\
冒号所在方是需要折叠的列表方,需要折叠的列表永远不靠着\

foldfoldLeftfoldRight源码:

  //fold
  def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op)

  //foldLeft 
  def foldLeft[B](z: B)(op: (B, A) => B): B = {
    var result = z
    this foreach (x => result = op(result, x))
    result
  }

  //foldRight
  def foldRight[B](z: B)(op: (A, B) => B): B =
    reversed.foldLeft(z)((x, y) => op(y, x))

同样,观察源码,虽然foldfoldLeft运算步骤差不多,但是还是有区别的,同样是类型的问题,fold要求传入函数的那两个参数类型一致,而foldLeft的第一个参数是没有限制的,可以传入ListMap等等。

foldfoldLeft运行示意图(从左往右归约):

图3

foldRight运行示意图(从右向左归约):

图4

示例:

1. 计算n!reduce的优化):
  def test(n: Int): Int = {
    (1 to n).foldLeft(1)(_ * _)
    // 或者,注意下面的也是foldLeft,记住口诀:冒号所在方是需要折叠的列表方,需要折叠的列表永远不靠着\
    // (1 /: (1 to n)) (_ * _)
  }

  println(test(0)) // 1
  println(test(4)) // 24
2. 统计字符串中每个字符出现的频次:
  def test(str: String): Map[Char, Int] = {
    str.foldLeft(Map[Char, Int]())((m, c) => m + (c -> (m.getOrElse(c, 0) + 1)))
    // 或者
    // (Map[Char, Int]() /: str) ((m, c) => m + (c -> (m.getOrElse(c, 0) + 1)))
  }

  test("Hello World!").foreach(t => println(t._1, t._2))
    /*结果:
      (e,1)
      (!,1)
      ( ,1)
      (l,3)
      (H,1)
      (W,1)
      (r,1)
      (o,2)
      (d,1)
     */

3. scan扫描

概念与区别:

如果理解了fold的话,学习scan将会很简单,scan就是在fold的基础上,存储了fold的中间结果,如下图,即存储了圈起来的中间值。

图片5

同样是求n!,我们来看scan的结果,可以看出scan把所有中间结果都保存到了一个集合当中:

  def main(args: Array[String]): Unit = {
    test(5).foreach(println)
    /*结果:
        1
        1
        2
        6
        24
        120
    */
  }

  def test(n: Int) = {
    (1 to n).scan(1)(_ * _)
  }
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值