1.Scala简介和安装
2.Scala语法介绍
3.Scala的函数
4.Scala中的集合类型
-------------------------------------------------------------------------------------------------------------
scala函数通过def关键字定义,def前面可以具有修饰符,可以通过private、protected来控制其访问权限。
注意:没有public,不写默认就是public的。此外也可跟上override,final等关键字修饰。
[private/protected] def 函数名(参数列表):返回值声明 = {函数体}
1)函数体中return关键字往往可以省略掉,一旦省略掉,函数将会返回整个函数体中最后一行表达式的值,这也要求整个函数体的最后一行必须是正确类型的值的表达式。
2)大部分时候scala都可以通过=符号来自动推断出返回值的类型,所以通常返回值类型声明可以省略。
但是注意:如果因为省略了返回值类型造成歧义,则一定要写上返回值声明。
3)如果函数体只有一行内容,则包裹函数体的大括号可以省略。
4)如果返回值类型是UNIT,则另一种写法是可以去掉返回值类型和等号,把方法体写在花括号内,而这时方法内无论返回什么,返回值都是UNIT。相当于Java中的void。
示例:
//方法的返回值为空
private def f1(){}
protected def f2():String={"hello"}
def f3()={"hello"}
//如果函数体只一行内容,可以省了花括号
def f4()="hello"
//定义方法参数类型,返回值类型,及返回值
def f5(a:Int,b:Int)={a+b}
可以为函数的参数设置默认值。
示例:
//默认参数的使用
def f8(a:String,b:String="[",c:String="]")={
b+a+c
}
占位符:占位符指的是scala中的下划线_ ,可以用它当作一个或多个参数来使用。
使用_占位符的前提要求:每个参数在函数仅出现一次。
使用下划线时,如果类型可以自动推断出,则不用声明类型。如果无法自动推断类型,则在下划线后自己来显示声明类型即可。
示例:
//要求通过reduceLeft函数计算阶乘结果
val a2=Array(1,2,3,4)
a2.reduceLeft{(a:Int,b:Int)=>{a*b}}
a2.reduceLeft{_*_}
Scala中的函数分为成员函数、本地函数(内嵌在函数内的函数)、函数值(匿名函数)、高阶函数。
成员函数:函数被使用在类的内部,作为类的一份子,称为类的成员函数。
示例:
class Person {
//eat方法是Person的成员方法
def eat() {
println("eat")
}
}
本地函数:函数内嵌的函数称为本地函数,这样的函数外界无法访问。
示例:
class Person {
//eat方法是Person的成员方法
def eat() {
println("eat")
//本地函数:内嵌在函数内的函数。对象不能直接调用本地函数。
def cook() {
println("cook")
}
}
}
函数值(匿名函数):
1.匿名函数没有函数名。
2.匿名函数的作用是配合高阶函数来使用的,匿名函数可以作为函数的参数进行传递。
示例:
(a:Int,b:Int)=>{a+b}
(a:Int,b:Int)=>a+b
val a1=Array(1,2,3,4)
//a=1 b=2 a+b=3
//a=3 b=3 a+b=6
//a=6 b=4 a+b=10
a1.reduceLeft{(a:Int,b:Int)=>a+b}
a1.reduceLeft{(a,b)=>a+b}
a1.foreach{(x:Int)=>{println(x)}}
a1.foreach{x=>println(x)}
高阶函数:函数可以作为方法的参数进行传递和调用。
示例:
//定义一个高阶函数,可以将函数当作参数传递
def f2(a:Int,b:Int,f:(Int,Int)=>Int)={
f(a,b)
}
f2(2,3,(a:Int,b:Int)=>{a+b})
f2(2,3,(a,b)=>a+b)
f2(2,3,(a,b)=>a*b)
f2(2,3,_*_)
//定义一个高阶函数,要求:传入一个String类型的参数,以及一个处理String类型的匿名函数
//此高阶函数的返回值就是匿名函数的返回值
def f3(a:String,f:(String)=>Array[String])={
f(a)
}
//注意:匿名函数一定要和指定返回值类型匹配
f3("hello,world",(a:String)=>{a.split(",")})
f3("hello,world",a=>a.split(","))
f3("hello,world",a=>a split ",")
f3("hello,world",_.split(","))
//要求通过reduceLeft函数计算阶乘结果
val a2=Array(1,2,3,4)
a2.reduceLeft{(a:Int,b:Int)=>{a*b}}
想要实现递归方法,有两个要素可以快速的实现。
要素1:找出递归结束的条件。
要素2:找出函数的映射关系。
scala中,如果在递归时,保证函数体的最后一行为递归调用,则称这样的递归为尾递归。scala会针对尾递归做优化处理,所以建议在写递归时写成尾递归形式。
范例:
斐波那契数列:1 1 2 3 5 8 13 ?
要素1:找出递归结束的条件:f(n)=f(n-1)+f(n-2)
要素2:找出函数的映射关系:f(0)=1;f(1)=1
所以代码就可以为:
def f1(n:Int):Int={
if(n==0)return 1
if(n==1)return 1
else f1(n-1)+f1(n-2)
}
示例:
//从1开始做加法,只加偶数,当加和累计超过50时,结束递归
//示意:2+4+6……
def f1(num: Int, sum: Int): Int = {
if (sum > 50) return sum;
if (num % 2 == 0) { f1(num + 1, sum + num) }
else { f1(num + 1, sum) }
}
//2 3 4 9 8 27 16 ?
//f(n)= f(n-2)*2
//f(n)=f(n-2)*3
//f(0)=2;f(1)=3
def f2(n: Int): Int = {
if (n == 0) return 2
if (n == 1) return 3
if (n % 2 == 0) return f2(n - 2) * 2
else f2(n - 2) * 3
}
//2 3 4 9 16 81 ?
// n的取值:f(0) f(1) f(2) f(3) f(4) f(5)
//当n为偶数时,f(n)=f(n-2)*f(n-2)
//当n为奇数是,f(n)=f(n-2)*f(n-2)
//fn=f(n-2)*f(n-2)
def f3(n: Int): Int = {
if (n == 0) return 2
if (n == 1) return 3
else f3(n - 2) * f3(n - 2)
}
//求 1~n的数字之和
//1 3 6 10 15
//f(0) f(1) f(2) f(3) f(4)
//f(n)=f(n-1)+n+1
def f5(n: Int): Int = {
if (n == 0) return 1
else f5(n - 1) + n + 1
}
//给定一个初始值n,并设定sum的初始值为0,当求和sum>12时结束递归
//0 1 3 6 10 15
//f(0,0)——n=0,sum=0
//f(1,1)——n=1,sum=1
//f(2,3)——n=2,sum=3
//f(3,6)——n=3,sum=6
//f(n+1,sum+n)
def f6(n: Int, sum: Int): Int = {
if (sum > 12) return sum
else f6(n + 1, sum + n)
}
//给定一个scope范围,计算0~scope范围的整数之和,
//当和>12或者达到scope边界时,结束递归
def f7(n: Int, sum: Int, scope: Int): Int = {
if (sum > 12) return sum
if (n - 1 == scope) return sum
else f7(n + 1, sum + n, scope)
}
在scala中,可以指明函数的最后一个参数是重复的。从而允许客户向函数传入可变参数的列表。
想要标注一个重复参数,可以在参数的类型之后放一个星号。重复参数(可变参数)的类型是声明参数类型的数组。
示例:
//定义变长参数
def f1(num:Int*)={}
def f2(num:Int*)={
for(i<-num)println(i)
}
f2(1,2,3,4,5)
def f21(a:Int,str:String*)={}
scala的柯里化的作用是结合scala的高阶函数,从而允许用户自建立控制结构。
柯里化(Currying)技术 Christopher Strachey 以逻辑学家 Haskell Curry 命名的(尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的)。它是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
//首先我们定义一个函数:
def f1(a:Int,b:Int):Int={a+b}
//现在我们把这个函数变一下形:
def f2(a:Int)(b:Int)={a+b}
//那么我们应用的时候,应该是这样用:f2(2)(3),最后结果都一样是5,这种方式(过程)就叫柯里化。
val r1=f2(2)(3)
//柯里化实质上会演变成这样一个函数:
//接收一个参数a,返回一个匿名函数,
//该匿名函数又接收一个参数b,函数体为a+b
def f3(a:Int)=(b:Int)=>a+b
val f4=f3(2)
//请思考 f4(3)的值是多少?答案是:5
f4(3)
//柯里化
def f3(a:Int,b:Int)={a+b}
def f31(a:Int)(b:Int)={a+b}
f3(2,3)
f31(2)(3)
def f4(a:Int,b:Int,c:Int)={a+b+c}
def f41(a:Int)(b:Int)(c:Int)={a+b+c}
def f42(a:Int,b:Int)(c:Int)={a+b+c}
def f43(a:Int)(b:Int,c:Int)={a+b+c}
def f5(a:Int,b:Int,f:(Int,Int)=>Int)={f(a,b)}
def f51(a:Int)(b:Int,f:(Int,Int)=>Int)={f(a,b)}
def f52(a:Int,b:Int)(f:(Int,Int)=>Int)={f(a,b)}
def f53(a:Int)(b:Int)(f:(Int,Int)=>Int)={f(a,b)}
f5(2,3,_+_)
f52(2,3)(_+_)
柯里化技术在提高适用性、延迟执行或者固定易变因素等方面有着重要重要的作用,加上scala语言本身就是推崇简洁编码,使得同样功能的函数在定义与转换的时候会更加灵活多样。另外在Spark的源码中有大量运用scala柯里化技术的情况,需要掌握好该技术才能看得懂相关的源代码。
在scala柯里化中,闭包也发挥着重要的作用。所谓的闭包就是变量出了函数的定义域外在其他代码块还能其作用,这样的情况称之为闭包。就上述讨论的案例而言,如果没有闭包作用,那么转换后函数其实返回的匿名函数是无法在与第一个参数a相关结合的,自然也就无法保证其所实现的功能是跟原来一致的。
适用于所有集合。
拆分,将一个集合按一个布尔值分成两个集合,满足条件的一个集合,其他另外一个集合。
按照指定原则拆分,返回的是一个二元Tuple。
val l1=List(1,2,3,4,5,6)
l1.partition{x=> x%2==0}
//> res0: (List[Int], List[Int]) = (List(2, 4, 6),List(1, 3, 5))
映射,把一个集合转换为另外一个集合。集合中元素个数不变。
改变集合类型中,元素的形式或数据,返回一个新的集合。此方法不会改变集合中元素的个数,只是改变了数值和形式。
val l2=List("hadoop","world","hello","hello")
l2.map(x=>(x,1))
//> res1: List[(String, Int)] = List((hadoop,1), (world,1), (hello,1), (hello,1))
val l3=List("hello word","hello hadoop")
l3.map{x=>x.split(" ")}
//> res2: List[Array[String]] = List(Array(hello, word), Array(hello, hadoop))
扁平化map,会取出集合的元素。注意,此方法会改变集合中的元素个数。
一般引用场景:读取文件后,处理文件,将每行数据按指定分割符切分。
l3.flatMap{x=>x.split(" ")}
//> res3: List[String] = List(hello, word, hello, hadoop)
//要求:操作l3将其变成(word,1)的形式
l3.flatMap{x=>x.split(" ")}.map(x=>(x,1))
//> res4: List[(String, Int)] = List((hello,1), (word,1), (hello,1), (hadoop,1))
过滤。
val l4=List(1,2,3,4,5)
l4.filter(x=>x>3)
//> res5: List[Int] = List(4, 5)
归约,reduce的过程:将上次的运行结果和下一个值进行运算。
函数接收两个参数 => 返回一个值。
等价于reduceLeft。
l4.reduce{_+_}
//> res6: Int = 15
按指定规则做聚合,最后将结果返回到一个map映射里。
按照指定原则做分组,返回的是Map类型。Map的key是分组键,value是对应的List集合。
val l5=List(("bj",1),("sh",2),("bj",3),("sh",4),("sz",5))
l5.groupBy{x=>x._1}
//> res7: scala.collection.immutable.Map[String,List[(String, Int)]] = Map(bj ->List((bj,1), (bj,3)), sz -> List((sz,5)), sh -> List((sh,2), (sh,4)))
l5.groupBy{case(addr,count)=>addr}
//> res8: scala.collection.immutable.Map[String,List[(String, Int)]] = Map(bj ->List((bj,1), (bj,3)), sz -> List((sz,5)), sh -> List((sh,2), (sh,4)))
此方法是针对的Map类型的值做操作,此方法只适用于Map类型。
val m1=Map("rose"->23,"tom"->25,"jary"->30)
m1.mapValues {x=>x+10}
//> res9: scala.collection.immutable.Map[String,Int] = Map(rose -> 33, tom -> 35, jary -> 40)
排序。
val l6=List((2,"aaa"),(1,"bbb"),(4,"ddd"),(3,"ccc"))
l6.sortBy{x=>x._1}
//> res10: List[(Int, String)] = List((1,bbb), (2,aaa), (3,ccc), (4,ddd))
l6.sortBy{x=>x._2}
//> res11: List[(Int, String)] = List((2,aaa), (1,bbb), (3,ccc), (4,ddd))
l6.sortBy{case(num,str)=>num}
//> res12: List[(Int, String)] = List((1,bbb), (2,aaa), (3,ccc), (4,ddd))
统计出每个单词出现的频次。最后的结果形式:(hello,5)(hadoop,2)……
val l7=List("hello hadoop","hello world","hello spark","hello hadoop","hello hive")
//方法一:
l7.flatMap{line=>line.split(" ")}.groupBy{word=>word}.mapValues { list => list.size }.foreach{println(_)}
//方法二:
l7.flatMap { line => line.split(" ") }.map { word => (word,1) }.groupBy(x=>x._1).mapValues{list=>list.size}.foreach{println(_)}
//方法三:
l7.flatMap { line => line.split(" ") }.map { word => (word,1) }.groupBy(x=>x._1).mapValues{list=>list.map(x=>x._2).reduce(_+_)}.foreach{println(_)}
//简化写法:
l7.flatMap{_.split(" ")}.map{(_,1)}.groupBy(_._1).mapValues{_.map(_._2).reduce(_+_)}.foreach{println(_)}
//要求统计单词频次,然后返回频次最高的前2项结果
l7.flatMap { line => line.split(" ") }.map { word => (word,1) }.groupBy(x=>x._1).mapValues{list=>list.map(x=>x._2).reduce(_+_)}.toList.sortBy(x=> -x._2).take(2)
//> res13: List[(String, Int)] = List((hello,5), (hadoop,2))
上一篇:Scala语法介绍
下一篇:Scala中的集合类型