Scala函數(shù)式編程專題--scala集合和函數(shù)
前情提要:
Scala函數(shù)式編程專題—— 函數(shù)式思想介紹
scala函數(shù)式編程專題——scala基礎(chǔ)語法介紹
前面已經(jīng)稍微介紹了scala的常用語法以及面向?qū)ο蟮囊恍┖喴R,這次是補充上一章的,主要會介紹集合和函數(shù)。
注意噢,函數(shù)和方法是不一樣的,方法是在類里面定義的,函數(shù)是可以單獨存在的(嚴格來說,在scala內(nèi)部,每個函數(shù)都是一個類)
一.scala集合介紹
還記得上一章介紹的object的apply方法嗎,很多數(shù)據(jù)結(jié)構(gòu)其實都用到了它,從而讓我們可以直接用List(...)這樣來新建一個List,而不用自己手動new一個。
PS:注意,scala默認的數(shù)據(jù)結(jié)構(gòu)都是不可變的,就是說一個List,沒法刪除或增加新的元素。當然,也有“可變”的數(shù)據(jù)結(jié)構(gòu),后面會介紹到。
1.1 List
得益于apply方法,我們可以不通過new來新建數(shù)據(jù)結(jié)構(gòu)。
//通過工廠,新建一個List,這個List是不可變的 scala> val numbers = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5) numbers: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
1.2 元組Tuple
Tuple這個概念在python應(yīng)用比較廣泛,它可以將多種數(shù)據(jù)類型(Int,String,Double等)打包在一起
scala> val tup = (1,1,2.1,"tuple",'c') //將多種不同數(shù)據(jù)結(jié)構(gòu)打包一起,可以有重復 tup: (Int, Int, Double, String, Char) = (1,1,2.1,tuple,c)
但在scala中,Tuple是有長度限制的,那就是一個Tuple最多只能有22個元素。
scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21) tup: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21) //一個Tuple超過22個元素,報錯了 scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22) <console>:1: error: too many elements for tuple: 23, allowed: 22 val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)
可以看到上面,一但一個Tuple超過22個元素,就會報錯了。至于為什么是22這個神奇的數(shù)字,好像一直沒有一個統(tǒng)一的論調(diào)。有人開玩笑的說23才對,因為有部電影的名字叫《The Number 23》~~
1.3 Map和Option
在說Map之前,需要先介紹Option,因為Map里面存的就是Option,這個后面介紹。
Option翻譯過來是選項,本質(zhì)上,它也確實是一個選項,用來告訴你存不存在。還是用實例說明吧:
//Option里面可以存普通數(shù)據(jù)類型 scala> val optionInt = Option(1) optionInt: Option[Int] = Some(1) scala> optionInt.get res8: Int = 1 //但一個Option也可能為空 scala> val optionNone = Option(null) optionNone: Option[Null] = None //當Option里面是空的時候,是get不出東西的,還會報錯 scala> optionNone.get java.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:347) at scala.None$.get(Option.scala:345) ... 32 elided //這個時候可以判斷它就是空的 scala> optionNone.isEmpty res11: Boolean = true //但可以用getOrElse()方法,當Option里面有東西的時候,就返回那個東西,如果沒有東西,就返回getOrElse()的參數(shù)的內(nèi)容 scala> optionNone.getOrElse("this is null") //里面沒東西 res12: String = this is null scala> optionInt.getOrElse("this is null") //里面有東西 res15: Any = 1
再說Map,Map里面的value的類型并不是你賦的那個數(shù)據(jù)類型,而是Option。即Map(key -> Option,key1 -> Option)。
scala> val map = Map("test1" -> 1,"test2" -> 2) map: scala.collection.immutable.Map[String,Int] = Map(test1 -> 1, test2 -> 2) scala> map.get("test1") res16: Option[Int] = Some(1) scala> map.get("test3") res17: Option[Int] = None scala> map.get("test3").getOrElse("this is null") res18: Any = this is null
這樣的好處是什么呢?還記得在java里面,每次都得為java為空的情況做判斷的痛苦嗎。在scala,這些煩惱通通不存在。有了Option,媽媽再也不用擔心NullPointerException啦。
1.4 常用函數(shù)組合子
匿名函數(shù)
將集合的函數(shù)組合子,那肯定得先將匿名函數(shù)。如果有用過python中的lambda表達式,那應(yīng)該就很了解這種方式了。
前面說到,在scala中,函數(shù)就是對象,匿名函數(shù)也是函數(shù)。舉個簡單的例子:
//創(chuàng)建一個匿名函數(shù) scala> val addOne = (x: Int) => x + 1 addOne: (Int) => Int = <function1> scala> addOne(1) res4: Int = 2
注意,函數(shù)里面是可以不用return的,最后面的那個x+1就是匿名函數(shù)的返回值了。
map,reduce
因為hadoop的出現(xiàn),MapReduce都被說爛了。雖然Hadoop的Mapreduce起源自函數(shù)式的map和reduce,但兩者其實是不一樣的。感興趣的可以看看我之前寫過的一篇:從分治算法到 Hadoop MapReduce
函數(shù)式里面的map呢,粗略的說,其實相當一個有返回值的for循環(huán)。
scala> val list = List(1,2,3) list: List[Int] = List(1, 2, 3) scala> list.map(_ + 1) //這里的(_+1)其實就是一個匿名函數(shù) //讓List中每一個元素+1,并返回 res29: List[Int] = List(2, 3, 4)
至于reduce呢,也是像for循環(huán),只是不像map每次循環(huán)是當前元素,reduce除了當前元素,還有上一次循環(huán)的結(jié)果,還是看看例子吧:
scala> list.reduce((i,j) => i + j) //兩個兩個一起循環(huán),這里是讓兩個相加 res28: Int = 6
比如上面的例子,有1,2,3三個數(shù)。第一次循環(huán)的兩個是1,2,1+2就等于3,第二次循環(huán)就是上次的結(jié)果3和原本的3,3+3等于6,結(jié)果就是6。
filter
filter故名思意,就是過濾的意思,可以在filter中傳進去一個匿名函數(shù),返回布爾值。返回true的則保留,返回false的丟棄。
scala> val numbers = List(1, 2, 3, 4) numbers: List[Int] = List(1, 2, 3, 4) //過濾出余2等于0的 scala> numbers.filter((i: Int) => i % 2 == 0) res0: List[Int] = List(2, 4)
foldLeft
這個和reduce類似,也是遍歷,除了當前元素,還有上一次迭代的結(jié)果。區(qū)別在于foldLeft有一個初始值。
scala> val numbers = List(1, 2, 3, 4) numbers: List[Int] = List(1, 2, 3, 4) //(m: Int, n: Int) => m + n這部分是一個匿名函數(shù) scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n) res30: Int = 10
二.scala函數(shù)
在最前面有介紹到,函數(shù)就是對象。那為什么函數(shù)能直接運行呢?這其實得益于object的apply這個語法糖。
偏函數(shù)
偏函數(shù)(PartialFunction),從某種意義上來說,偏函數(shù)也是scala中挺重要的一個語法糖。
偏函數(shù)它長這樣:
PartialFunction[A, B] //接收一個A類型的參數(shù),返回B類型的參數(shù)
值得一提的是,scala中有一個關(guān)鍵字case,就是使用偏函數(shù)。繼續(xù)舉例子:
//下面的case就是一個偏函數(shù)PartialFunction[Int, String] scala> val one: PartialFunction[Int, String] = { case 1 => "one" } one: PartialFunction[Int,String] = <function1> scala> one(1) res11: String = Int scala> one("one") <console>:13: error: type mismatch; found : String("one") required: Int one("one")
case關(guān)鍵字會匹配符合條件的類型或值,如果不符合,會報錯。內(nèi)部實現(xiàn)就不介紹了,知道是個常用語法糖就好。
而這個case關(guān)鍵字也正是實現(xiàn)scala模式匹配中,必不可少的一環(huán),有興趣的童鞋可以看看我這篇:scala模式匹配詳細解析
這里再說一個常見應(yīng)用吧,函數(shù)組合子中有一個collect,它需要的參數(shù)就是一個偏函數(shù)。下面看個有意思的例子:
//這個list里面的Any類型 scala> val list:List[Any] = List(1, 3, 5, "seven") list: List[Any] = List(1, 3, 5, seven) //使用map會報錯,因為map接收的參數(shù)是普通函 scala> list.map { case i: Int => i + 1 } scala.MatchError: seven (of class java.lang.String) at $anonfun$1.apply(<console>:13) at $anonfun$1.apply(<console>:13) at scala.collection.immutable.List.map(List.scala:277) ... 32 elided //但如果用collect函數(shù)就可以,因為collect接收的參數(shù)是偏函數(shù),它會自動使用偏函數(shù)的一些特性,所以可以自動過濾掉不符合的數(shù)據(jù)類型 scala> list.collect { case i: Int => i + 1 } res15: List[Int] = List(2, 4, 6)
因為collect接收的參數(shù)是偏函數(shù),它會自動使用偏函數(shù)的特性,自動過濾不符合的數(shù)據(jù)類型,而map就做不到。
部分應(yīng)用函數(shù)
所謂部分應(yīng)用的意思,就是說,當調(diào)用一個函數(shù)時,可以僅傳遞一部分參數(shù)。而這樣會生成一個新的函數(shù),來個實例看看吧:
//定義一個打印兩個輸出參數(shù)的函數(shù) scala> def partial(i:Int,j:Int) : Unit = { | println(i) | println(j) | } partial: (i: Int,j: Int)Unit //賦一個值給上面那個函數(shù),另一個參數(shù)不賦值,生成一個新的函數(shù) scala> val partialFun = partial(5,_:Int) partialFun: Int => Unit = <function1> //只要一個參數(shù)就可以調(diào)用啦 scala> partialFun(10) 5 10
部分應(yīng)用函數(shù),主要是作用是代碼復用,同時也能夠增加一定的代碼可讀性。
當然還有更多有意思的用法,后面有機會說到再說。
函數(shù)柯里化
剛開始,聽到柯里化的時候很奇怪。柯里?啥玩意?
后來才知道,其實柯里是從curry音譯過來的,這個是個人名,就是發(fā)明了柯里化的發(fā)明人。
柯里化是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。
看看具體是怎么使用吧:
//我們知道函數(shù)可以這樣定義,讓它接收兩個參數(shù) scala> def curring(i:Int)(j: Int): Boolean = {false} curring: (i: Int)(j: Int)Boolean //可以把這個函數(shù)賦值給一個變量,注意變量的類型 scala> val curringVal:(Int => (Int => Boolean)) = curring _ curringVal: Int => (Int => Boolean) = <function1> //可以讓這個變量接收一個參數(shù),又變成另一個函數(shù)了 scala> val curringVal_1 = curringVal(5) curringVal_1: Int => Boolean = <function1> //再用這個變量接收一個參數(shù),終于能返回結(jié)果了 scala> curringVal_1(10) res32: Boolean = false
柯里化其實是把一個函數(shù)變成一個調(diào)用鏈的過程,和上面的部分應(yīng)用函數(shù)看起來有點像。
這幾個部分初次看可能不知道它究竟有什么用,其實這些功能的一個主要用途是函數(shù)式的依賴注入。通過這部分技術(shù)可以把被依賴的函數(shù)以參數(shù)的形式傳遞給上層函數(shù)。限于篇幅這里就先省略,后面再介紹。
結(jié)語:
此次介紹的是scala集合的一些內(nèi)容,以及一些函數(shù)的特性,再說一遍,函數(shù)其實就是對象。
我一直有一種觀點,在學習新的東西的時候,一些偏固定規(guī)則的東西,比如語法。不用全部記熟,只要知道大概原理,有個映像就行。
比如說scala的函數(shù)式編程,或是java的OOP,不需要抱有先把語法學完,再學習相關(guān)的編程理念,這在我看來是有點本末倒置了。
我一般的做法,是先熟悉大概的語法,然后去學習語言的精髓。當碰到不懂的時候,再反過來查詢具體的語法,有了目標之后,語法反而變得不是那么枯燥了。
以上只是我的一些個人看法,那么本篇到此就結(jié)束了。
到此這篇關(guān)于Scala函數(shù)式編程專題--scala集合和函數(shù)的文章就介紹到這了,更多相關(guān)scala函數(shù)式編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文讀懂modbus slave和modbus poll使用說明
modbus poll和modbus slave是一款實用的modbus開發(fā)和調(diào)試工具,可以非常方便的進行modbus調(diào)試,是非常有用的Modbus主機/從機模擬程序,這篇文章給大家介紹modbus slave和modbus poll使用說明,感興趣的朋友一起看看吧2021-04-04bilibili彈幕轉(zhuǎn)ass程序制作思路及過程
本文主要是為了方便線下播放Bilibili的彈幕,而專門制作的一款將彈幕轉(zhuǎn)換為ASS的程序,介紹了程序制作的思路及過程,有需要的朋友可以參考下2014-09-09uniApp微信小程序使用騰訊地圖定位功能及getLocation需要在app.json中聲明permission字段問
這篇文章主要介紹了uniApp微信小程序使用騰訊地圖定位功能及getLocation需要在app.json中聲明permission字段問題解決,需要的朋友可以參考下2022-12-12