收集removeAll无视案例?

时间:2009-08-06 21:06:14

标签: java collections case-insensitive hashset

好的,这是我的问题。我必须HashSet,我使用removeAll方法删除一个集合中存在的值。

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

拨打removeAll后,我需要将原始案例返回给Set中剩余的值。有没有一种有效的方法可以在不运行原始列表并使用CompareToIgnoreCase

的情况下执行此操作

示例:

的List1:

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

列表2:

"JOE"
"MARK"
"DAVE"

在此之后,使用HashSet上的toUpperCase()为每个列表创建单独的String。然后拨打removeAll

Set1.removeAll(set2);

Set1:
    "BOB"
    "JOHN"
    "BILL"

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

"BOB"
"john"
"Bill"

任何想法都会非常感激。我知道它很糟糕,原始列表应该有一个标准,但这不是我决定的。

5 个答案:

答案 0 :(得分: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));

不幸的是,这更加冗长。

答案 1 :(得分:3)

可以通过以下方式完成:

  1. 将列表内容移至不区分大小写的TreeSet s,
  2. 然后删除所有常见String不区分大小写的感谢TreeSet#removeAll(Collection<?> c)
  3. 并最终依赖于ArrayList#retainAll(Collection<?> c)将迭代列表元素的事实,并且对于每个元素,它将在提供的集合上调用contains(Object o)以了解是否应保留该值并且在这里,由于集合不区分大小写,我们将只保留与{1}}实例中的内容不区分大小写的String
  4. 相应的代码:

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

    注意1:将第二个列表的内容放入BOB john Bill 非常重要,尤其是如果我们不知道它的大小,因为{TreeSet的行为1}}取决于两个集合的大小,如果当前集合的大小严格大于提供的集合的大小,那么它将直接调用当前集合上的TreeSet#removeAll(Collection<?> c)来删除每个元素,在此case提供的集合可以是一个列表。但如果相反,它将在提供的集合上调用remove(Object o)以了解是否应删除给定元素,因此如果它不是不区分大小写的集合,我们将无法获得预期结果

    NB 2:上述方法contains(Object o)的行为与ArrayList#retainAll(Collection<?> c)方法的默认实现相同,我们可以在{{{ 1}}这样,这种方法实际上适用于retainAll(Collection<?> c)的实现具有相同行为的任何集合。

答案 2 :(得分:1)

您可以使用hashmap并使用大写字母作为映射到混合大小写集的键。

哈希映射的键是唯一的,您可以使用HashMap.keyset()获取它们的一组;

检索原始案例,就像HashMap.get(“UPPERCASENAME”)一样简单。

根据documentation

  

返回键的set视图   包含在这张地图中。 该集是   由地图支持,所以改变了   地图反映在集合中,和   反之亦然。集支持元素   删除,删除   来自此地图的相应映射,   通过Iterator.remove,Set.remove,   removeAll,retainAll和clear   操作。它不支持   添加或添加所有操作。

所以HashMap.keyset()。removeAll将影响hashmap:)

编辑:使用McDowell的解决方案。我忽略了这样一个事实:你实际上并不需要这些字母是大写的:P

答案 3 :(得分: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());
    }
}

那是:

  • 构建“丢弃”列表的大写版本
  • 将过滤器应用于原始列表,仅保留 那些在大写仅列表中的大写值的项目。

请注意,Collections2.transform的输出不是一个有效的Set实现,因此如果您处理大量数据并且探测该列表的成本会对您造成伤害,您可以改为使用

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

将恢复有效的查找,将过滤返回到O(n)而不是O(n ^ 2)。

答案 4 :(得分:0)

据我所知,hashset使用对象的hashCode方法将它们彼此区分开来。 因此,您应该在对象中覆盖此方法,以便区分不同的情况。

如果您真的使用字符串,则不能覆盖此方法,因为您无法扩展String类。

因此,您需要创建自己的类,其中包含一个字符串作为您填充内容的属性。你可能想要一个getValue()和setValue(String)方法来修改字符串。

然后您可以将自己的类添加到hashmap。

这可以解决你的问题。

问候