2009-08-06 248 views
12

好的,這是我的問題。我必須HashSet's,我使用removeAll方法刪除一組中存在的值。集合removeAll忽略大小寫?

在調用該方法之前,我明顯將這些值添加到Set s。在添加之前,我在每個String上呼叫.toUpperCase(),因爲這兩個列表中的值都是不同的情況。這個案件沒有韻律或理由。

有一次,我打電話給removeAll,我需要返回Set中剩下的值。有沒有一種有效的方式做到這一點,而無需運行原始列表並使用CompareToIgnoreCase

實施例:

的List1:

"BOB" 
"Joe" 
"john" 
"MARK" 
"dave" 
"Bill" 

列表2:

"JOE" 
"MARK" 
"DAVE" 

在此之後,對於使用toUpperCase()String s各自列表創建一個單獨的HashSet。然後致電removeAll

Set1.removeAll(set2); 

Set1: 
    "BOB" 
    "JOHN" 
    "BILL" 

我需要在列表中再次像這樣:

"BOB" 
"john" 
"Bill" 

任何想法,將不勝感激。我知道這是窮人,應該有一個原始列表的標準,但這不是我決定的。

回答

13

在我原來的答覆,我不假思索地使用Comparator建議,但是這會導致TreeSet違反equals contract,是一個錯誤等待發生:

// 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)); 

這是不幸的是,更詳細。

+4

無需滾動您自己的比較器。 String類爲您提供了一個:http://java.sun.com/javase/6/docs/api/java/lang/String.html#CASE_INSENSITIVE_ORDER – banjollity 2009-08-06 22:04:33

+0

@bankollity。謝謝! - 從Java 1.2開始就一直存在,我從未注意到它。代碼修改。 – McDowell 2009-08-07 09:13:04

+1

哇,這是非常簡單的實現,雖然文檔導致你相信比較器僅用於排序。 TreeSet(Comparator c):構造一個新的空集,按照指定的比較器進行排序。 http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeSet.html#TreeSet%28java.util.Comparator%29。我很高興它的工作,但非常感謝你的迴應! – user84786 2009-08-07 13:42:19

1

您可以使用hashmap並使用大寫集作爲映射到混合大小寫集的鍵。

hashmaps的鍵是獨一無二的,你可以使用HashMap.keyset()獲得一組鍵。

檢索原始案例,就像HashMap.get(「UPPERCASENAME」)一樣簡單。

而按照documentation

返回包含在此映射中鍵 的一組視圖。 設置爲 支持的地圖,所以對地圖的更改將反映在 集合中,反之亦然 。 set支持元素 移除,可通過Iterator.remove,Set.remove, 的removeAll,retainAll和清晰 操作去除該映射中,在 對應 映射。它不支持 add或addAll操作。

所以HashMap.keyset()的removeAll將影響到散:)

編輯:麥克道爾使用的解決方案。我忽略了一個事實,你沒有實際需要的字母是大寫字母:P

0

據我所知,HashSet的的使用對象的hashCode的方法來區別出它們彼此。 因此,您應該在對象中重寫此方法以區分不同情況。

如果你真的使用字符串,你不能重寫這個方法,因爲你不能擴展String類。

因此,您需要創建自己的類,其中包含字符串作爲填充內容的屬性。你可能想要有一個getValue()和setValue(String)方法來修改字符串。

然後你可以添加你自己的類到散列表。

這應該可以解決您的問題。

問候

1

這將是一個有趣的使用google-collections解決。你可以有一個恆定的謂詞像這樣:

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()); 
    } 
} 

即:

  • 建立一個大寫'丟棄'列表的案例版本
  • 對原始列表應用一個過濾器,僅保留這些項目的u在大寫字母列表中,取值爲而不是

注意的Collections2.transform輸出是不是一個有效的Set實現,因此,如果您正在處理大量的數據和探測該列表會傷害你的成本,你可以改用

Set<String> toRemove = Sets.newHashSet(Collections2.transform(list2, TO_UPPER)); 

這將恢復一個有效的查找,返回到O(n)而不是O(n^2)的過濾。

3

它可以通過完成:

  1. 移動你的列表的內容爲不區分大小寫TreeSet S,
  2. 然後刪除所有常見String案不區分大小寫感謝TreeSet#removeAll(Collection<?> c)
  3. ,最後依靠ArrayList#retainAll(Collection<?> c)將遍歷列表中的元素以及每個元素都會在提供的集合上調用contains(Object o)以瞭解值是否應該保留,因爲集合不區分大小寫,所以我們將僅保留String s與我們在提供的TreeSet實例中具有的不區分大小寫匹配。

相應的代碼:

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 

NB 1:到第二列表的內容具有成TreeSet特別是如果它是重要的,我們不知道它的大小,因爲TreeSet#removeAll(Collection<?> c)的行爲取決於兩個集合的大小,如果第當前集合嚴格大於提供集合的大小,那麼它將直接調用當前集合中的remove(Object o)來刪除每個元素,在這種情況下,提供的集合可能是一個列表。但是如果相反,它會在提供的集合上調用contains(Object o)來知道給定的元素是否應該被刪除,所以如果它不是不區分大小寫的集合,我們不會得到預期的結果。

NB 2:上述方法ArrayList#retainAll(Collection<?> c)的行爲是一樣的方法retainAll(Collection<?> c)的默認實現的行爲,我們可以在AbstractCollection找到這樣,這種做法實際上會用其實現的任何藏品工作retainAll(Collection<?> c)具有相同的行爲。

+0

非常好。我想知道retainAll方法怎麼知道保留值,儘管在set1中他們有點不同 – Muky 2017-02-02 14:17:50

+0

@Muky thx對於反饋,我改進了我的答案以清楚地說明,希望現在會好起來 – 2017-02-02 15:15:53