從 Java 中的 HashMap 中刪除重複值
1. 概述
HashMap
是Java編程中存儲和管理鍵值對的強大工具。然而,有時我們的數據可能包含重複的值。
在本教程中,我們將探索從HashMap.
2.問題介紹
HashMap
允許多個鍵具有相同的值,在某些場景下不可避免地會出現重複。讓我們看一個例子:
Map<String, String> initDevMap() {
Map<String, String> devMap = new HashMap<>();
devMap.put("Tom", "Linux");
devMap.put("Kent", "Linux");
devMap.put("Bob", "MacOS");
devMap.put("Eric", "MacOS");
devMap.put("Peter", "Windows");
devMap.put("Saajan", "Windows");
devMap.put("Jan", "Windows");
devMap.put("Kevin", "FreeBSD");
return devMap;
}
在上面的示例中, initDevMap()
方法初始化一個新的HashMap
,其中包含開發人員名稱和他們使用的操作系統 (OS) 的關聯。假設我們要從映射中刪除重複的操作系統名稱。因此,我們將得到一張只有四個條目的地圖。
一種簡單的方法可能是迭代映射,跟踪值,並在發現重複項時刪除映射條目。此外,我們可能希望使用Iterator
遍歷映射並刪除條目以避免ConcurrentModificationException
。
但是,在本教程中,我們將學習一種不同的方法。此外,我們還將介紹兩種重複數據刪除場景:
- 只要結果僅包含唯一值,我們就不關心刪除哪些條目。
- 對於重複數據刪除,我們應該遵循特定的規則,例如保留“AZ排序”中的第一個/最後一個鍵、保留最長/最短的名稱等等。
3. 反轉地圖兩次
我們知道**HashMap
不允許有重複的鍵。因此,如果我們將輸入映射從“ developer -> OS
”反轉為“OS -> developer
”,則相同的操作系統名稱將被刪除。**然後,我們可以將地圖反轉以獲得最終結果。
接下來,讓我們實現這個“反轉兩次”的想法並檢查它是否按預期工作:
Map<String, String> devMap = initDevMap();
Map<String, String> tmpReverseMap = new HashMap<>();
Map<String, String> result = new HashMap<>();
for (String name : devMap.keySet()) {
tmpReverseMap.put(devMap.get(name), name);
}
for (String os : tmpReverseMap.keySet()) {
result.put(tmpReverseMap.get(os), os);
}
assertThat(result.values()).hasSize(4)
.containsExactlyInAnyOrder("Windows", "MacOS", "Linux", "FreeBSD");
正如我們所看到的,我們使用兩個for
循環來反轉映射兩次。然後,我們使用方便的assertj 庫驗證結果。
4. 使用流API
如果我們使用 Java 8 或更高版本,我們可以使用 Stream API 實現“反轉兩次”方法:
Map<String, String> devMap = initDevMap();
Map<String, String> result = devMap.entrySet()
.stream()
.collect(toMap(Map.Entry::getValue, Map.Entry::getKey, (keyInMap, keyNew) -> keyInMap))
.entrySet()
.stream()
.collect(toMap(Map.Entry::getValue, Map.Entry::getKey));
assertThat(result.values()).hasSize(4)
.containsExactlyInAnyOrder("Windows", "MacOS", "Linux", "FreeBSD");
基於流的實現比兩個for
循環更流暢。值得注意的是,我們使用了Collector.toMap()
方法進行反轉操作。第一個反轉旨在刪除重複值。由於我們有重複的值,因此在鍵值反轉之後,我們會出現鍵(操作系統名稱)衝突。因此,我們在那裡放置了一個合併函數來處理鍵衝突。另外,由於我們不關心重複數據刪除後映射中剩下哪個條目,因此我們使合併函數返回映射中已有的鍵。換句話說,我們丟棄稍後出現的重複值。
5. 適應特定的重複數據刪除要求。
由於**HashMap
不保證條目的順序,所以使用目前的解決方案,我們無法決定去重後映射中還剩下哪個條目**。
不過,我們可以通過調整合併功能來滿足不同的需求。接下來我們看一個例子。
假設我們有一個新需求:如果多個開發人員使用相同的操作系統,則名稱最長的開發人員應保留在映射中。因此,預期結果如下所示:
Map<String, String> expected = ImmutableMap.of(
"Eric", "MacOS",
"Kent", "Linux",
"Saajan", "Windows",
"Kevin", "FreeBSD");
接下來,讓我們看看如何實現這一目標:
Map<String, String> result = devMap.entrySet()
.stream()
.collect(toMap(Map.Entry::getValue, Map.Entry::getKey, (k1, k2) -> k1.length() > k2.length() ? k1 : k2))
.entrySet()
.stream()
.collect(toMap(Map.Entry::getValue, Map.Entry::getKey));
assertThat(result).hasSize(4)
.isEqualTo(expected);
正如我們所看到的,我們採用了之前基於流的實現,只修改了toMap()
中的合併函數,以在發生衝突時返回更長的鍵。
六,結論
在本文中,我們學習了“反轉兩次”方法來刪除重複的HashMap
值。此外,我們還了解瞭如何修改toMap()'s
合併函數以適應特定的重複數據刪除要求。
與往常一樣,示例的完整源代碼可在 GitHub 上獲取。