這是Kotlin Koans學(xué)習(xí)筆記的第二部分佑刷,上一篇在這里。
第二部分一共12個(gè)任務(wù)耸峭,都是關(guān)于Kotlin集合操作瞒斩。
13.Introduction
Kotlin提供了一系列的to
方法將一個(gè)集合類(lèi)型轉(zhuǎn)換成另外一個(gè)集合類(lèi)型。
這一部分的第一個(gè)任務(wù)很簡(jiǎn)單啄巧,根據(jù)提示就可以完成寻歧,關(guān)于任務(wù)就不必多說(shuō)。
先說(shuō)明一下第二部分所有任務(wù)的數(shù)據(jù)模型秩仆。這一部分所有的任務(wù)都是圍繞一個(gè)商店(Shop)展開(kāi)熄求,商店有一個(gè)客戶(Customer)列表。
客戶具有姓名逗概、城市和訂單(Order)列表三個(gè)屬性弟晚。
訂單具有商品(Product)列表和是否已經(jīng)發(fā)貨兩個(gè)屬性。
商品具有名稱(chēng)和價(jià)格兩個(gè)屬性逾苫。
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
}
第二部分所有的任務(wù)都是使用擴(kuò)展函數(shù)的形式完成卿城。
14.Filter Map
這個(gè)任務(wù)主要練習(xí)使用filter
和 map
這兩個(gè)方法。
filter
filter方法返回一個(gè)包含所有滿足指定條件元素的列表铅搓。與之對(duì)應(yīng)的還有filterNot瑟押,顧名思義就是返回一個(gè)包含所有不滿足指定條件的元素列表。還有一個(gè)filterNotNull星掰,返回所有不為null的元素列表多望。
回到我們的任務(wù)要求:返回指定城市所有客戶的列表。使用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}
}
再精簡(jiǎn)一下:
// Return a list of the customers who live in the given city
fun Shop.getCustomersFrom(city: City) = customers.filter{it.city == city}
map
map就是將指定的轉(zhuǎn)換函數(shù)運(yùn)用到原始集合的每一個(gè)元素氢烘,并返回一個(gè)轉(zhuǎn)換后的集合怀偷。
任務(wù)要求返回所有客戶所在城市的Set。這里我們需要使用map 和toSet兩個(gè)方法:
// Return the set of cities the customers are from
fun Shop.getCitiesCustomersAreFrom() = customers.map { it.city }.toSet()
15.All Any and others Predicates
這個(gè)任務(wù)主要練習(xí)all
,any
和count
等幾個(gè)方法播玖。
all
第一個(gè)小任務(wù)是判斷是否所有的客戶都來(lái)自指定的城市椎工。這需要使用Kotlin庫(kù)提供的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) }
}
當(dāng)然也可以不使用all來(lái)完成,不過(guò)效率可能沒(méi)有all高维蒙,因?yàn)閍ll方法在遍歷的過(guò)程中遇到第一個(gè)不滿足條件的元素就返回結(jié)果(false):
// Return true if all customers are from the given city
fun Shop.checkAllCustomersAreFrom(city: City) = customers.filter{!it.isFrom(city)}.isEmpty()
any
第二個(gè)小任務(wù)就是查詢是否至少存在一個(gè)用戶來(lái)自指定的城市掰吕。需要使用any
方法,如果至少有一個(gè)元素滿足指定的條件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
第三個(gè)小任務(wù)計(jì)算來(lái)自指定城市的客戶數(shù)量颅痊。需要使用count
方法殖熟,count
方法返回滿足指定條件的元素?cái)?shù)量。
fun Shop.countCustomersFrom(city: City): Int {
// Return the number of customers from the given city
return customers.count{ it.city == city}
}
firstOrNull
最后一個(gè)小任務(wù)斑响,返回一個(gè)來(lái)自指定城市的客戶菱属,如果沒(méi)有就返回null。需要使用firstOrNull
方法恋捆,該方法返回第一個(gè)滿足指定條件的元素照皆,如果沒(méi)有就返回null重绷。和它相似的還有first
沸停,不過(guò)first是返回第一個(gè)滿足指定條件的元素,如果沒(méi)有元素滿足指定條件則拋出異常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
這個(gè)任務(wù)的兩個(gè)小項(xiàng)都是練習(xí)使用flatmap
方法愤钾。flatmap
方法就是針對(duì)列表中的每一項(xiàng)根據(jù)指定的方法生成一個(gè)列表,最后將所有的列表拼接成一個(gè)列表返回候醒。
第一個(gè)小項(xiàng)是要求返回一個(gè)客戶所有已訂購(gòu)的產(chǎn)品能颁,需要使用flatmap
方法,遍歷該用戶所有的訂單倒淫,然后將所有訂單的產(chǎn)品拼接起來(lái):
val Customer.orderedProducts: Set<Product> get() {
// Return all products ordered by customer
return orders.flatMap { it.products }.toSet()
}
第二個(gè)小項(xiàng)是要求返回所有至少被一個(gè)客戶訂購(gòu)過(guò)的商品集合伙菊。這個(gè)在第一個(gè)小任務(wù)的基礎(chǔ)上再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
第一個(gè)任務(wù)是返回商店中訂單數(shù)目最多的一個(gè)客戶。使用Kotlin庫(kù)提供的max
方法很好實(shí)現(xiàn)敌土。max
方法返回最大的一個(gè)元素镜硕,如果沒(méi)有元素則返回null。對(duì)于自定義的對(duì)象返干,我們可以通過(guò)maxBy
方法提供最大的評(píng)判標(biāo)準(zhǔn)兴枯,maxBy
方法返回第一個(gè)滿足指定評(píng)判標(biāo)準(zhǔn)的最大值。
fun Shop.getCustomerWithMaximumNumberOfOrders(): Customer? {
// Return a customer whose order count is the highest among all customers
return customers.maxBy { it.orders.size }
}
第二個(gè)任務(wù)是要求返回一個(gè)客戶所訂購(gòu)商品中價(jià)格最高的一個(gè)商品矩欠,使用flatmap
和 maxBy
組合:
fun Customer.getMostExpensiveOrderedProduct(): Product? {
// Return the most expensive product which has been ordered
return orders.flatMap { it.products }.maxBy { it.price }
}
當(dāng)然和max
和maxBy
對(duì)應(yīng)的還有min
和minBy
财剖,只不過(guò)返回的是最小值。
18.Sort
Kotlin庫(kù)提供了為元素排序的方法sorted
癌淮。sorted
方法會(huì)返回一個(gè)升序排序的列表躺坟,同樣可以通過(guò)sortedBy
指定排序的標(biāo)準(zhǔn),按照指定的標(biāo)準(zhǔn)排序乳蓄。
任務(wù)的要求返回一個(gè)客戶列表瞳氓,客戶的順序是根據(jù)訂單的數(shù)量由低到高排列:
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 }
}
對(duì)于排序操作同樣可以要求按照降序排序,兩個(gè)方法分別是:sortedDescending
和sortedByDescending
。
還有另外一個(gè)操作方法就是反轉(zhuǎn)reverse
匣摘。
19.Sum
任務(wù)要求計(jì)算一個(gè)客戶所有已訂購(gòu)商品的價(jià)格總和店诗。使用Kotlin的sumBy
方法就可以完成,sumBy
將集合中所有元素按照指定的函數(shù)變換以后的結(jié)果累加音榜。當(dāng)然先要將所有的訂單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
方法返回一個(gè)根據(jù)指定條件分組好的map庞瘸。任務(wù)要求是返回來(lái)自每一個(gè)城市的客戶的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
任務(wù)要求返回所有未發(fā)貨訂單數(shù)目多于已發(fā)貨訂單的用戶。
任務(wù)的范例中展示了怎么使用partition
方法赠叼。partition
方法會(huì)將原始的集合分成一對(duì)集合擦囊,這一對(duì)集合中第一個(gè)是滿足指定條件的元素集合,第二個(gè)是不滿足指定條件的集合嘴办。
這里我們先給Customer定義一個(gè)函數(shù)瞬场,判斷該用戶是否屬于未發(fā)貨訂單大于已發(fā)貨訂單,處理方法就是使用partition
方法將所有的訂單分割涧郊,分割的條件就是該訂單已經(jīng)發(fā)貨:
fun Customer.isMoreUndeliveredOrdersThanDelivered(): Boolean{
val(delivered, undelivered) = orders.partition { it.isDelivered }
return delivered.size < undelivered.size
}
然后再對(duì)所有的客戶進(jìn)行篩選:
fun Shop.getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set<Customer> {
// Return customers who have more undelivered orders than delivered
return customers.filter { it.isMoreUndeliveredOrdersThanDelivered() }.toSet()
}
將這兩個(gè)函數(shù)寫(xiě)到一起:
fun Shop.getCustomersWithMoreUndeliveredOrdersThanDelivered() =
customers.filter {
val(delivered, undelivered) = it.orders.partition { it.isDelivered }
undelivered.size > delivered.size
}.toSet()
22.Fold
任務(wù)要求返回每一個(gè)顧客都購(gòu)買(mǎi)過(guò)的商品集合贯被。
先來(lái)看一下fold
方法,fold
方法就是給定一個(gè)初始值妆艘,然后通過(guò)迭代對(duì)集合中的每一個(gè)元素執(zhí)行指定的操作并將操作的結(jié)果累加彤灶。注意操作函數(shù)的兩個(gè)參數(shù)分別是累加結(jié)果和集合的元素。
直接看fold函數(shù)的定義吧:
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
}
回到我們的任務(wù)批旺,使用fold函數(shù)幌陕,由于任務(wù)要求返回所有客戶都已經(jīng)訂購(gòu)的商品,所以初始值設(shè)置為所有已經(jīng)訂購(gòu)的商品汽煮,然后用這個(gè)初始值去和每一個(gè)客戶已訂購(gòu)的商品求交集搏熄,最終的結(jié)果就是所有用戶都已經(jīng)購(gòu)買(mǎi)過(guò)的商品:
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
終于快結(jié)束這一部分的任務(wù)了暇赤。這一部分包括幾個(gè)小任務(wù)心例,完成任務(wù)需要用到前面所練習(xí)的各種方法的組合。
來(lái)看第一個(gè)小任務(wù):返回所有購(gòu)買(mǎi)了指定商品的客戶列表翎卓。首先給Customer擴(kuò)展一個(gè)方法契邀,判斷他是否已經(jīng)訂購(gòu)指定的商品,使用any
方法:
fun Customer.hasOrderedProduct(product: Product) = orders.any{it.products.contains(product)}
然后根據(jù)他是否已經(jīng)訂購(gòu)指定商品來(lái)做過(guò)濾:
fun Shop.getCustomersWhoOrderedProduct(product: Product): Set<Customer> {
// Return the set of customers who ordered the specified product
return customers.filter { it.hasOrderedProduct(product) }.toSet()
}
第二個(gè)小任務(wù):查找某個(gè)用戶所有已發(fā)貨的商品中最昂貴的商品失暴。首先過(guò)濾出已發(fā)貨的訂單坯门,然后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 }
}
第三個(gè)小任務(wù):查找指定商品被購(gòu)買(mǎi)的次數(shù)逗扒。首先獲取到客戶所有已訂購(gòu)的商品列表古戴,使用flatmap
:
fun Customer.getOrderedProducts() = orders.flatMap { it.products }
然后繼續(xù)flatmap
,將所有客戶已經(jīng)訂購(gòu)的商品組成一個(gè)列表矩肩,最后再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}
}
將兩個(gè)函數(shù)組合到一起:
// 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
最后一個(gè)任務(wù)现恼,就是實(shí)現(xiàn) _24_JavaCode.doSomethingStrangeWithCollection
函數(shù)的功能。所以先讀懂_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;
}
}
- 將一個(gè)字符串集合按照長(zhǎng)度分組,放入一個(gè)map中
- 求出map中所有元素(String List)的最大長(zhǎng)度
- 根據(jù)步驟2的結(jié)果叉袍,返回map中字符串?dāng)?shù)目最多的那一組
Kotlin的實(shí)現(xiàn),首先根據(jù)長(zhǎng)度分組始锚,然后求最大值:
fun doSomethingStrangeWithCollection(collection: Collection<String>): Collection<String>? {
val groupsByLength = collection.groupBy { it.length }
return groupsByLength.values.maxBy { it.size }
}
精簡(jiǎn)一下:
fun doSomethingStrangeWithCollection(collection: Collection<String>) =
collection.groupBy { it.length }.values.maxBy { it.size }
好了,第二部分全部12個(gè)任務(wù)都在這里了喳逛。