kotlin 集合的操作
1.集合式函數(shù)操作
a. filter和map
filter即過(guò)濾,它會(huì)遍歷集合并選出應(yīng)用給定lambda后返回未true的元素孵坚。使用它可以移除不滿足條件的元素(數(shù)據(jù)源并不會(huì)改變)
例如:
val list = listOf(1,2,3,4,5,6)
//過(guò)濾奇數(shù)粮宛,保留偶數(shù)
println(list.filter { it % 2==0 }) //[2, 4, 6]
//數(shù)據(jù)源
val people = listOf(Person("jack", 29),
Person("nick", 23),
Person("jone", 26))
//過(guò)濾掉年齡小于25的,保留年齡大于25的
println(people.filter { it.age>25 })
//[Person(name=jack, age=29), Person(name=jone, age=26)]
map對(duì)集合每一個(gè)元素應(yīng)用給定的函數(shù)并把結(jié)果收集到一個(gè)新集合卖宠,即元素變換
val list = listOf(1,2,3,4,5,6)
//map把元素?fù)Q為它的平方集合
println(list.map { it*it }) //[1, 4, 9, 16, 25, 36]
//使用map將元素為Person類型轉(zhuǎn)換為String
println(people.map { it.name }) //[jack, nick, jone]
//可以使用成員引用簡(jiǎn)寫(xiě)
println(people.map(Person::name)) //[jack, nick, jone]
b. all,any,count,find:對(duì)集合的判斷應(yīng)用
檢查集合中所有的元素是否都符合某個(gè)條件(又或是是否存在符合的元素)巍杈。它們是通過(guò)all和any函數(shù)表達(dá)。count為檢查有多少個(gè)元素滿足判斷式逗堵,find函數(shù)返回第一個(gè)符合條件的元素秉氧。
//數(shù)據(jù)源
val people = listOf(Person("jack", 29),
Person("nick", 23),
Person("jone", 26))
//年齡是否滿足23
val isAge23={p:Person->p.age<=23}
//檢查集合看是否所有元素滿足(all)
println(people.all(isAge23)) //false
//檢查集合中是否至少存在一個(gè)匹配的元素(any)
println(people.any(isAge23))//true
//檢查有多少個(gè)元素滿足判斷式(count)
println(people.count(isAge23))//1
//找到第一個(gè)滿足判斷式的元素(find)
//如有多個(gè)匹配返回其中第一個(gè)元素,沒(méi)有返回null蜒秤。同義函數(shù)firstOrNull汁咏。
println(people.find(isAge23)) //Person(name=nick, age=23)
值得注意的是!all(不是所有)加上某個(gè)條件亚斋,應(yīng)該用any取反
val list = listOf(1,2,3)
println(!list.all{it==3})//此種方式不推薦
println(list.any { it!=3 })//推薦此種方式定義(lambda參數(shù)中條件取反)
再就是count和.size。count方法只是跟蹤匹配元素的數(shù)量攘滩,不關(guān)心元素本身帅刊,所以更高效。.size需要配合filter過(guò)濾從而中間會(huì)創(chuàng)建新集合用來(lái)存儲(chǔ)漂问。
//.size的方式(具體使用情況看實(shí)際赖瞒,只關(guān)心數(shù)量不推薦此方式)
println(people.filter (isAge23).size)
c. groupBy:把列表轉(zhuǎn)換成分組的map
如果需要把不同的元素劃分到不同的分組,使用groupaBy事半功倍蚤假。
//數(shù)據(jù)源
val people = listOf(Person("jack", 29),
Person("nick", 23),
Person("jone", 23))
//使用group按年齡分組栏饮,返回結(jié)果是map
println(people.groupBy{it.age})
// {29=[Person(name=jack, age=29)], 23=[Person(name=nick, age=23), Person(name=jone, age=23)]}
每一個(gè)分組都存儲(chǔ)在一個(gè)列表中,這里結(jié)果類型實(shí)質(zhì)是Map<Int,List<Person>>磷仰,mapKeys或mapValues函數(shù)也能作用于它袍嬉。
val list = listOf("a", "ab", "abc", "b")
//值得注意的是,這里的first不是String的成員灶平,而是一個(gè)擴(kuò)展(可成員引用)
println(list.groupBy(String::first))//{a=[a, ab, abc], b=[b]}
2.kotlin列表自有方法的鏈?zhǔn)秸{(diào)用問(wèn)題
例如:查找列表中年齡最大的人
val people = listOf(PersonB("AAA",23),PersonB("BBB",18),PersonB("CCC",26),PersonB("DDD",5))
println(people.filter { println("filter:${it.name}"); it.age == people.maxBy { println("maxBy::${it.name}");it.age }!!.age })
運(yùn)行結(jié)果為:
filter:AAA
maxBy::AAA
maxBy::BBB
maxBy::CCC
maxBy::DDD
filter:BBB
maxBy::AAA
maxBy::BBB
maxBy::CCC
maxBy::DDD
filter:CCC
maxBy::AAA
maxBy::BBB
maxBy::CCC
maxBy::DDD
filter:DDD
maxBy::AAA
maxBy::BBB
maxBy::CCC
maxBy::DDD
[PersonB(name=CCC, age=26)]
每次filter都走了一遍maxBy方法
推薦使用以下用法
val maxAge = people.maxBy { println("maxBy::${it.name},");it.age }!!.age
println(people.filter { println("filter:${it.name}"); it.age == maxAge })
運(yùn)行結(jié)果為:
maxBy::AAA,
maxBy::BBB,
maxBy::CCC,
maxBy::DDD,
filter:AAA
filter:BBB
filter:CCC
filter:DDD
[PersonB(name=CCC, age=26)]
3.惰性集合操作:序列
如果對(duì)java8的lambda熟悉伺通,一定會(huì)知道stream流的存在。上面大部分的lambda函數(shù)會(huì)及早的創(chuàng)建中間集合(每一步中間結(jié)果都被存儲(chǔ)在一個(gè)臨時(shí)列表)逢享。因此如果數(shù)據(jù)過(guò)多的話鏈?zhǔn)秸{(diào)用就會(huì)特別低效罐监。而序列恰好能避免創(chuàng)建這些臨時(shí)中間對(duì)象,從而解決這一問(wèn)題瞒爬。
people.asSequence() //把初始集合轉(zhuǎn)成序列
.map (Person::name) //序列支持和集合一樣的api
.filter { it.startsWith("j") }
.toList() //把結(jié)果序列轉(zhuǎn)換未序列表
惰性集合操作入口就是Sequence接口弓柱,這個(gè)接口就是表示可以逐個(gè)列舉的元素序列。Sequence只提供一個(gè)方法侧但,iterator(用來(lái)從序列里獲取值)吆你。
Sequence接口強(qiáng)大在于操作實(shí)現(xiàn)的形式。序列中元素求值是惰性的俊犯。值得注意的是asSequence()是擴(kuò)展函數(shù)妇多。
執(zhí)行序列操作:中間和末端操作
序列操作分為兩類:中間的和末端。一次中間操作返回值是另一個(gè)序列(知道如何變換原始序列中的元素)燕侠。而一次末端操作返回的是一個(gè)結(jié)果者祖,這個(gè)結(jié)果可能是集合,元素绢彤,數(shù)字或者其它從初始集合的變換序列中獲取的任意對(duì)象七问。
people.asSequence()
.map (Person::name) //中間操作
.filter { it.startsWith("j") } //中間操作
.toList() //末端操作
中間操作始終都是惰性的,不會(huì)輸出任何結(jié)果。
//不會(huì)輸出任何內(nèi)容茫舶,lambdaa變換被延期械巡,只有獲取結(jié)果時(shí)才會(huì)被調(diào)用(末端操作)
listOf(1,2,3,4).asSequence()
.map { print("map($it) ");it*it }
.filter { print("filter($it)");it%2==0 }
加上末端操作才會(huì)進(jìn)行真正的結(jié)果輸出
listOf(1,2,3,4).asSequence()
.map { print("map($it) ");it*it }
.filter { print("filter($it)");it%2==0 }
.toList() // 末端操作觸發(fā)執(zhí)行所有延期計(jì)算
//map(1) filter(1)map(2) filter(4)map(3) filter(9)map(4) filter(16)
著重注意的是計(jì)算順序,序列的操作是按順序應(yīng)用在每一個(gè)元素上:處理第一個(gè)元素后,再完成第二個(gè)元素讥耗,以此類推有勾。
這也意味著有部分元素根本不會(huì)發(fā)生任何變換,舉個(gè)map和find的例子古程。先把一個(gè)數(shù)字映射成它的平方蔼卡,然后找到第一個(gè)比3大的條目。
println(listOf(1,2,3,4).asSequence().map { it*it }.find { it>3 })
如果同樣的操作被應(yīng)用在集合上挣磨,那么map結(jié)果會(huì)先被求出來(lái)雇逞,然后會(huì)把中間集合中滿足的判斷式的元素找出來(lái)。而對(duì)于序列來(lái)說(shuō)茁裙,惰性方法意味著可以跳過(guò)處理部分元素塘砸。這也是及早求值(用集合)和惰性求值(用序列)的區(qū)別。
集合上執(zhí)行操作的順序是會(huì)影響性能的晤锥。再舉個(gè)例子谣蠢,用不同的操作順序找出上述數(shù)據(jù)源person集合中長(zhǎng)度小于某個(gè)限制的人名。
/數(shù)據(jù)源
val people = listOf(Person("jk", 29),
Person("nec", 23),
Person("jojo", 23))
//先map再filter
println(people.asSequence().map (Person::name ).filter { it.length>2 }.toList())//[nec, jojo]
//先f(wàn)ilter再map
println(people.asSequence().filter { it.name.length>2 }.map(Person::name).toList())//[nec, jojo]
結(jié)果當(dāng)然是一樣的查近,不同的是如果map在前,那么是每個(gè)元素都進(jìn)行變換后在去過(guò)濾挤忙,而filter在前霜威,則是先過(guò)濾在變換(被過(guò)濾掉的不會(huì)進(jìn)行變換)。
使用序列的效率問(wèn)題
執(zhí)行如下代碼不適用序列
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${System.currentTimeMillis() - startTime} ms")
}
fun main(args: Array<String>) = computeRunTime{
(0..10000000)
.map { it + 1 }
.filter { it % 2 == 0 }
.count { it < 100 }
.run {
println("by using sequence result is $this")
}
}
//by using sequence result is 49
//the code run time is 2755 ms
以下為使用序列
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${System.currentTimeMillis() - startTime} ms")
}
fun main(args: Array<String>) = computeRunTime{
(0..10000000)
.asSequence()
.map { it + 1 }
.filter { it % 2 == 0 }
.count { it < 100 }
.run {
println("by using sequence result is $this")
}
}
//by using sequence result is 49
//the code run time is 150 ms
當(dāng)運(yùn)算級(jí)降低時(shí)
不適用序列
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${System.currentTimeMillis() - startTime} ms")
}
fun main(args: Array<String>) = computeRunTime{
(0..1000)
.map { it + 1 }
.filter { it % 2 == 0 }
.count { it < 100 }
.run {
println("by using sequence result is $this")
}
}
//by using sequence result is 49
//the code run time is 21 ms
使用序列
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${System.currentTimeMillis() - startTime} ms")
}
fun main(args: Array<String>) = computeRunTime{
(0..1000)
.asSequence()
.map { it + 1 }
.filter { it % 2 == 0 }
.count { it < 100 }
.run {
println("by using sequence result is $this")
}
}
//by using sequence result is 49
//the code run time is 31 ms
由此可以看出序列比較適合運(yùn)算級(jí)數(shù)比較大的場(chǎng)景