收集removeAll无视案例?

好的,这是我的问题。 我必须使用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 setA = new TreeSet(String.CASE_INSENSITIVE_ORDER); setA.add("hello"); setA.add("Hello"); System.out.println(setA); Set setB = new HashSet(); 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 set1 = new HashSet(); set1.add(CaselessString.as("Hello")); set1.add(CaselessString.as("HELLO")); Set set2 = new HashSet(); 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 list1 = new ArrayList<>( Arrays.asList("BOB", "Joe", "john", "MARK", "dave", "Bill") ); List list2 = Arrays.asList("JOE", "MARK", "DAVE"); // Add all values of list1 in a case insensitive collection Set set1 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); set1.addAll(list1); // Add all values of list2 in a case insensitive collection Set 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

这将是一个有趣的使用谷歌collections解决。 你可以有一个像这样的常量谓词:

 private static final Function TO_UPPER = new Function() { public String apply(String input) { return input.toUpperCase(); } 

然后你可以做到这样的事情:

 Collection toRemove = Collections2.transform(list2, TO_UPPER); Set kept = Sets.filter(list1, new Predicate() { public boolean apply(String input) { return !toRemove.contains(input.toUpperCase()); } } 

那是:

  • 构建“丢弃”列表的仅大写版本
  • 将filter应用于原始列表, 保留其大写值不在 大写列表中的项目。

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

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

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

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

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

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

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

这应该可以解决你的问题。

问候