Kotlin學(xué)習(xí)筆記(五)-常見高階函數(shù)

[toc]

前言

這一節(jié)我們主要說下Kotlin中關(guān)于數(shù)據(jù)集合中的常用高階函數(shù)

map

map是遍歷一個數(shù)組遍歷的過程可以對數(shù)組item進(jìn)行操作(篩選、數(shù)據(jù)轉(zhuǎn)換等) ,返回一個新的數(shù)據(jù)集合
例子:

 val list = listOf(2, 8, 4, 5, 9, 7)
 //Kotlin 寫法 等價于 newList的轉(zhuǎn)化
 val newList1 = list.map {
        it * 3 + 2
    }
flatmap

就是把幾個小的list轉(zhuǎn)換到一個大的list中
例子:

    val flatList = listOf(
        2..10,
        5..25,
        100..200
    )
    //flatten()  flatMap方法中無其他操作可以用flatten()
    val flatMapList = flatList.flatMap { intRange: IntRange ->
        intRange
    }
    

嵌套使用:

    //上面flatMapList2表達(dá)式的完整寫法
    val flatMapList3 = flatList.flatMap(fun(intRange: IntRange): List<String> {
        return intRange.map(fun(intElement: Int): String {
            return "No.$intElement"
        })
    })
reduce

求list的和、求階乘
求和:

/reduce求list的和 acc是累加的結(jié)果 i是每次遍歷出來的元素
    val int: Int = list.reduce { acc, i -> acc + i }

求階乘:

    
    //0->0 1->(1*1)*1 2->(1*1)*2 3->(1*2)*3
    (0..6).map(::factorial).forEach(::println)
    
    fun factorial(n: Int): Int {
    if (n == 0) return 1
    //相當(dāng)于 n=3是 1*1,(1+2)*2,(1+2+3)*3,(1+2+3+4)*4
    return (1..n).reduce { acc, i -> acc * i }
    }
fold

是帶初始值的reduce 相對更強大,且對返回值無要求

 println((0..6).map(::factorial).fold(100) { acc, i -> acc + i })//100+873=973

字符串拼接:
這里傳入的類型初始值是StringBuilder()

    println((0..6).map(::factorial).fold(StringBuilder())
    { acc, i -> acc.append(i).append(",") })
joinToString

字符串拼接

 println((0..6).joinToString("/", ".", ";"))
filter/takeWhile

根據(jù)條件篩選

    println((0..6).map(::factorial))
    println((0..6).map(::factorial).filter { it % 2 == 1 })
    println((0..6).map(::factorial).takeWhile { it < 130 })//遇到第一個不滿足條件的停止輸出
尾遞歸優(yōu)化

Kotlin 支持一種稱為尾遞歸的函數(shù)式編程?格。這允許一些通常用循環(huán)寫的算法改用遞歸函數(shù)來寫,而無堆棧溢出的?險。當(dāng)一個函數(shù)用tailrec修飾符標(biāo)記并滿足所需的形式時,編譯器會優(yōu)化該遞歸,留下一個快速而高效的基于循環(huán)的版本。

這是官網(wǎng)的說法。反正我是覺得有些晦澀。我的理解,首先理解什么是尾遞歸。下看下下面的三個例子:

data class TreeNode(val value: Int) {
    var left: TreeNode? = null
    var right: TreeNode? = null
}


//尾遞歸
tailrec fun findListNode(head: ListNode?, value: Int): ListNode? {
    head ?: return null
    if (head.value == value) return head
    return findListNode(head.next, value)
}

//返回中存在 * 運算 所以是非尾遞歸
fun factorial(n: Long): Long {
    return n * factorial(n - 1)
}


//這個也是非 尾遞歸
fun findTreeNode(root: TreeNode?, value: Int): TreeNode? {
    root ?: return null
    if (root.value == value) return root
    return findTreeNode(root.left, value) ?: findTreeNode(root.right, value)

}

調(diào)用完自己之后沒有任何操作的遞歸就是尾遞歸尾遞歸優(yōu)化就是在方法_上加tailrec關(guān)鍵地提示編譯器進(jìn)行優(yōu)化(將遞歸轉(zhuǎn)化味迭代進(jìn)行處理)

若非尾遞歸加上tailrec也會提示(提示黃色警告)。

閉包

在函數(shù)為一等公民的語言中,都具有閉包的特性。我的理解就是函數(shù)里面聲明函數(shù),函數(shù)里面返回函數(shù),這就是閉包。在Java中調(diào)用完方法,方法內(nèi)部的狀態(tài)是不會被記住的,但是在Kotlin中,函數(shù)的狀態(tài)在調(diào)用后不會被銷毀。閉包有點像java的內(nèi)部類,內(nèi)部類持有外部類的引用,會導(dǎo)致外部類無法釋放,也就是java中的內(nèi)存泄漏。我個人覺的在Kotlin中閉包也會帶來消耗。

  1. 函數(shù)的運行環(huán)境
  2. 持有函數(shù)運行狀態(tài)
  3. 函數(shù)內(nèi)部可以定義函數(shù)
  4. 函數(shù)內(nèi)部也可以定義類
