【大数据处理学习笔记】1.8 掌握Scala函数

本文详细介绍了Scala中函数的声明方式,包括显式和隐式声明,以及如何使用函数修饰符、返回值和参数。此外,还讨论了匿名函数、高阶函数、局部函数、神奇占位符_的功能和用法,并通过实例展示了闭包、可变参数和尾递归的概念。最后,文章提供了一道关于计算因数和的编程题,展示函数在解决实际问题中的应用。
摘要由CSDN通过智能技术生成

一、声明函数
函数其实是一段具有特定功能的代码的集合,由函数修饰符、函数名、函数参数列表、函数返回值声明与函数体组成。
(一)显式声明函数
1、声明格式
[public | private | protected] def 函数名(参数列表) : 返回值声明 = {函数体}

2、注意事项
函数通过def关键字定义
def前面可以具有修饰符,可以通过private、protected来控制其访问权限。注意默认访问权限是public
还可使用override、final等关键字修饰
函数体中return关键字往往可以省略掉,一旦省略掉,函数将会返回整个函数体中最后一行表达式的值,这也要求整个函数体的最后一行必须是正确类型的值的表达式
scala一般都可以自动推断出返回值类型,所以通常返回值类型声明可以省略,但是注意,如果因为省略了返回值类型造成歧义,则一定要写上返回值声明
如果函数体只有一行内容,则包裹函数体的大括号可以省略
如果返回值类型是Unit,则另一种写法是可以去掉返回值类型和等号,把方法体写在花括号内,而这时方法内无论返回什么,返回值都是Unit
3、案例演示
(1)加法函数
创建net.huawei.day08包,在包里创建Example01对象

package net.huawei.day08

 

import scala.io.StdIn


object Example01 {
  def add1(a: Int, b: Int): Int = {
    return a + b
  }

  def add2(a: Int, b: Int): Int = {
    a + b
  }

  def add3(a: Int, b: Int) = {
    a + b
  }

  def add4(a: Int, b: Int) = a + b

  def add5(a: Int, b: Int) {
    println(a + " + " + b + " = " + (a + b))
  }

  def main(args: Array[String]): Unit = {
    print("a = ")
    val a = StdIn.readLine().toInt
    print("b = ")
    val b = StdIn.readLine().toInt

    println(a + " + " + b + " = " + add1(a, b))
    println(a + " + " + b + " = " + add2(a, b))
    println(a + " + " + b + " = " + add3(a, b))
    println(a + " + " + b + " = " + add4(a, b))
    add5(a, b)
  }
}
运行程序,查看结果

 

(2)阶乘函数
显式声明阶乘函数并调用,在net.huawei.day08包里创建Example02对象

package net.huawei.day08

 

import scala.io.StdIn


object Example02 {
  def factorial(n: Int): Long = {
    var jc = 1
    (1 to n).foreach(i => jc = jc * i)
    jc
  }

  def main(args: Array[String]): Unit = {
    print("n = ")
    val n = StdIn.readInt()
    println(n.toString + "! = " + factorial(n))
  }
}


运行程序,查看结果

 

(二)隐式声明函数
1、声明格式
(参数列表) => {函数体}

2、注意事项
如果函数体只有一行,那么大括号可以省略
如果参数类型可以推测得知,那么参数列表中类型声明可以省略
如果函数参数列表中只有一个参数,在不会产生歧义的情况下,小括号可以省略
3、案例演示
(1)加法函数
在net.huawei.day08包里创建Example03对象

package net.huawei.day08

import scala.io.StdIn


object Example03 {
  val add1 = (a: Int, b: Int) => {a + b}
  val add2 = (a: Int, b: Int) => a + b
  val add3 = (n: Int) => n + 1

  def main(args: Array[String]): Unit = {
    print("a = ")
    val a = StdIn.readInt()
    print("b = ")
    val b = StdIn.readInt()

    println(a + " + " + b + " = " + add1(a, b))
    println(a + " + " + b + " = " + add2(a, b))
    println(a + " + " + 1 + " = " + add3(a))
  }
}


val add1 = (a: Int, b: Int) => {a + b}

功能:将函数直接量(a: Int, b: Int) => {a + b}赋给常量add1。

运行程序,查看结果


(2)三整数加法函数和阶乘函数
隐式声明三整数相加函数和阶乘函数

有些算子要用函数作为参数,我们经常会用匿名函数作为参数
(3)打印直角三角形
方法一、采用传统的双重循环实现

方法二、采用单重循环配上流间变量来实现

方法三、采用映射算子(map())和遍历算子(foreach())来实现

