Kotlin Koans学习笔记(2)

这是Kotlin Koans学习笔记的第二部分,上一篇在这里

第二部分一共12个任务,都是关于Kotlin集合操作。

13.Introduction

Kotlin提供了一系列的to方法将一个集合类型转换成另外一个集合类型。

这一部分的第一个任务很简单,根据提示就可以完成,关于任务就不必多说。

先说明一下第二部分所有任务的数据模型。这一部分所有的任务都是围绕一个商店(Shop)展开,商店有一个客户(Customer)列表。

客户具有姓名、城市和订单(Order)列表三个属性。

订单具有商品(Product)列表和是否已经发货两个属性。

商品具有名称和价格两个属性。

data class Shop(val name: String, val customers: List<Customer>)

data class Customer(val name: String, val city: City, val orders: List<Order>) {
    override fun toString() = "$name from ${city.name}"
}

data class Order(val products: List<Product>, val isDelivered: Boolean)

data class Product(val name: String, val price: Double) {
    override fun toString() = "'$name' for $price"
}

data class City(val name: String) {
    override fun toString() = name
}

第二部分所有的任务都是使用扩展函数的形式完成。

14.Filter Map

这个任务主要练习使用filtermap这两个方法。

filter

filter方法返回一个包含所有满足指定条件元素的列表。与之对应的还有filterNot,顾名思义就是返回一个包含所有不满足指定条件的元素列表。还有一个filterNotNull,返回所有不为null的元素列表。

回到我们的任务要求:返回指定城市所有客户的列表。使用filter方法就可以完成:

fun Shop.getCustomersFrom(city: City): List<Customer> {
    // Return a list of the customers who live in the given city
    return customers.filter{it.city == city}
}

再精简一下:

// Return a list of the customers who live in the given city
fun Shop.getCustomersFrom(city: City) = customers.filter{it.city == city}

map

map就是将指定的转换函数运用到原始集合的每一个元素,并返回一个转换后的集合。

任务要求返回所有客户所在城市的Set。这里我们需要使用map 和toSet两个方法:

// Return the set of cities the customers are from
fun Shop.getCitiesCustomersAreFrom() = customers.map { it.city }.toSet()

15.All Any and others Predicates

这个任务主要练习all ,anycount等几个方法。

all

第一个小任务是判断是否所有的客户都来自指定的城市。这需要使用Kotlin库提供的all方法。如果所有的元素都满足指定的条件那么all方法就返回true:

// Return true if all customers are from the given city
fun Shop.checkAllCustomersAreFrom(city: City): Boolean {
    //return customers.filter { it.city != city }.isEmpty()
    //return customers.filter{!it.isFrom(city)}.isEmpty()
    return customers.all { it.isFrom(city) }
}

当然也可以不使用all来完成,不过效率可能没有all高,因为all方法在遍历的过程中遇到第一个不满足条件的元素就返回结果(false):

// Return true if all customers are from the given city
fun Shop.checkAllCustomersAreFrom(city: City) = customers.filter{!it.isFrom(city)}.isEmpty()

any

第二个小任务就是查询是否至少存在一个用户来自指定的城市。需要使用any方法,如果至少有一个元素满足指定的条件any就返回ture:

// Return true if there is at least one customer from the given city
fun Shop.hasCustomerFrom(city: City) = customers.any{it.city==city}

count

第三个小任务计算来自指定城市的客户数量。需要使用count方法,count方法返回满足指定条件的元素数量。

fun Shop.countCustomersFrom(city: City): Int {
    // Return the number of customers from the given city
    return customers.count{ it.city == city}
}

firstOrNull

最后一个小任务,返回一个来自指定城市的客户,如果没有就返回null。需要使用firstOrNull方法,该方法返回第一个满足指定条件的元素,如果没有就返回null。和它相似的还有first,不过first是返回第一个满足指定条件的元素,如果没有元素满足指定条件则抛出异常NoSuchElementException

// Return a customer who lives in the given city, or null if there is none
fun Shop.findAnyCustomerFrom(city: City) = customers.firstOrNull { it.city == city }

16.FlatMap

这个任务的两个小项都是练习使用flatmap方法。flatmap方法就是针对列表中的每一项根据指定的方法生成一个列表,最后将所有的列表拼接成一个列表返回。

第一个小项是要求返回一个客户所有已订购的产品,需要使用flatmap方法,遍历该用户所有的订单,然后将所有订单的产品拼接起来:

