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<Int,List<Person>>,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<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

当运算级降低时

不适用序列

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容