其中,map()算子传入的是一个隐式函数,foreach()算子传入的是一个显式函数(系统提供的函数)
(4)计算1 + 2 + 3 + …… + 100的值
不采用循环,而采用归约算子(reduce())算子来完成,归约算子传入一个匿名函数_ + _或(x, y) => x + y

二、Scala函数种类
(一)成员方法
1、基本概念
函数被使用在类的内部,作为类的一个成员,称为类的成员方法。
2、案例演示
任务:显示文件中长度超过15的行
在项目根目录里创建文本文件text.txt


在net.huawei.day08包里创建Example04对象


package net.huawei.day08
object Example04 {
  def filter(line: String, len: Int): Boolean = {
    line.length > len
  }

  def getLinesFromFile(path: String, len: Int) = {
    val lines = Source.fromFile(path).getLines()
    for (line <- lines if filter(line, len)) yield line
  }

  def main(args: Array[String]): Unit = {
    val lines = getLinesFromFile("text.txt", 15)
    for (line <- lines) println(line + " " + line.length)
  }
}


运行程序,查看结果

课堂练习:输出文件中包含Scala的行
文件中有5行都包含了Scala

(二)局部函数
1、基本概念
函数内嵌的函数称为局部函数,这样的函数,只能在外部函数内部使用,外界无法访问。注意,Java是不允许函数嵌套的,但是Scala是允许的。
2、案例演示
任务:采用局部函数显示文件中长度超过15的行
在net.huawei.day08包里创建Example05对象

package net.huawei.day08

import scala.io.Source


object Example05 {
  def getLinesFromFile(path: String, len: Int) = {
    def filter(line: String, len: Int): Boolean = {
      line.length > len
    }

    val lines = Source.fromFile(path).getLines()
    for (line <- lines if filter(line, len)) yield line
  }

  def main(args: Array[String]): Unit = {
    val lines = getLinesFromFile("text.txt", 15)
    for (line <- lines) println(line + " " + line.length)
  }
}


函数filter就是函数getLinesFromFile的内嵌函数或本地函数,只能在getLinesFromFile函数内部使用,但是在main方法里是无法访问到filter函数的。

运行程序,查看结果

(三)匿名函数
1、基本概念
函数在Scala中是头等公民,这体现在函数可以任意赋值给变量或常量,甚至可当作方法的实参或当作方法的返回值。在Java中只有变量或常量才能这么去使用。将函数直接量赋值给一个常量或变量,得到就是一个函数值,在需要时可以通过这个函数值来调用方法本身。
2、案例演示
任务:将函数赋值给常量
在net.huawei.day08包里创建Example06对象

package net.huawei.day08


object Example06 {
  def sum(a: Int, b: Int): Int = a + b

  def main(args: Array[String]): Unit = {
    // 直接调用显式函数
    val r = sum(100, 150)
    println("r = " + r)

    // 将函数赋值给常量
    val sum1 = sum(_, _)
    val r1 = sum1(100, 150)
    println("r1 = " + r1)

    // 将函数固定一个参数后赋值给常量
    val sum2 = sum(_: Int, 100)
    val r2 = sum2(150)
    println("r2 = " + r2)

    // 将隐式函数赋值给常量
    val sum3 = (a: Int, b: Int) => a + b
    val r3 = sum3(100, 150)
    println("r3 = " + r3)
  }
}


运行程序,查看结果

(四)高阶函数
1、基本概念
函数可以作为另一个函数的参数被传递或作为另一个函数的返回值。
2、案例演示
任务1、演示函数作为函数的参数
在net.huawei.day08包里创建Example07对象

package net.huawei.day08


object Example07 {
  def printStr: (String) => Unit = {
    x => println(x)
  }

  def main(args: Array[String]): Unit = {
    val cities = List("北京", "上海", "深圳", "泸州")
    cities.foreach(printStr)
  }
}


匿名函数x => println(x)作为函数printStr的返回值。
函数printStr作为列表对象cities的foreach方法的实参。
运行程序,查看结果

任务2、演示函数作为函数的返回值
在net.huawei.day08包里创建Example08对象

package net.huawei.day08

import scala.io.StdIn


object Example08 {
  def findRecipe(name: String): (String) => (String) = {
    if (name == "鸡肉") {
      (food: String) => {
        println("把" + food + "切丁,放在锅里烧,出锅")
        "美味的宫保鸡丁"
      }
    } else if (name == "鸭子") {
      (food: String) => {
        println("把" + food + "洗干净,放在火上好,烤熟")
        "美味的北京烤鸭"
      }
    } else {
      (food: String) => {
        println("把" + food + "洗净,放在锅里烧,烧熟")
        "美味的食物"
      }
    }
  }