復(fù)合函數(shù)

本身不是語法上的關(guān)鍵字或是格式,是按照以前現(xiàn)有的知識,只不過在編寫上有點難以理解。這個只是函數(shù)的復(fù)合 沒有新的知識點
結(jié)合例子說明:

val add5 = { i: Int -> i + 5 }//g(x)

val multiplyBy2 = { i: Int -> i * 2 }//f(x)
fun main(args: Array<String>) {

    println(multiplyBy2(add5(8)))

    val add5AndMultiplyBy2 = add5 andThen multiplyBy2 //m(x)=f(g(x))  2*(8+5)=26
    println(add5AndMultiplyBy2(8))

    val add5AndMultiplyByCopy = multiplyBy2 andThen add5//m(x)=g(f(x))  2*8+5=21//前后參數(shù)類型相同可以置換位置 否則是不可以的 所以置換后的結(jié)果也是不同的
    println(add5AndMultiplyByCopy(8))

    val add5ComposeThen = add5 compose multiplyBy2
    println(add5ComposeThen(8))//m(x)=g(f(x)) 21

    val complexFunX = funFx complexFun funGxy
//    val complexFunXCopy =funGxy  complexFun funFx //這個就不可以 類型參數(shù)是要根據(jù)條件
    println(complexFunX(3, 2))//3*3+50+2=61
}


//m(x)=f(g(x))   add5  andThen multiplyBy2相當(dāng)于g(x).andThen(f(g(x)))=Function1<P1, P2>.andThen(f(g(x)))
//復(fù)合函數(shù) 擴展Function1的擴展方法 infix 中綴表達(dá)式
//Function1 傳入1個參數(shù)的函數(shù) P1 接收的參數(shù)類型 P2返回的參數(shù)類型
//擴展方法andThen接收 一個參數(shù)的函數(shù) 他的參數(shù) 是add5的返回值 再返回最終結(jié)果
//andThen左邊的函數(shù)  Function1<P1,P2> 接收一個參數(shù)P1 返回結(jié)果P2
//andThen右邊的函數(shù) function:Function1<P2,R> 參數(shù)為左邊函數(shù)的返回值P2 返回結(jié)果R
//聚合的結(jié)果返回函數(shù)Function1<P1,R> 是以P1作為參數(shù) R做為結(jié)果的函數(shù)
//相當(dāng)于P1,P2 聚合 P2,R 返回 P1,R
//f(g(x))  P1相當(dāng)于x P2 相當(dāng)于g(x)返回值 返回的結(jié)果Function1<P1,R> R相當(dāng)于f(g(x)) 的返回值
//Function1<P1,P2> 相當(dāng)于g(x)
//function:Function1<P2,R> 相當(dāng)于x
//
infix fun <P1, P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>): Function1<P1, R> {
    return fun(p1: P1): R {
        return function.invoke(this.invoke(p1))
    }
}

//compose左邊函數(shù)接收參數(shù)P2 返回R
//compse右邊函數(shù) 接收參數(shù)P1 返回P2
//返回結(jié)果函數(shù)P1,R
//相當(dāng)于先執(zhí)行右邊返回了P1,P2  在執(zhí)行P2,R函數(shù) 聚合成P1,R
//g(f(x))
//f(x).compose(g(f(x)))
infix fun <P1, P2, R> Function1<P2, R>.compose(function: Function1<P1, P2>): Function1<P1, R> {
    return fun(p1: P1): R {
        return this.invoke(function.invoke(p1))
    }
}

//課外擴展  m(x,y) = f(g(x,y)
val funFx = { i: Int -> i + 2 }
val funGxy = { i: Int, j: Int -> 3 * i + 100 / j }

//m(x,y) = f(g(x,y))
infix fun <P1, P2, P3, R> Function1<P3, R>.complexFun(function: Function2<P1, P2, P3>): Function2<P1, P2, R> {
    return fun(p1: P1, p2: P2): R {
        return this.invoke(function.invoke(p1, p2))
    }
}

