Google Code Prettify

2020年11月7日 星期六

Java Stream: Collections

這一篇要整理的是 import static java.util.stream.Collectors.* 配合 stream 所進行的操作,它的作用就像 reduce 一樣,但是提供了更多高可讀性的操作,看一下實作 … 先定義資料如下:
@Data
public class Product implements Serializable {
	private String name;		//商品名稱
	private String brand;		//品牌
	private String kind;		//分類
	private int price;		//價格
	private int count;		//數量
	
	public Product() { }
	
	public Product(String name, String brand, String kind, int price, int count) {
		this.name = name;
		this.brand = brand;
		this.kind = kind;
		this.price = price;
		this.count = count;
	}
}

List<Product> products = Arrays.asList(
	//精油
	new Product("橙花純精油", "怡森氏", "精油", 1179, 10),
	new Product("快樂鼠尾草純精油", "怡森氏", "精油", 879, 8),
	new Product("純天然玫瑰純精油", "怡森氏", "精油", 1569, 5),
	new Product("薰衣草純精油", "奧地利KP", "精油", 680, 12),
	new Product("真正薰衣草", "ANDZEN", "精油", 288, 25),
	//鍵盤
	new Product("G512 RGB 機械遊戲鍵盤(青軸)", "羅技", "鍵盤", 3290, 2),
	new Product("黑寡婦蜘蛛幻彩版鍵盤", "Razer", "鍵盤", 2190, 1),
	new Product("Cynosa Lite 薩諾狼蛛Lite版鍵盤", "Razer", "鍵盤", 990, 7),
	new Product("Vigor GK50 Low Profile 短軸機械式鍵盤", "MSI微星", "鍵盤", 2990, 13),
	new Product("Vigor GK70 Cherry MX RGB機械電競鍵盤 (紅軸版)", "MSI微星", "鍵盤", 2999, 6),
	new Product("Vigor GK60 CL TC 電競鍵盤", "MSI微星", "鍵盤", 3290, 3),
	//滑鼠
	new Product("MX Master 2S 無線滑鼠", "羅技", "滑鼠", 1880, 25),
	new Product("MX Master 3 無線滑鼠", "羅技", "滑鼠", 3490, 18),
	new Product("M590 多工靜音無線滑鼠", "羅技", "滑鼠", 849, 30)
);
假設有家商店,有如上述的貨品,三類 (精油、鍵盤、滑鼠) 及多種品牌的商品,現在開始進行資料的操作:
  1. 所有商品按種類分組
  2. Map<String, List<Product>> groupByKind = products.stream()
    		.collect(groupingBy(Product::getKind));
    		
    groupByKind.forEach((k, v) -> {
    		log.info("Kind: " + k);
    		v.forEach(p -> log.info("  product: " + p.getName()));
    	});
    
    Kind: 滑鼠
      product: MX Master 2S 無線滑鼠
      product: MX Master 3 無線滑鼠
      product: M590 多工靜音無線滑鼠
    Kind: 精油
      product: 橙花純精油
      product: 快樂鼠尾草純精油
      product: 純天然玫瑰純精油
      product: 薰衣草純精油
      product: 真正薰衣草
    Kind: 鍵盤
      product: G512 RGB 機械遊戲鍵盤(青軸)
      product: 黑寡婦蜘蛛幻彩版鍵盤
      product: Cynosa Lite 薩諾狼蛛Lite版鍵盤
      product: Vigor GK50 Low Profile 短軸機械式鍵盤
      product: Vigor GK70 Cherry MX RGB機械電競鍵盤 (紅軸版)
      product: Vigor GK60 CL TC 電競鍵盤
    用 collect  可以展現出 functional programming 的優勢,從指令就可以看的出來,我們的需求是什麼,至於程式實際怎麼執行,不勞我們擔心,Java 會幫我們找出辦法。上述 groupingBy 指令指出以 kind (商品種類) 分組,匯整在 map 裡,後面我們將它依序輸出。
  3. 所有商品按種類分組後再依品牌分組
  4. Map<String, Map<String, List<Product>>> result = products.stream()
    	.collect(groupingBy(Product::getKind,
    		groupingBy(Product::getBrand)
    	));
    		
    result.forEach((kind, p1) -> {
    	log.info("Kind: " + kind);
    	p1.forEach((brand, p2) -> {
    		log.info("  Brand: " + brand);
    		p2.forEach(p3 -> log.info("    Product: " + p3.getName()));
    	});
    });
    
    Kind: 滑鼠
      Brand: 羅技
        Product: MX Master 2S 無線滑鼠
        Product: MX Master 3 無線滑鼠
        Product: M590 多工靜音無線滑鼠
    Kind: 精油
      Brand: 怡森氏
        Product: 橙花純精油
        Product: 快樂鼠尾草純精油
        Product: 純天然玫瑰純精油
      Brand: 奧地利KP
        Product: 薰衣草純精油
        Brand: ANDZEN
        Product: 真正薰衣草
    Kind: 鍵盤
      Brand: 羅技
        Product: G512 RGB 機械遊戲鍵盤(青軸)
      Brand: Razer
        Product: 黑寡婦蜘蛛幻彩版鍵盤
        Product: Cynosa Lite 薩諾狼蛛Lite版鍵盤
      Brand: MSI微星
        Product: Vigor GK50 Low Profile 短軸機械式鍵盤
        Product: Vigor GK70 Cherry MX RGB機械電競鍵盤 (紅軸版)
        Product: Vigor GK60 CL TC 電競鍵盤
    上面語法,我們要多層次分組,也是很明白的"說出"我們的需求,先以種類分組再以品牌分組,就得到我們要的結果了。
  5. 計算各種類中各品牌的商品樣式數量
  6. Map<String, Map<String, Long>> result = products.stream()
    	.collect(groupingBy(Product::getKind,
    		groupingBy(Product::getBrand, counting())
    	));
    
    result.forEach((kind, p1) -> {
    	log.info("Kind: " + kind);
    	p1.forEach((brand, count) -> {
    		log.info("  Brand: " + brand + " = " + count);
    	});
    });
    
    Kind: 滑鼠
      Brand: 羅技 = 3
    Kind: 精油
      Brand: 怡森氏 = 3
      Brand: 奧地利KP = 1
      Brand: ANDZEN = 1
    Kind: 鍵盤
      Brand: 羅技 = 1
      Brand: Razer = 2
      Brand: MSI微星 = 3
    利用 Collections 中的 counting() 就可以計算出數量了!
  7. 最貴的滑鼠
  8. Comparator<Product> priceComparator = Comparator.comparingInt(Product::getPrice);
    		
    Optional<Product> mouse = products.stream()
    	.filter(p -> p.getKind().equals("滑鼠"))
    	.collect(maxBy(priceComparator));
    		
    if (mouse.isPresent()) {
    	log.info(mouse.get().getName());
    }
    
    MX Master 3 無線滑鼠
    先定義 Comparator 讓 collect 的 maxBy 知道比較的方式,這裡就是比價格,在 stream 中配合 filter 找出滑鼠比一下價格,就找出最貴的滑鼠了! 這裡要注意的是,找出來的結果為 Optional, 因為有可能根本沒有滑鼠!
  9. 全部的鍵盤有幾個?
  10. int total = products.stream()
    	.filter(p -> p.getKind().equals("鍵盤"))
    	.collect(summingInt(Product::getCount));
    		
    log.info("鍵盤數量: " + total);
    
    鍵盤數量: 32
    非常簡單的,過濾出鍵盤後,用 summingInt 將數量加總。
  11. 全部的商品以種類先排序,再以分號分隔列出
  12. Comparator<Product> kindComparator = Comparator.comparing(Product::getKind);
    		
    String s = products.stream()
    	.sorted(kindComparator)
    	.map(Product::getName)
    	.collect(joining(","));
    		
    log.info(s);
    
    MX Master 2S 無線滑鼠,MX Master 3 無線滑鼠,M590 多工靜音無線滑鼠,橙花純精油,快樂鼠尾草純精油,純天然玫瑰純精油,薰衣草純精油,真正薰衣草,G512 RGB 機械遊戲鍵盤(青軸),黑寡婦蜘蛛幻彩版鍵盤,Cynosa Lite 薩諾狼蛛Lite版鍵盤,Vigor GK50 Low Profile 短軸機械式鍵盤,Vigor GK70 Cherry MX RGB機械電競鍵盤 (紅軸版),Vigor GK60 CL TC 電競鍵盤
    實務上資料常常要以 csv 檔產出,這時候可以用 joining,這是連接字串的 method,這裡在列出商品時,用 joining 連接並於中間加上逗點。
  13. 將精油分成怡森氏和非怡森氏兩組
  14. Map<Boolean, List<Product>> result = products.stream()
    	.filter(p -> p.getKind().equals("精油"))
    	.collect(partitioningBy(p -> p.getBrand().equals("怡森氏")));
    		
    result.forEach((k, v) -> {
    	log.info(k ? "怡森氏" : "非怡森氏");
    	v.forEach(p -> log.info("  " + p.getName()));
    });
    
    非怡森氏
      薰衣草純精油
      真正薰衣草
    怡森氏
      橙花純精油
      快樂鼠尾草純精油
      純天然玫瑰純精油
    分組除了可以用 groupingBy, 還有另一個選擇 -- partitioningBy,partitioningBy 要帶入的參數是 Predicate,只能分 true、false 兩組,但是效率比較高,如果剛好適用,partitioningBy 會是更好的選擇。
  15. 找出鍵盤中,微星與非微星品牌中最貴的
  16. Map<Boolean, Product> result = products.stream()
    	.filter(p -> p.getKind().equals("鍵盤"))
    	.collect(
    		partitioningBy(
    			p -> p.getBrand().equals("MSI微星"),
    			collectingAndThen(
    				maxBy(Comparator.comparingInt(Product::getPrice)), 
    				Optional::get)
    			)
    		);
    		
    result.forEach((k, v) -> {
    	log.info(k ? "MSI微星" : "非MSI微星");
    	log.info("  Product: " + v.getName() + ": " + v.getPrice());
    });
    
    非MSI微星
      Product: G512 RGB 機械遊戲鍵盤(青軸): 3290
    MSI微星
      Product: Vigor GK60 CL TC 電競鍵盤: 3290
    partitioningBy 還有個優點就是,在分組後,還可以使用 collectingAndThen 做進一步的處理,上面在分組後,我們在該組裡找出最貴的鍵盤。

沒有留言:

張貼留言