  def cook(food: String): String = {
    val recipe = findRecipe(food)
    recipe(food);
  }

  def main(args: Array[String]): Unit = {
    print("食材:")
    val food = StdIn.readLine()
    println(cook(food))
  }
}


运行程序,查看结果

三、神奇占位符
(一)基本概念
Scala中的下划线_是一个神奇的占位符,可以用它当作一个或多个参数来使用。可以使用下划线_占位符的前提要求是每个参数在函数直接量中仅出现一次,满足条件的情况下,可以去掉参数的说明,直接在函数体中使用下划线即可。使用下划线时,如果类型可以自动推断出,则不用声明类型,如果无法自动推断类型,则在下划线后自己来显式声明类型即可。
(二)案例演示
任务1、演示过滤集合
在net.huawei.day08包里创建Example09对象

package net.huawei.day08


object Example09 {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    println(list)

    println("奇数子列表:" + list.filter((num: Int) => {
      num % 2 == 1
    }))

    println("偶数子列表:" + list.filter(_ % 2 == 0))
  }
}


运行程序,查看结果

任务2、演示处理集合
在net.huawei.day08包里创建Example10对象

package net.huawei.day08


object Example10 {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4, 5, 6, 7)

    println(list)

    list.foreach((x: Int) => {
      print((x * 10) + "\t")
    })
    println()

    println(list.map((x: Int) => {
      x * 10
    }))

    println(list.map(_ * 10))
  }
}


运行程序,查看结果

课堂练习

任务3、演示参数占位符
在net.huawei.day08包里创建Example11对象
object Example11 {
  def main(args: Array[String]): Unit = {
    def sum(a: Int, b: Int, c: Int): Int = a + b + c

    // 直接调用原函数
    println("sum = " + sum(3, 4, 5))

    // 全部参数使用占位符
    val sum1 = sum(_, _, _)
    println("sum1 = " + sum1(3, 4, 5))
    var sum2 = sum _
    println("sum2 = " + sum2(3, 4, 5))

    // 部分参数使用占位符
    val sum3 = sum(_, 4, _)
    println("sum3 = " + sum3(3, 5))
  }
}


运行程序,查看结果

课堂练习

四、闭包
(一)基本概念
如果在函数定义时,如果用到了上下文中的变量,则函数的具体执行将会和该变量的值具有了相关性,即这个函数包含了外部该变量的引用,这个过程称之为函数的闭包。这种情况下,变量值的变化将会影响函数的执行,同样函数执行的过程中改变了变量的值,也会影响其他位置对变量的使用。甚至在一些极端情况下,变量所在的环境已经被释放,但是由于函数中包含对它的引用,变量依然会存在,阻止了对象的释放,造成内存泄露的问题。所以闭包不是一项技术,而是一种现象。
(二)案例演示
任务:演示闭包
在net.huawei.day08包里创建Example12对象

package net.huawei.day08


object Example12 {
  def main(args: Array[String]): Unit = {
    var x = 100

    def sum(n: Int): Int = {
      var sum = 0
      var flag = true
      for (i <- 1 to n if flag) {
        sum = sum + i
        if (x > 50) {
          flag = false
        }
      }
      x = 0
      sum
    }

    println("x = " + x)
    println("sum = " + sum(100))
    println("x = " + x)

    x = 25
    println("x = " + x)
    println("sum = " + sum(100))
    println("x = " + x)
  }
}


运行程序,查看结果

五、可变参数
(一)基本概念
在Scala中,可以指明函数的最后一个参数是重复的。从而允许客户向函数传入可变参数的列表。想要标注一个重复参数,可以在参数的类型之后放一个星号。
def echo(args :String *) = for (arg <- args) println(arg)
1
调用:echo("aaa","bbb","ccc")或echo("泸州","成都","北京", "上海")
在函数内部,重复参数(可变参数)的类型是声明参数类型的数组
(二)案例演示
任务1、演示可变参数
在net.huawei.day08包里创建Example13对象

package net.huawei.day08


object Example13 {
  def sum(nums: Int*): Int = {
    var sum = 0
    for (num <- nums) {
      sum = sum + num
    }
    sum
  }

  def main(args: Array[String]): Unit = {
    println(sum(1))
    println(sum(1, 2))
    println(sum(1, 2, 3))
    println(sum(1, 2, 3, 4))
    println(sum(1, 2, 3, 4, 5))
    println(sum(1, 2, 3, 4, 5, 6))
    println(sum(1, 2, 3, 4, 5, 6, 7))
  }
}