柯里化函數(shù)(currying) -函數(shù)的鏈?zhǔn)秸{(diào)用
  • 柯里化函數(shù)就是把多個函數(shù)轉(zhuǎn)話成一個一個參數(shù)傳入
  • 柯里化就是將具有多個參數(shù)的函數(shù),變成多個單個參數(shù)的函數(shù),然后鏈?zhǔn)秸{(diào)用。注意調(diào)用時參數(shù)的順序不能顛倒

個人覺得 柯里化的意義在于:允許調(diào)用者分段調(diào)用。因為Kotlin是函數(shù)為一等公民的語言。那么假設(shè)有一個方法需要傳10個參數(shù),可能A模塊傳了2個,然后返回函數(shù),B模塊調(diào)用A模塊的方法并將其8個參數(shù)補齊,并真正使用。
例子:

//正常下的函數(shù)編寫:  
fun log1(tag: String, target: OutputStream, message: Any?) {
    target.write("[$tag] $message\n".toByteArray())
}

上面函數(shù)變化:

//這是另外一種表達(dá)方式 與之前的函數(shù)表達(dá)結(jié)果相同
fun log2(tag: String) = fun(target: OutputStream) = fun(message: Any?) = target.write("[$tag] $message\n".toByteArray())

這就是柯里化函數(shù)。

再講將新的函數(shù)表達(dá)抽象就變成柯里化函數(shù)

//kotlin中柯里化鏈?zhǔn)秸{(diào)用的含義
fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.curried() = fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = this(p1, p2, p3)

調(diào)用:

// ::log1與 { tag: String, target: OutputStream, message: Any? -> log1(tag, target, message) } 是等價的 表示對函數(shù)的引用
//    { tag: String, target: OutputStream, message: Any? -> log1(tag, target, message) }.curried()("ggxiaozhi")(System.out)("Hello World!")
    log1("ggxiaozhi", System.out, "Hello World!")
    log2("ggxiaozhi")(System.out)("Hello World!!")
    //一個函數(shù)的參數(shù)復(fù)合柯里化版本 那么就可以使用::方法名字 如:::log1 拿到引用使用.curried()方法
    ::log1.curried()("ggxiaozhi")(System.out)("Hello World!!!")

這里封裝成擴展方法,是為了方便以后調(diào)用

偏函數(shù)

偏函數(shù)其實就是給多個參數(shù)的函數(shù)設(shè)置默認(rèn)參數(shù),那么再使用的時候只需要傳入部分參數(shù)即可。

在上面柯里化函數(shù)的例子中,如果默認(rèn)參數(shù)在前面,也可以使用偏函數(shù),如:

    val consoleLogWithTag = (::log1.curried())("ggxiaozhi")(System.out)
    consoleLogWithTag("Hello World Tag")//偏函數(shù)

consoleLogWithTag方法就是一個偏函數(shù)。首先經(jīng)過柯里化后,將第一個參數(shù)和第二個參數(shù)固定得到consoleLogWithTag一個新的函數(shù)。那個這個函數(shù)其實就是偏函數(shù)

所以偏函數(shù)與柯里化函數(shù)存在一定的聯(lián)系,當(dāng)柯里化函數(shù)最前面的參數(shù)想設(shè)置默認(rèn)值的時候可以使用偏函數(shù)

下面我們來看下真正的偏函數(shù):

   //partial2
    val bytes = "我是中國人".toByteArray(charset("GBK"))
    val stringFormGBK = makeStringFromGBKBytes(bytes)
    println(stringFormGBK)

    //partial1
    val stringFormGBKP1=makeStringFromGBKBytesp1(charset("GBK"))
    println(stringFormGBKP1)
    
    //偏函數(shù)  1-3
fun <P1, P2, R> Function2<P1, P2, R>.partial2() = fun(p2: P2) = fun(p1: P1) = this(p1, p2)//第一個參數(shù)默認(rèn) 傳入第二個參數(shù)

fun <P1, P2, R> Function2<P1, P2, R>.partial1() = fun(p1: P1) = fun(p2: P2) = this(p1, p2)//第二個參數(shù)默認(rèn) 傳入第一個

完全可以使用默認(rèn)參數(shù)+具名參數(shù)的方式來實現(xiàn)參數(shù)的固定。如果需要固定的參數(shù)在中間,雖然說可以通過具名參數(shù)來解決,但是很尷尬,因為必須使用一大堆具名參數(shù)。因為默認(rèn)參數(shù)你不傳就用默認(rèn)參數(shù),但是你傳入了,如果不使用具名參數(shù)那么函數(shù)就會以為你傳參數(shù)的位置是要覆蓋默認(rèn)參數(shù),所以必須具名函數(shù)因此偏函數(shù)就誕生了。偏函數(shù)就是一個多元函數(shù)傳入了部分參數(shù)之后的得到的新的函數(shù)。

總結(jié):

  1. 當(dāng)柯里化后的函數(shù) 如果默認(rèn)函數(shù)位置在參數(shù)的前面 那么 可以直接使用偏函數(shù)
  2. 如果函數(shù)的默認(rèn)函數(shù)在氣其他位置 那么可以使用擴展方法 FunctionN 來實現(xiàn)

結(jié)語

下篇我們說下反射和泛型

Github源碼直接運行,包含全部詳細(xì)筆記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內(nèi)容

  • 原文鏈接:https://github.com/EasyKotlin 值就是函數(shù),函數(shù)就是值。所有函數(shù)都消費函數(shù),...
    JackChen1024閱讀 6,027評論 1 17
  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念。通過函數(shù)可以封裝任意多條語句,而且...
    道無虛閱讀 4,614評論 0 5
  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法。 上面代碼檢查函數(shù)l...
    陳老板_閱讀 457評論 0 1
  • 1.函數(shù)參數(shù)的默認(rèn)值 (1).基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法。
    趙然228閱讀 703評論 0 0
  • 一、函數(shù)參數(shù)的默認(rèn)值 1.1、基本用法 ES6 允許為函數(shù)的參數(shù)設(shè)置默認(rèn)值,直接寫在參數(shù)定義的后面 ES6 的寫法...
    了凡和纖風(fēng)閱讀 289評論 0 0