集合删除忽略所有大小写?

好的,这就是我的问题。我必须's,我使用该方法从一个集合中删除存在于另一个集合中的值。HashSetremoveAll

在调用该方法之前,我显然将值添加到s中。在添加之前,我调用每个值,因为两个列表中的值是不同的情况。案件没有押韵或理由。Set.toUpperCase()String

调用 后,我需要将原始事例返回中剩余的值。有没有一种有效的方法来做到这一点,而无需运行原始列表并使用?removeAllSetCompareToIgnoreCase

例:

列表 1:

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

列表 2:

"JOE"
"MARK"
"DAVE"

在此之后,使用 on 为每个列表创建一个单独的列表。然后呼叫 .HashSettoUpperCase()StringremoveAll

Set1.removeAll(set2);

Set1:
    "BOB"
    "JOHN"
    "BILL"

我需要让列表再次如下所示:

"BOB"
"john"
"Bill"

任何想法将不胜感激。我知道这很差,原始列表应该有一个标准,但这不是由我决定的。


答案 1

在我最初的答案中,我不假思索地建议使用 ,但这会导致 违反 equals 契约,并且是一个等待发生的错误:ComparatorTreeSet

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

不幸的是,这更加冗长。


答案 2

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

  1. 将列表的内容移动到不区分大小写的 s 中,TreeSet
  2. 然后删除所有常见的s不区分大小写谢谢StringTreeSet#removeAll(Collection<?> c)
  3. 最后,依赖于将循环访问列表元素的事实,并且对于每个元素,它将调用提供的集合以了解是否应保留该值,并且由于集合不区分大小写,我们将仅保留与提供实例中的内容不区分大小写的 s。ArrayList#retainAll(Collection<?> c)contains(Object o)StringTreeSet

相应的代码:

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:重要的是要将第二个列表的内容放入一个,特别是如果我们不知道它的大小,因为的行为取决于两个集合的大小,如果当前集合的大小严格大于提供的集合的大小,那么它将直接调用当前集合来删除每个元素, 在这种情况下,提供的集合可以是一个列表。但是,如果情况恰恰相反,它将调用提供的集合来知道是否应该删除给定元素,因此,如果它不是不区分大小写的集合,我们将无法获得预期的结果。TreeSetTreeSet#removeAll(Collection<?> c)remove(Object o)contains(Object o)

注 2:上述方法的行为与我们可以找到的方法的默认实现的行为相同,因此此方法实际上将适用于其实现具有相同行为的任何集合。ArrayList#retainAll(Collection<?> c)retainAll(Collection<?> c)AbstractCollectionretainAll(Collection<?> c)