运行程序,查看结果

任务2、定义任意多个数据求和的函数
当方法需要多个相同类型的参数时,可以指定最后一个参数为可变长度的参数列表,只需要在最后一个参数的类型之后加入一个星号即可。
在net.huawei.day08包里创建Example14对象

package net.huawei.day08


object Example14 {
  def addData(str: String, nums: Int*): String = {
    var sum = 0
    for (num <- nums) sum = sum + num
    str + ": " + sum
  }

  def main(args: Array[String]): Unit = {
    println(addData("泸州", 5, 6, 9))
    println(addData("南京", 6, 3, 9, 7, 5))
  }
}


运行程序,查看结果


在方法内部,重复参数的类型实际上是一个数组。因此,上述方法中的参数nums的类型实际上是一个Int类型的数组,即Array[Int]。但是如果直接向方法addData()传入一个Int类型的数组,编译器反而会报错 - 类型不匹配


此时需要在数组参数后添加一个:和一个_*符号,这样告诉编译器把数组arr的每个元素分别当作参数,而不是将数组当作单一的参数传入。


虽然结果跟先前一样,但是有个警告,从Scala2.13.0开始,这种处理数组的方式已被废弃掉了,建议使用ArraySeq.unsafeWrapArray或toIndexedSeq来处理


六、尾递归
(一)基本概念
Scala中为了避免使用while循环需要用递归。如果在递归时,保证函数体的最后一行为调用自己的代码,则称这样的递归为尾递归,Scala会优化它来大大提高其性能。
(二)案例演示
任务:计算1 + 3 + 5…,总和刚好超过50就返回
在net.huawei.day08包里创建Example15对象

package net.huawei.day08


object Example15 {
  def main(args: Array[String]): Unit = {
    def fx(x: Int, sum: Int): Int = {
      if (sum > 50) sum
      else if (x >= 100) sum
      else if (x % 2 == 0) {
        print((x + 1) + "\t")
        fx(x + 1, sum)
      } else {
        fx(x + 1, sum + x)
      }
    }
    println("\nsum = " + fx(0, 0))
  }
}


运行程序,查看结果

七、课后练习 - 因数和(蓝桥杯竞赛题)
(一)提出任务


(二)完成任务
1、编写程序,实现功能
在net.huawei.day08包里创建Example16对象

package net.huawei.day08

import scala.io.StdIn


object Example16 {
  // 获取因子的函数
  def getFactors(n: Int) = {
    for (i <- 1 to n if n % i == 0) yield i
  }

  // 定义f函数
  def f(n: Int): Long = {
    val factors = getFactors(n)
    var sum: Long = 0
    for (factor <- factors) sum = sum + factor * factor
    sum
  }

  // 定义g函数
  def g(n: Int): Long = {
    var sum: Long = 0
    for (i <- 1 to n) sum = sum + f(i)
    sum
  }

  def main(args: Array[String]): Unit = {
    print("n = ")
    val n = StdIn.readInt()

    val start_time = System.currentTimeMillis()
    val r = g(n) % (Math.pow(10, 9).toInt + 7)
    val end_time = System.currentTimeMillis()
    val time = (end_time - start_time) / 1000.0
    println("余数:" + r)
    println("耗时:" + time + "秒")
  }
}


2、运行程序,查看结果


3、优化算法
在net.huawei.day08包里创建Example16_对象

package net.huawei.day08

import scala.io.StdIn


object Example16_ {
  // 定义f函数
  def f(n: Int): Long = {
    var sum: Long = 0
    for (i <- 1 to Math.sqrt(n).toInt) {
      if (n % i == 0) {
        if (n != i * i)
          sum = sum + i * i + (n / i) * (n / i)
        else
          sum = sum + i * i
      }
    }
    sum
  }

  // 定义g函数
  def g(n: Int): Long = {
    var sum: Long = 0
    for (i <- 1 to n) sum = sum + f(i)
    sum
  }

  def main(args: Array[String]): Unit = {
    print("n = ")
    val n = StdIn.readInt()

    val start_time = System.currentTimeMillis()
    val r = g(n) % (Math.pow(10, 9).toInt + 7)
    val end_time = System.currentTimeMillis()
    val time = (end_time - start_time) / 1000.0
    println("余数:" + r)
    println("耗时:" + time + "秒")
  }
}


4、运行程序,查看结果


50.66 ÷ 0.103 ≈ 491 50.66 \div 0.103\approx49150.66÷0.103≈491,两种算法的速度相差接近500倍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值