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

文章推薦指數: 80 %
投票人數:10人

【Guava 教學】(7)Multiset、Multimap 與BiMap. 如果有個清單,想要取得清單中不重複的元素,最快的方式就是使用JDK 中的 Set 。

例如: 回GuavaTutorial 如果有個清單,想要取得清單中不重複的元素,最快的方式就是使用JDK中的Set。

例如: Listwords=Arrays.asList("one","two","three","one","three"); SetwordSet=newHashSet<>(words); out.println(wordSet);//[two,one,three] 如果不單只是想取得清單中不重複元素,也想要知道清單中重複元素個數,那麼你也許會這麼撰寫: Listwords=Arrays.asList("one","two","three","one","three"); Mapcounts=newHashMap<>(); for(Stringword:words){ Integercount=counts.get(word); counts.put(word,count==null?1:count+1); } out.println(counts);//{two=1,one=2,three=2} 如果不單只是計數,在後續迭代時也想要取得重複元素呢?你可能會如此撰寫: Listwords=Arrays.asList("one","two","three","one","three"); Map>wordBag=newHashMap<>(); for(Stringword:words){ ListrepeatedWds=wordBag.get(word); if(repeatedWds==null){ repeatedWds=newArrayList<>(); wordBag.put(word,repeatedWds); } repeatedWds.add(word); } 透過repeatedWds的entrySet取得Map.Entry>,就可以迭代所有元素。

實際上,因為元素相同,使用List逐一保留重複的元素沒有必要,可以直接使用先前第二個程式片段中的 Map的count。

例如: for(Map.Entryentry:counts.entrySet()){ intcount=entry.getValue(); for(intc=0;cwords=Arrays.asList("one","two","three","one","three"); MultisetwordBag=HashMultiset.create(words); out.println(wordBag);//[two,onex2,threex2] for(Stringword:wordBag){ 用迭代的word做些事... } 多重集合(Multiset)是集合(Set)概念的推廣(Generalization),集合中相同元素只能出現一次,元素在集合中只是有或無兩個屬性,多重集合則允許相同元素出現多次,元素在集合中具有重複次數(Occurrence)的概念,多重集合又稱為集合包(Bag)。

Guava的 Multiset實作是基於Map,像是 HashMultiset、TreeMultiset等,不過 Multiset不是Map,Multiset介面直接繼承了Collection,跟Set也沒有任何繼承關係。

在Multiset的實作中加入的物件若重複,並不會再加以收集,而是利用一個Count物件來記錄物件重複次數。

在迭代時,會根據Count的重複次數資訊來物件要迭代幾次,size方法傳回的是所有物件重複次數之總和。

Multiset繼承自Collection,並擴充了一些操作重複次數的方法,像是 add(E,int)、remove(E,int)、setCount(E,int)等,Multiset不單只是元素有或無的集合概念(這是Collection的contains職責),因此對於詢問元素重複次數,提供了 count(E)方法。

在資料結構上,Multiset實作多重集合時,僅實作了對相同(Identical)物件加以計數的概念,實際上還有另一個應用場合,某個物件符合某個相等(Equivalent)定義時,確實地將物件儲存下來。

例如Letter物件具有zipCode及其它屬性時,若zipCode值相同,則zipCode與Letter資訊都要成對儲存下來: Listletters=...; Map>letterBag=newHashMap<>(); for(Letterletter:letters){ IntegerzipCode=letter.getZipCode(); ListsameZipLetters=letterBag.get(zipCode); if(sameZipLetters==null){ sameZipLetters=newArrayList<>(); letterBag.put(zipCode,sameZipLetters); } sameZipLetters.add(letter); } //{106=[Letter(106,adr1),Letter(106,adr2)],804=[Letter(804,adr3),Letter(804,..)]} out.println(letterBag); 對這樣的需求,單純使用Guava的Multiset沒辦法達成,Guava定義這類的需求可使用Multimap來解決。

Listletters=...; MultimapletterBag=HashMultimap.create(); for(Letterletter:letters){ letterBag.put(letter.getZipCode(),letter); } //{106=[Letter(106,adr1),Letter(106,adr2)],804=[Letter(804,adr3),Letter(804,..)]} out.println(letterBag); JDK的Map一個鍵只會有一個對應值,put時給定相同的鍵,則先前的值會被覆蓋;Guava的Multimap一個鍵允許有多個對應值,put時給定相同的鍵,先前的值不會被覆蓋,而是收集在鍵對應的Collection中,get方法指定鍵時,傳回的會是鍵對應的Collection。

選用Multimap實作時要考慮的是,鍵對應的Collection之行為,以先前第三個程式片段目的而言,如果word重複了,也想要分別收集起來,那麼可以使用ArrayListMultimap。

例如: Listwords=Arrays.asList("one","two","three","one","three"); MultimapwordBag=ArrayListMultimap.create(); for(Stringword:words){ wordBag.put(word,word); } 如此重複的字串,才會被個別收集起來,如果你選用了HashMultimap,那麼值的部份是使用HashSet來儲存,結果就是重複的字串並不會被個別收集起來,最後的行為又有點像是Multiset了。

要注意的是,Multimap並不是Map,它是一個獨立定義的介面,沒有繼承自任何父介面。

倒是Guava的BiMap就真的是Map了,它是Map的子介面,BiMap是指雙向對應(Bidirectionalmap),鍵可以對應至值,值也可以對應至鍵。

舉例而言,如果沒有BiMap,而只是有個Map,現在有個值要找出它對應的鍵,那麼可能會寫成: publicstaticIntegergetId(Mapusers,Stringname){ for(Map.EntryuserEntry:users.entrySet()){ if(name.equals(userEntry.getValue())){ returnuserEntry.getKey(); } } returnnull; } 使用BiMap的話,你可以寫成: publicstaticIntegergetId(Mapusers,Stringname){ BiMapuserBiMap=HashBiMap.create(users); returnuserBiMap.inverse().get(name); } 為了要能夠達到從值取鍵的這個目的,BiMap的值不能是重複的,因而也成了BiMap的一個作用,確保值的獨特性,如果建立BiMap時給的Map值有重複,或者是使用BiMap時加入的值有重複,就會引發IllegalArgumentException。



請為這篇文章評分?