kotlin 集合的操作
1.集合式函数操作
a. filter和map
filter即过滤,它会遍历集合并选出应用给定lambda后返回未true的元素。使用它可以移除不满足条件的元素(数据源并不会改变)
例如:
val list = listOf(1,2,3,4,5,6)
//过滤奇数,保留偶数
println(list.filter { it % 2==0 }) //[2, 4, 6]
//数据源
val people = listOf(Person("jack", 29),
Person("nick", 23),
Person("jone", 26))
//过滤掉年龄小于25的,保留年龄大于25的
println(people.filter { it.age>25 })
//[Person(name=jack, age=29), Person(name=jone, age=26)]
map对集合每一个元素应用给定的函数并把结果收集到一个新集合,即元素变换
val list = listOf(1,2,3,4,5,6)
//map把元素换为它的平方集合
println(list.map { it*it }) //[1, 4, 9, 16, 25, 36]
//使用map将元素为Person类型转换为String
println(people.map { it.name }) //[jack, nick, jone]
//可以使用成员引用简写
println(people.map(Person::name)) //[jack, nick, jone]
b. all,any,count,find:对集合的判断应用
检查集合中所有的元素是否都符合某个条件(又或是是否存在符合的元素)。它们是通过all和any函数表达。count为检查有多少个元素满足判断式,find函数返回第一个符合条件的元素。
//数据源
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
//检查集合中是否至少存在一个匹配的元素(any)
println(people.any(isAge23))//true
//检查有多少个元素满足判断式(count)
println(people.count(isAge23))//1
//找到第一个满足判断式的元素(find)
//如有多个匹配返回其中第一个元素,没有返回null。同义函数firstOrNull。
println(people.find(isAge23)) //Person(name=nick, age=23)
值得注意的是!all(不是所有)加上某个条件,应该用any取反
val list = listOf(1,2,3)
println(!list.all{it==3})//此种方式不推荐
println(list.any { it!=3 })//推荐此种方式定义(lambda参数中条件取反)
再就是count和.size。count方法只是跟踪匹配元素的数量,不关心元素本身,所以更高效。.size需要配合filter过滤从而中间会创建新集合用来存储。
//.size的方式(具体使用情况看实际,只关心数量不推荐此方式)
println(people.filter (isAge23).size)
c. groupBy:把列表转换成分组的map
如果需要把不同的元素划分到不同的分组,使用groupaBy事半功倍。
//数据源
val people = listOf(Person("jack", 29),
Person("nick", 23),
Person("jone", 23))
//使用group按年龄分组,返回结果是map
println(people.groupBy{it.age})
// {29=[Person(name=jack, age=29)], 23=[Person(name=nick, age=23), Person(name=jone, age=23)]}
每一个分组都存储在一个列表中,这里结果类型实质是Map
val list = listOf("a", "ab", "abc", "b")
//值得注意的是,这里的first不是String的成员,而是一个扩展(可成员引用)
println(list.groupBy(String::first))//{a=[a, ab, abc], b=[b]}
2.kotlin列表自有方法的链式调用问题
例如:查找列表中年龄最大的人
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 })
运行结果为:
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 })
运行结果为:
maxBy::AAA,
maxBy::BBB,
maxBy::CCC,
maxBy::DDD,
filter:AAA
filter:BBB
filter:CCC
filter:DDD
[PersonB(name=CCC, age=26)]
3.惰性集合操作:序列
如果对java8的lambda熟悉,一定会知道stream流的存在。上面大部分的lambda函数会及早的创建中间集合(每一步中间结果都被存储在一个临时列表)。因此如果数据过多的话链式调用就会特别低效。而序列恰好能避免创建这些临时中间对象,从而解决这一问题。
people.asSequence() //把初始集合转成序列
.map (Person::name) //序列支持和集合一样的api
.filter { it.startsWith("j") }
.toList() //把结果序列转换未序列表
惰性集合操作入口就是Sequence接口,这个接口就是表示可以逐个列举的元素序列。Sequence只提供一个方法,iterator(用来从序列里获取值)。
Sequence接口强大在于操作实现的形式。序列中元素求值是惰性的。值得注意的是asSequence()是扩展函数。
执行序列操作:中间和末端操作
序列操作分为两类:中间的和末端。一次中间操作返回值是另一个序列(知道如何变换原始序列中的元素)。而一次末端操作返回的是一个结果,这个结果可能是集合,元素,数字或者其它从初始集合的变换序列中获取的任意对象。
people.asSequence()
.map (Person::name) //中间操作
.filter { it.startsWith("j") } //中间操作
.toList() //末端操作
中间操作始终都是惰性的,不会输出任何结果。
//不会输出任何内容,lambdaa变换被延期,只有获取结果时才会被调用(末端操作)
listOf(1,2,3,4).asSequence()
.map { print("map($it) ");it*it }
.filter { print("filter($it)");it%2==0 }
加上末端操作才会进行真正的结果输出
listOf(1,2,3,4).asSequence()
.map { print("map($it) ");it*it }
.filter { print("filter($it)");it%2==0 }
.toList() // 末端操作触发执行所有延期计算
//map(1) filter(1)map(2) filter(4)map(3) filter(9)map(4) filter(16)
着重注意的是计算顺序,序列的操作是按顺序应用在每一个元素上:处理第一个元素后,再完成第二个元素,以此类推。
这也意味着有部分元素根本不会发生任何变换,举个map和find的例子。先把一个数字映射成它的平方,然后找到第一个比3大的条目。
println(listOf(1,2,3,4).asSequence().map { it*it }.find { it>3 })
如果同样的操作被应用在集合上,那么map结果会先被求出来,然后会把中间集合中满足的判断式的元素找出来。而对于序列来说,惰性方法意味着可以跳过处理部分元素。这也是及早求值(用集合)和惰性求值(用序列)的区别。
集合上执行操作的顺序是会影响性能的。再举个例子,用不同的操作顺序找出上述数据源person集合中长度小于某个限制的人名。
/数据源
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]
//先filter再map
println(people.asSequence().filter { it.name.length>2 }.map(Person::name).toList())//[nec, jojo]
结果当然是一样的,不同的是如果map在前,那么是每个元素都进行变换后在去过滤,而filter在前,则是先过滤在变换(被过滤掉的不会进行变换)。
使用序列的效率问题
执行如下代码不适用序列
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${System.currentTimeMillis() - startTime} ms")
}
fun main(args: Array) = 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) = 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
当运算级降低时
不适用序列
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${System.currentTimeMillis() - startTime} ms")
}
fun main(args: Array) = 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) = 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
由此可以看出序列比较适合运算级数比较大的场景