val Customer.orderedProducts: Set<Product> get() {
    // Return all products ordered by customer
    return orders.flatMap { it.products }.toSet()
}

第二个小项是要求返回所有至少被一个客户订购过的商品集合。这个在第一个小任务的基础上再flatmap一次:

val Shop.allOrderedProducts: Set<Product> get() {
    // Return all products that were ordered by at least one customer
    return customers.flatMap { it.orderedProducts }.toSet()
}

17.Max Min

第一个任务是返回商店中订单数目最多的一个客户。使用Kotlin库提供的max方法很好实现。max方法返回最大的一个元素,如果没有元素则返回null。对于自定义的对象,我们可以通过maxBy方法提供最大的评判标准,maxBy方法返回第一个满足指定评判标准的最大值。

fun Shop.getCustomerWithMaximumNumberOfOrders(): Customer? {
    // Return a customer whose order count is the highest among all customers
    return customers.maxBy { it.orders.size }
}

第二个任务是要求返回一个客户所订购商品中价格最高的一个商品,使用flatmapmaxBy组合:

fun Customer.getMostExpensiveOrderedProduct(): Product? {
    // Return the most expensive product which has been ordered
    return orders.flatMap { it.products }.maxBy { it.price }
}

当然和maxmaxBy对应的还有minminBy,只不过返回的是最小值。

18.Sort

Kotlin库提供了为元素排序的方法sortedsorted方法会返回一个升序排序的列表,同样可以通过sortedBy指定排序的标准,按照指定的标准排序。

任务的要求返回一个客户列表,客户的顺序是根据订单的数量由低到高排列:

fun Shop.getCustomersSortedByNumberOfOrders(): List<Customer> {
    // Return a list of customers, sorted by the ascending number of orders they made
    return customers.sortedBy { it.orders.size }
}

对于排序操作同样可以要求按照降序排序,两个方法分别是:sortedDescendingsortedByDescending

还有另外一个操作方法就是反转reverse

19.Sum

任务要求计算一个客户所有已订购商品的价格总和。使用Kotlin的sumBy方法就可以完成,sumBy将集合中所有元素按照指定的函数变换以后的结果累加。当然先要将所有的订单flatmap:

fun Customer.getTotalOrderPrice(): Double {
    // Return the sum of prices of all products that a customer has ordered.
    // Note: a customer may order the same product for several times.
   return  orders.flatMap { it.products }.sumByDouble { it.price }
}

20.GroupBy

groupBy方法返回一个根据指定条件分组好的map。任务要求是返回来自每一个城市的客户的map:

fun Shop.groupCustomersByCity(): Map<City, List<Customer>> {
    // Return a map of the customers living in each city
    return  customers.groupBy { it.city }
}

21.Parition

任务要求返回所有未发货订单数目多于已发货订单的用户。

任务的范例中展示了怎么使用partition方法。partition方法会将原始的集合分成一对集合,这一对集合中第一个是满足指定条件的元素集合,第二个是不满足指定条件的集合。

这里我们先给Customer定义一个函数,判断该用户是否属于未发货订单大于已发货订单,处理方法就是使用partition方法将所有的订单分割,分割的条件就是该订单已经发货:

fun Customer.isMoreUndeliveredOrdersThanDelivered(): Boolean{
    val(delivered, undelivered) = orders.partition { it.isDelivered }
    return delivered.size < undelivered.size
}

然后再对所有的客户进行筛选:

fun Shop.getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set<Customer> {
    // Return customers who have more undelivered orders than delivered
    return customers.filter { it.isMoreUndeliveredOrdersThanDelivered() }.toSet()
}

将这两个函数写到一起:

fun Shop.getCustomersWithMoreUndeliveredOrdersThanDelivered() =
        customers.filter {
            val(delivered, undelivered) = it.orders.partition { it.isDelivered }
            undelivered.size > delivered.size 
        }.toSet()

22.Fold

任务要求返回每一个顾客都购买过的商品集合。

先来看一下fold方法,fold方法就是给定一个初始值,然后通过迭代对集合中的每一个元素执行指定的操作并将操作的结果累加。注意操作函数的两个参数分别是累加结果和集合的元素。

直接看fold函数的定义吧:

public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (R, T) -> R): R {
    var accumulator = initial
    for (element in this) accumulator = operation(accumulator, element)
    return accumulator
}

回到我们的任务,使用fold函数,由于任务要求返回所有客户都已经订购的商品,所以初始值设置为所有已经订购的商品,然后用这个初始值去和每一个客户已订购的商品求交集,最终的结果就是所有用户都已经购买过的商品:

fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    // Return the set of products ordered by every customer
    return customers.fold(allOrderedProducts, {
        orderedByAll, customer -> orderedByAll.intersect(customer.orderedProducts)
    })
}

这里使用了Kotlin提供的intersect方法。

23.CompoundTasks

终于快结束这一部分的任务了。这一部分包括几个小任务,完成任务需要用到前面所练习的各种方法的组合。

来看第一个小任务:返回所有购买了指定商品的客户列表。首先给Customer扩展一个方法,判断他是否已经订购指定的商品,使用any方法:

fun Customer.hasOrderedProduct(product: Product) = orders.any{it.products.contains(product)}

然后根据他是否已经订购指定商品来做过滤:

fun Shop.getCustomersWhoOrderedProduct(product: Product): Set<Customer> {
    // Return the set of customers who ordered the specified product
    return customers.filter { it.hasOrderedProduct(product) }.toSet()
}

第二个小任务:查找某个用户所有已发货的商品中最昂贵的商品。首先过滤出已发货的订单,然后flatmap,再求最大值:

fun Customer.getMostExpensiveDeliveredProduct(): Product? {
    // Return the most expensive product among all delivered products
    // (use the Order.isDelivered flag)
    return orders.filter { it.isDelivered }.flatMap { it.products }.maxBy { it.price }
}

第三个小任务:查找指定商品被购买的次数。首先获取到客户所有已订购的商品列表,使用flatmap

fun Customer.getOrderedProducts() = orders.flatMap { it.products }

然后继续flatmap,将所有客户已经订购的商品组成一个列表,最后再count

fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int {
    // Return the number of times the given product was ordered.
    // Note: a customer may order the same product for several times.
    return customers.flatMap { it.getOrderedProducts() }.count{it == product}
}

将两个函数组合到一起:

    // Return the number of times the given product was ordered.
    // Note: a customer may order the same product for several times.
fun Shop.getNumberOfTimesProductWasOrdered(product: Product) 
= customers.flatMap { it.orders.flatMap { it.products } }.count{it == product}

24.Extensions On Collections

最后一个任务,就是实现 _24_JavaCode.doSomethingStrangeWithCollection函数的功能。所以先读懂_24_JavaCode.doSomethingStrangeWithCollection的意图:

public class _24_JavaCode extends JavaCode {
    public Collection<String> doSomethingStrangeWithCollection(Collection<String> collection) {
        Map<Integer, List<String>> groupsByLength = Maps.newHashMap();
        for (String s : collection) {
            List<String> strings = groupsByLength.get(s.length());
            if (strings == null) {
                strings = Lists.newArrayList();
                groupsByLength.put(s.length(), strings);
            }
            strings.add(s);
        }

        int maximumSizeOfGroup = 0;
        for (List<String> group : groupsByLength.values()) {
            if (group.size() > maximumSizeOfGroup) {
                maximumSizeOfGroup = group.size();
            }
        }

        for (List<String> group : groupsByLength.values()) {
            if (group.size() == maximumSizeOfGroup) {
                return group;
            }
        }
        return null;
    }
}
  • 将一个字符串集合按照长度分组,放入一个map中
  • 求出map中所有元素(String List)的最大长度
  • 根据步骤2的结果,返回map中字符串数目最多的那一组

Kotlin的实现,首先根据长度分组,然后求最大值:

fun doSomethingStrangeWithCollection(collection: Collection<String>): Collection<String>? {
    val groupsByLength = collection.groupBy { it.length }

    return groupsByLength.values.maxBy { it.size }
}

精简一下:

fun doSomethingStrangeWithCollection(collection: Collection<String>) =
        collection.groupBy { it.length }.values.maxBy { it.size }

好了,第二部分全部12个任务都在这里了。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,585评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,105评论 18 139
  • 1.Sqlite的简介 Sqlite是嵌入式的关系型数据库,其特点如下: 1)它是基于c语言开发的数据库,libs...
    _TT_阅读 1,238评论 0 1
  • 春来绿一川,石桥朱塔两依然。 壹 益生第一时间知道了向海峡对岸开放探亲的政策,那一股寻亲浪潮席卷每一个老兵的神经。...
    xhy0606阅读 279评论 1 9