2024/3/25

Guava Collection Multiset Multimap

New Collection Type

ref: NewCollectionTypesExplained · google/guava Wiki · GitHub

ref: 【Guava 教學】(7)Multiset、Multimap 與 BiMap

Multiset

多重集合(Multiset)是集合(Set)概念的推廣(Generalization),Set 裡面相同元素只能出現一次,Multiet 多重集合則允許相同元素出現多次,元素在集合中有重複次數(Occurrence)的概念,多重集合又稱為 Bag。

Guava 提供 Multiset,可方便地計算每一個元素發生的次數,且能 iterate 每一個元素。換句話說,Multiset 有兩個功能

  1. 類似沒有順序的 ArrayList

    • add(E) 新增一個元素

    • iterator() 迭代每一個元素

    • size()

  2. 類似 Map<E, Integer>,儲存元素及數量

    • count(Object) 計算某個元素的數量

    • entrySet() 回傳 Set<Multiset.Entry> ,類似 Map 的 entrySet

    • elementSet() 回傳 Set ,包含所有不同的 elements,類似 Map 的 keySet()

    @Test
    public void jdk_word_count() {
        List<String> words = Arrays.asList("one", "two", "three", "one", "three");
        Map<String, Integer> counts = new HashMap<>();
        for(String word : words) {
            Integer count = counts.get(word);
            if (count == null) {
                counts.put(word, 1);
            } else {
                counts.put(word, count + 1);
            }
        }
        System.out.println(counts); // {two=1, one=2, three=2}

        Map<String, List<String>> wordBag = new HashMap<>();
        for(String word : words) {
            List<String> repeatedWds = wordBag.get(word);
            if(repeatedWds == null) {
                repeatedWds = new ArrayList<>();
                wordBag.put(word, repeatedWds);
            }
            repeatedWds.add(word);
        }
        // {one=[one, one], two=[two], three=[three, three]}
        System.out.println(wordBag);
    }

    @Test
    public void guava_multiset_word_count() {
        List<String> words = Arrays.asList("one", "two", "three", "one", "three");
        Multiset<String> wordBag = HashMultiset.create(words);

        System.out.println("");
        // [two, one x 2, three x 2]
        System.out.println(wordBag);

//        Element: one, Occurrence(s): 2
//        Element: two, Occurrence(s): 1
//        Element: three, Occurrence(s): 2
        for (Multiset.Entry<String> entry : wordBag.entrySet())
        {
            System.out.println("Element: "+entry.getElement() +", Occurrence(s): " + entry.getCount());
        }
        for(String word : wordBag) {
            // 可直接使用 words 裡面的每一個元素
            System.out.print(word);
        }
    }

Guava 提供的 Multiset implementation,可對應到 JDK map implementation

Map Corresponding Multiset Supports null elements
HashMap HashMultiset Yes
TreeMap TreeMultiset Yes
LinkedHashMap LinkedHashMultiset Yes
ConcurrentHashMap ConcurrentHashMultiset No
ImmutableMap ImmutableMultiset No

TreeMultiset 有實作 SortedMultiset 介面,該 interface 可快速取得 sub-multiset,ex:

    @Test
    public void guava_submultiset_word_count() {
        List<String> words = Arrays.asList("one", "two", "three", "one", "three");
        TreeMultiset<String> wordBag = TreeMultiset.create(words);

        SortedMultiset<String> subWordBag = wordBag.subMultiset("one", BoundType.CLOSED, "two", BoundType.OPEN);
        System.out.println("");
        // [two, one x 2, three x 2]
        System.out.println(wordBag);
        // [one x 2, three x 2]
        // 根據 String 的排序,取得比 "two" 比較後還小的那些元素集合
        // TreeMultiset 裡面的元素都有排序過
        System.out.println(subWordBag);

        //Element: one, Occurrence(s): 2
        //Element: three, Occurrence(s): 2
        for (SortedMultiset.Entry<String> entry : subWordBag.entrySet())
        {
            System.out.println("Element: "+entry.getElement() +", Occurrence(s): " + entry.getCount());
        }
        for(String word : subWordBag) {
            // 可直接使用 subWordBag 裡面的每一個元素
            // one one three three
            System.out.print(word+" ");
        }
    }

Multimap

ref: Guava Multimap類 - Guava教學

在 java 只有提供 Map<K, List<V>> or Map<K, Set<V>>

在 Guava 以 Multimap 提供 keys 對應到任意資料結構的一個介面

Multimap 有一個 asMap() ,會回傳 Map<K, Collection<V>>

比較常用的是 ListMultimap / SetMutimap,比較少直接使用 Multimap interface


Construction & Modify

