这是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
这个任务主要练习使用filter
和 map
这两个方法。
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
,any
和count
等几个方法。
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 }
}
第二个任务是要求返回一个客户所订购商品中价格最高的一个商品,使用flatmap
和 maxBy
组合:
fun Customer.getMostExpensiveOrderedProduct(): Product? {
// Return the most expensive product which has been ordered
return orders.flatMap { it.products }.maxBy { it.price }
}
当然和max
和maxBy
对应的还有min
和minBy
,只不过返回的是最小值。
18.Sort
Kotlin库提供了为元素排序的方法sorted
。sorted
方法会返回一个升序排序的列表,同样可以通过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 }
}
对于排序操作同样可以要求按照降序排序,两个方法分别是:sortedDescending
和sortedByDescending
。
还有另外一个操作方法就是反转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个任务都在这里了。