簡體   English   中英

收集removeAll無視案例?

[英]Collection removeAll ignoring case?

好的,這是我的問題。 我必須使用HashSet ,我使用removeAll方法刪除一個集合中存在的值。

在調用方法之前,我顯然將值添加到Set 我在添加之前在每個String上調用.toUpperCase() ,因為兩個列表中的值都是不同的情況。 這個案子沒有押韻或理由。

一旦我調用removeAll ,我需要將原始案例返回給Set中剩下的值。 有沒有一種有效的方法可以在不運行原始列表和使用CompareToIgnoreCase情況下執行此操作?

例:

列表1:

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

列表2:

"JOE"
"MARK"
"DAVE"

在此之后,使用StringtoUpperCase()為每個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));

不幸的是,這更加冗長。

它可以通過以下方式完成:

  1. 將列表的內容移動到不區分大小寫的TreeSet
  2. 感謝TreeSet#removeAll(Collection<?> c)然后刪除所有常見的String不區分大小寫
  3. 最后依賴於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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM