Kotlin在Android上的運用(四)
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 }