[英]Collection removeAll ignoring case?
好的,這是我的問題。 我必須使用HashSet
,我使用removeAll
方法刪除一個集合中存在的值。
在調用方法之前,我顯然將值添加到Set
。 我在添加之前在每個String
上調用.toUpperCase()
,因為兩個列表中的值都是不同的情況。 這個案子沒有押韻或理由。
一旦我調用removeAll
,我需要將原始案例返回給Set
中剩下的值。 有沒有一種有效的方法可以在不運行原始列表和使用CompareToIgnoreCase
情況下執行此操作?
例:
列表1:
"BOB"
"Joe"
"john"
"MARK"
"dave"
"Bill"
列表2:
"JOE"
"MARK"
"DAVE"
在此之后,使用String
的toUpperCase()
為每個List創建一個單獨的HashSet
。 然后調用removeAll
。
Set1.removeAll(set2);
Set1:
"BOB"
"JOHN"
"BILL"
我需要讓列表再次看起來像這樣:
"BOB"
"john"
"Bill"
任何想法將不勝感激。 我知道它很差,應該有原始列表的標准,但這不是我要決定的。
在我原來的回答中,我不假思索地建議使用Comparator
,但這會導致TreeSet
違反equals
合同,並且是一個等待發生的錯誤:
// Don't do this:
Set<String> setA = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
setA.add("hello");
setA.add("Hello");
System.out.println(setA);
Set<String> setB = new HashSet<String>();
setB.add("HELLO");
// Bad code; violates symmetry requirement
System.out.println(setB.equals(setA) == setA.equals(setB));
最好使用專用類型:
public final class CaselessString {
private final String string;
private final String normalized;
private CaselessString(String string, Locale locale) {
this.string = string;
normalized = string.toUpperCase(locale);
}
@Override public String toString() { return string; }
@Override public int hashCode() { return normalized.hashCode(); }
@Override public boolean equals(Object obj) {
if (obj instanceof CaselessString) {
return ((CaselessString) obj).normalized.equals(normalized);
}
return false;
}
public static CaselessString as(String s, Locale locale) {
return new CaselessString(s, locale);
}
public static CaselessString as(String s) {
return as(s, Locale.ENGLISH);
}
// TODO: probably best to implement CharSequence for convenience
}
此代碼不太可能導致錯誤:
Set<CaselessString> set1 = new HashSet<CaselessString>();
set1.add(CaselessString.as("Hello"));
set1.add(CaselessString.as("HELLO"));
Set<CaselessString> set2 = new HashSet<CaselessString>();
set2.add(CaselessString.as("hello"));
System.out.println("1: " + set1);
System.out.println("2: " + set2);
System.out.println("equals: " + set1.equals(set2));
不幸的是,這更加冗長。
它可以通過以下方式完成:
TreeSet
, TreeSet#removeAll(Collection<?> c)
然后刪除所有常見的String
不區分大小寫 ArrayList#retainAll(Collection<?> c)
將遍歷列表元素的事實,並且對於每個元素,它將在提供的集合上調用contains(Object o)
來知道是應該保留值還是因為集合不區分大小寫,所以我們將只保留與所提供的TreeSet
實例中的String
不敏感的String
。 相應的代碼:
List<String> list1 = new ArrayList<>(
Arrays.asList("BOB", "Joe", "john", "MARK", "dave", "Bill")
);
List<String> list2 = Arrays.asList("JOE", "MARK", "DAVE");
// Add all values of list1 in a case insensitive collection
Set<String> set1 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set1.addAll(list1);
// Add all values of list2 in a case insensitive collection
Set<String> set2 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set2.addAll(list2);
// Remove all common Strings ignoring case
set1.removeAll(set2);
// Keep in list1 only the remaining Strings ignoring case
list1.retainAll(set1);
for (String s : list1) {
System.out.println(s);
}
輸出:
BOB
john
Bill
注意1:將第二個列表的內容放入TreeSet
非常重要,特別是如果我們不知道它的大小,因為TreeSet#removeAll(Collection<?> c)
取決於兩個集合的大小,如果當前集合的大小嚴格大於提供的集合的大小,那么它將直接調用當前集合上的remove(Object o)
來刪除每個元素,在這種情況下,提供的集合可以是一個列表。 但如果相反,它將在提供的集合上調用contains(Object o)
以知道是否應該刪除給定元素,因此如果它不是不區分大小寫的集合,我們將無法獲得預期的結果。
注意2:上面描述的方法ArrayList#retainAll(Collection<?> c)
的行為與我們可以在AbstractCollection
找到的方法retainAll(Collection<?> c)
的默認實現的行為相同,這樣方法實際上適用於retainAll(Collection<?> c)
具有相同行為的任何集合。
您可以使用散列映射並使用大寫集作為映射到混合大小寫集的鍵。
哈希映射的鍵是唯一的,您可以使用HashMap.keyset()獲取它們的一組;
要檢索原始案例,它就像HashMap.get(“UPPERCASENAME”)一樣簡單。
並根據文件 :
返回此映射中包含的鍵的set視圖。 該集由地圖支持,因此對地圖的更改將反映在集中,反之亦然。 該集支持元素刪除,它通過Iterator.remove,Set.remove,removeAll,retainAll和clear操作從此映射中刪除相應的映射。 它不支持add或addAll操作。
所以HashMap.keyset()。removeAll會影響hashmap :)
編輯:使用McDowell的解決方案。 我忽略了你實際上並不需要這些字母是大寫的事實:P
這將是一個有趣的使用谷歌收藏解決。 你可以有一個像這樣的常量謂詞:
private static final Function<String, String> TO_UPPER = new Function<String, String>() {
public String apply(String input) {
return input.toUpperCase();
}
然后你可以做的就像這樣:
Collection<String> toRemove = Collections2.transform(list2, TO_UPPER);
Set<String> kept = Sets.filter(list1, new Predicate<String>() {
public boolean apply(String input) {
return !toRemove.contains(input.toUpperCase());
}
}
那是:
請注意, Collections2.transform
的輸出不是一個有效的Set
實現,所以如果你處理大量數據並且探測該列表的成本會傷害你,你可以改用
Set<String> toRemove = Sets.newHashSet(Collections2.transform(list2, TO_UPPER));
這將恢復有效的查找,將過濾返回到O(n)而不是O(n ^ 2)。
據我所知,hashset使用對象的hashCode方法將它們彼此區分開來。 因此,您應該在對象中覆蓋此方法以區分不同的情況。
如果您真的使用字符串,則無法覆蓋此方法,因為您無法擴展String類。
因此,您需要創建自己的類,其中包含一個字符串作為您填充內容的屬性。 您可能希望使用getValue()和setValue(String)方法來修改字符串。
然后你可以將自己的類添加到hashmap中。
這應該可以解決你的問題。
問候
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.