kotlin 集合的操作

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>,mapKeys或mapValues函数也能作用于它。

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

由此可以看出序列比较适合运算级数比较大的场景

你可能感兴趣的:(kotlin 集合的操作)