雖然可以直接用 ListMultimap / SetMutimap 的 create() 產生 Multimap,但比較建議使用 MultimapBuilder 產生

    @Test
    public void construction() {
        List<String> tolist = Arrays.asList("to1@domain");
        List<String> cclist = Arrays.asList("cc1@domain", "cc2@domain");
        List<String> bcclist = Arrays.asList("bcc1@domain", "bcc2@domain");

        // 使用 MultimapBuilder
        // creates a ListMultimap with tree keys and array list values
        ListMultimap<String, List<String>> treeListMultimap =
                MultimapBuilder.treeKeys().arrayListValues().build();
        treeListMultimap.put("to", tolist);
        treeListMultimap.put("cc", cclist);
        treeListMultimap.put("bcc", bcclist);
        System.out.println("treeListMultimap:");
        // {bcc=[[bcc1@domain, bcc2@domain]], cc=[[cc1@domain, cc2@domain]], to=[[to1@domain]]}
        System.out.println(treeListMultimap);

        // 直接使用 create()
        Multimap<String,List<String>> treeListMultimap2 = ArrayListMultimap.create();
        treeListMultimap2.put("to", tolist);
        treeListMultimap2.put("cc", cclist);
        treeListMultimap2.put("bcc", bcclist);

        // creates a SetMultimap with hash keys and enum set values
        SetMultimap<String, Grade> hashEnumMultimap =
                MultimapBuilder.hashKeys().enumSetValues(Grade.class).build();
        hashEnumMultimap.put("Alice", Grade.A);
        hashEnumMultimap.put("Bruce", Grade.B);

        System.out.println("hashEnumMultimap:");
        // {Bruce=[B], Alice=[A]}
        System.out.println(hashEnumMultimap);
    }
    public enum Grade {
        A, B, C, D, F, INCOMPLETE
    }

    @Test
    public void modify() {
        System.out.println("");

        List<String> tolist = Arrays.asList("to1@domain");
        List<String> cclist = Arrays.asList("cc1@domain", "cc2@domain");
        List<String> bcclist = Arrays.asList("bcc1@domain", "bcc2@domain");

        // 使用 MultimapBuilder
        // creates a ListMultimap with tree keys and array list values
        ListMultimap<String, List<String>> treeListMultimap =
                MultimapBuilder.treeKeys().arrayListValues().build();
        treeListMultimap.put("to", tolist);
        treeListMultimap.put("cc", cclist);
        treeListMultimap.put("bcc", bcclist);
        // treeListMultimap: {bcc=[[bcc1@domain, bcc2@domain]], cc=[[cc1@domain, cc2@domain]], to=[[to1@domain]]}
        System.out.println("treeListMultimap: "+ treeListMultimap);
        treeListMultimap.removeAll("cc");

        // treeListMultimap2 after removeAll: {bcc=[[bcc1@domain, bcc2@domain]], to=[[to1@domain]]}
        System.out.println("treeListMultimap2 after removeAll: "+ treeListMultimap);

        List<String> tolist2 = Arrays.asList("to2@domain");
        treeListMultimap.put("to", tolist2);
        // treeListMultimap2 put new key/value: {bcc=[[bcc1@domain, bcc2@domain]], to=[[to1@domain], [to2@domain]]}
        System.out.println("treeListMultimap2 put new key/value: "+ treeListMultimap);

        treeListMultimap.clear();
        treeListMultimap.put("to", tolist2);
        // treeListMultimap2: {to=[[to2@domain]]}
        System.out.println("treeListMultimap2: "+ treeListMultimap);
    }

views

    @Test
    public void views() {
        System.out.println("");

        List<String> tolist = Arrays.asList("to1@domain");
        List<String> cclist = Arrays.asList("cc1@domain", "cc2@domain");
        List<String> bcclist = Arrays.asList("bcc1@domain", "bcc2@domain");

        // 使用 MultimapBuilder
        // creates a ListMultimap with tree keys and array list values
        ListMultimap<String, List<String>> treeListMultimap =
                MultimapBuilder.treeKeys().arrayListValues().build();
        treeListMultimap.put("to", tolist);
        treeListMultimap.put("cc", cclist);
        treeListMultimap.put("bcc", bcclist);
        // treeListMultimap: {bcc=[[bcc1@domain, bcc2@domain]], cc=[[cc1@domain, cc2@domain]], to=[[to1@domain]]}
        System.out.println("treeListMultimap: "+ treeListMultimap);

        // asMap:  views any Multimap<K, V> as a Map<K, Collection<V>>
        Map<String, Collection<List<String>>> tomap = treeListMultimap.asMap();
        // asMap: {bcc=[[bcc1@domain, bcc2@domain]], cc=[[cc1@domain, cc2@domain]], to=[[to1@domain]]}
        System.out.println("asMap: "+ tomap);

        // entries():  views the Collection<Map.Entry<K, V>>
        // entries: [bcc=[bcc1@domain, bcc2@domain], cc=[cc1@domain, cc2@domain], to=[to1@domain]]
        Collection<Map.Entry<String, List<String>>> entries = treeListMultimap.entries();
        System.out.println("entries: "+ entries);

        // keySet: views the distinct keys in the Multimap as a Set
        // keySet: [bcc, cc, to]
        Set<String> keySet = treeListMultimap.keySet();
        System.out.println("keySet: "+ keySet);

        // keys: views the keys of the Multimap as a Multiset
        // keysMultiset: [bcc, cc, to]
        Multiset<String> keysMultiset = treeListMultimap.keys();
        System.out.println("keysMultiset: "+ keysMultiset);

        // values: views all the values in the Multimap as a "flattened" Collection<V>
        // values: [[bcc1@domain, bcc2@domain], [cc1@domain, cc2@domain], [to1@domain]]
        Collection<List<String>> values = treeListMultimap.values();
        System.out.println("values: "+ values);
    }

implementations

建議使用  MultimapBuilder 不是直接 create()

Implementation Keys behave like... Values behave like..
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap * LinkedHashMap``* LinkedList``*
LinkedHashMultimap** LinkedHashMap LinkedHashSet
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet

沒有留言:

張貼留言