偏函數
先看一個需求,給你一個集合val list = List(1, 2, 3, 4, “abc”) ,請完成如下要求:
将集合list中的所有數字+1,并傳回一個新的集合
要求忽略掉 非數字 的元素,即傳回的 新的集合 形式為 (2, 3, 4, 5)
用之前學過的知識,這個題有兩個解法,第一個是利用filter和map函數
- filter篩選出Int的元素,注意此時傳回的Any類型
- 把filter之後的元素再轉換成Int類型
- map對篩選出來的元素計算+1
val list = List(1, 2, 3, 4, "abc")
def isInt(x:Any)={
// 傳回true false
x.isInstanceOf[Int]
}
def asInt(x:Any)={
// 傳回true false
x.asInstanceOf[Int]
}
def andOne(x:Int)={
x+1
}
// 當然asInt和andOne可以寫在一起,這樣省點事
list.filter(isInt).map(asInt).map(andOne)
list: List[Any] = List(1, 2, 3, 4, abc)
isInt: (x: Any)Boolean
asInt: (x: Any)Int
andOne: (x: Int)Int
res3: List[Int] = List(2, 3, 4, 5)
也可以用模式比對寫
// 此時如果x非Int,傳回(),是以還得再filter
def matchF(x:Any)={
x match{
case x:Int => x+1
case _ =>
}
}
println(list.map(matchF))
println(list.map(matchF).filter(isInt))
List(2, 3, 4, 5, ())
List(2, 3, 4, 5)
matchF: (x: Any)AnyVal
上述兩種方法都要進行filter和map兩步操作,偏函數可以一步到位~
- 在對符合某個條件,而不是所有情況進行邏輯操作時,使用偏函數是一個不錯的選擇
- 将包在大括号内的一組case語句封裝為函數,我們稱之為偏函數,它隻對會作用于指定類型的參數或指定範圍值的參數實施計算,超出範圍的值會忽略(未必會忽略,這取決于你打算怎樣處理)
- 偏函數在Scala中是一個特質
直接上代碼吧~
val list = List(1, 2, 3, 4, "abc")
//定義一個偏函數
//1. PartialFunction[Any,Int] 表示偏函數接收的參數類型是Any,傳回類型是Int
//2. isDefinedAt(x: Any) 如果傳回true ,就會去調用 apply 建構對象執行個體,如果是false,過濾
//3. apply 構造器 ,對傳入的值 + 1,并傳回(新的集合)
//4. isDefinedAt和apply方法是固定的,不可以随便定義方法名稱
val addOne3= new PartialFunction[Any, Int] {
def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
def apply(any: Any) = any.asInstanceOf[Int] + 1
}
val list3 = list.collect(addOne3)
println("list3=" + list3)
list3=List(2, 3, 4, 5)
list: List[Any] = List(1, 2, 3, 4, abc)
addOne3: PartialFunction[Any,Int] = <function1>
list3: List[Int] = List(2, 3, 4, 5)
- 使用建構特質的實作類(使用的方式是PartialFunction的匿名子類)
- PartialFunction 是個特質(看源碼)
- 建構偏函數時,參數形式[Any, Int]是泛型,第一個表示參數類型,第二個表示傳回參數
- 當使用偏函數時,會周遊集合的所有元素,編譯器執行流程時先執行isDefinedAt()如果為true ,就會執行 apply, 建構一個新的Int對象傳回
- 執行isDefinedAt() 為false 就過濾掉這個元素,即不建構新的Int對象
- map函數不支援偏函數,因為map底層的機制就是所有循環周遊,無法過濾處理原來集合的元素
- collect函數支援偏函數
上面偏函數也有其他簡化形式,後面遇到我應該也不會寫了吧。。。直接把代碼Copy過來~
def f2: PartialFunction[Any, Int] = {
case i: Int => i + 1 // case語句可以自動轉換為偏函數
}
List(1, 2, 3, 4,"ABC").collect(f2)
f2: PartialFunction[Any,Int]
res10: List[Int] = List(2, 3, 4, 5)
List(1, 2, 3, 4,"ABC").collect{ case i: Int => i + 1
case i:String=>i*2}
res13: List[Any] = List(2, 3, 4, 5, ABCABC)
作為參數的函數
這個其實在map中用過,簡單看個例子:
//說明
def plus(x: Int) = 3 + x
//說明
println(Array(1, 2, 3, 4).map(plus(_)).mkString(","))
println(Array(1, 2, 3, 4).map(plus).mkString(","))
4,5,6,7
4,5,6,7
plus: (x: Int)Int
-
map(plus(_)) 中的plus(_) 就是将plus這個函數當做一個參數傳給了map,_這裡代表從集合中周遊出來的一個元素
-
plus(_) 這裡也可以寫成 plus 表示對 Array(1,2,3,4) 周遊,将每次周遊的元素傳給plus的 x!
匿名函數
沒有名字的函數就是匿名函數,可以通過函數表達式來設定匿名函數!
val triple = (x: Double) => 3 * x
println(triple(3))
println(triple.getClass)
9.0
class $line39.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anonfun$1
triple: Double => Double = <function1>
- (x: Double) => 3 * x 就是匿名函數
- (x: Double) 是形參清單, => 是規定文法表示後面是函數體, 3 * x 就是函數體,如果有多行,可以 {} 換行寫
- triple 是指向匿名函數的變量
再看個例子:
val f1 = (n1: Int, n2: Int ) => {
println("biubiubiu")
n1 + n2
}
println(f1.getClass)
println("f1:" + f1)
println(f1(10, 30))
class $line41.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anonfun$1
f1:<function2>
biubiubiu
40
f1: (Int, Int) => Int = <function2>
高階函數
能夠接受函數作為參數的函數,叫做高階函數 (higher-order function)。可使應用程式更加健壯
//test 就是一個高階函數,它可以接收f: Double => Double ,f的參數和傳回值都為Double
def test(f: Double => Double, n1: Double) = {
f(n1)
}
//sum 是接收一個Double,傳回一個Double
def sum(d: Double): Double = {
d + d
}
val res = test(sum, 6.0)
println("res=" + res)
res=12.0
test: (f: Double => Double, n1: Double)Double
sum: (d: Double)Double
res: Double = 12.0
上面的例子是把函數作為參數,我們也可以定義一個函數,使之結果傳回為一個函數
// 相當于把x固定了傳回的新函數中,y是參數
def minusxy(x: Int) = {
(y: Int) => x-y //匿名函數
}
//這一步相當于先傳回個函數(3-y),然後y=5
val result3 = minusxy(3)(5)
// println(result3)
minusxy: (x: Int)Int => Int
result3: Int = -2
參數類型推斷
據說這裡的應用很多,我暫時沒遇到過。參數推斷省去類型資訊(在某些情況下[需要有應用場景],參數類型是可以推斷出來的,如list=(1,2,3) list.map(),map中函數參數類型是可以推斷的,因為list中元素都是Int),同時也可以進行相應的簡寫。
參數類型推斷寫法說明:
- 參數類型是可以推斷時,可以省略參數類型
- 當傳入的函數,隻有單個參數時,可以省去括号
- 如果變量隻在=>右邊隻出現一次,可以用_來代替
val list = List(1, 2, 3, 4)
//匿名函數寫法
println(list.map((x:Int)=>x + 1)) //(2,3,4,5)
//list中都是Int,可以簡寫
println(list.map((x)=>x + 1)) //(2,3,4,5)
println(list.map(x=>x + 1)) //(2,3,4,5)
//x在右邊隻出現一次,直接_簡寫
println(list.map( _ + 1)) //(2,3,4,5)
// reduce好像預設是reduceLeft?從左邊開始計算,把元素一次傳進去
println(list.reduce((n1:Int ,n2:Int) => n1 + n2)) //10
println(list.reduce((n1 ,n2) => n1 + n2)) //10
println(list.reduce( _ + _)) //10
List(2, 3, 4, 5)
List(2, 3, 4, 5)
List(2, 3, 4, 5)
List(2, 3, 4, 5)
10
10
10
list: List[Int] = List(1, 2, 3, 4)
閉包
前面高階函數中,傳回值為函數的case已經涉及到了閉包。閉包就是一個函數和與其相關的引用環境組合的一個整體(實體)。看個例子:
//1.用等價了解方式改寫 2.對象屬性了解
def minusxy(x: Int) = (y: Int) => x - y
//f函數就是閉包.這玩意感覺就是友善後面使用時,不需要重複把x=20傳進去
val f = minusxy(20)
println("f(1)=" + f(1)) // 19
println("f(2)=" + f(2)) // 18
f(1)=19
f(2)=18
minusxy: (x: Int)Int => Int
f: Int => Int = <function1>
再來看個實際case:
- 編寫一個函數 makeSuffix(suffix: String) 可以接收一個檔案字尾名(比如.jpg),并傳回一個閉包
- 調用閉包,可以傳入一個檔案名,如果該檔案名沒有指定的字尾(比如.jpg) ,則傳回 檔案名.jpg , 如果已經有.jpg字尾,則傳回原檔案名
- 要求使用閉包的方式完成
- 字尾名可以使用String.endsWith(xx),這個函數類似截取最後length(xx)個字元串和xx比較,傳回true false
def makeSuffix(suffix: String) ={
(fileName:String)=> if( fileName.endsWith(suffix)) fileName else fileName+suffix
}
val f = makeSuffix(".jpg")
println(f("dog.jpg")) // dog.jpg
println(f("cat")) // cat.jpg
dog.jpg
cat.jpg
makeSuffix: (suffix: String)String => String
f: String => String = <function1>
函數柯裡化
韓老師講的一個例子很懵逼,有點繞,但是如果隻看函數柯裡化的形式,似乎又不是很複雜。
- 函數程式設計中,接受多個參數的函數都可以轉化為接受單個參數的函數,這個轉化過程就叫柯裡化
- 柯裡化就是證明了函數隻需要一個參數而已。其實我們剛才的學習過程中,已經涉及到了柯裡化操作
上面閉包和高階函數的case就用到所謂的函數柯裡化
def mulCurry1(x: Int,y:Int) = x * y
def mulCurry2(x: Int)(y:Int) = x * y
println(mulCurry1(10,8))
println(mulCurry2(10)(8))
80
80
mulCurry1: (x: Int, y: Int)Int
mulCurry2: (x: Int)(y: Int)Int
控制抽象
控制抽象學着更是蒙蔽。。。先看定義:
控制抽象是函數且滿足如下條件
- 參數是函數
- 函數參數沒有輸入值也沒有傳回值
var x = 10
def until(condition: => Boolean)(block: => Unit): Unit = {
//類似while循環,遞歸
if (!condition) {
block
until(condition)(block)
}
// println("x=" + x)
// println(condition)
// block
// println("x=" + x)
}
// x==0是condition,後面的代碼塊是block參數
//如果condition不滿足,則執行block,并且遞歸調用until
until(x == 0){
x -= 1
println("x=" + x)
}
x=9
x=8
x=7
x=6
x=5
x=4
x=3
x=2
x=1
x=0
x: Int = 0
until: (condition: => Boolean)(block: => Unit)Unit