收集removeAll无视案例?
好的,这是我的问题。 我必须使用HashSet
,我使用removeAll
方法删除一个集合中存在的值。
在调用方法之前,我显然将值添加到Set
。 我在添加之前在每个String
上调用.toUpperCase()
,因为两个列表中的值都是不同的情况。 这个案子没有押韵或理由。
一旦我调用removeAll
,我需要将原始案例返回给Set
中剩下的值。 有没有一种有效的方法可以在不运行原始列表和使用CompareToIgnoreCase
情况下执行此操作?
例:
列表1:
"BOB" "Joe" "john" "MARK" "dave" "Bill"
列表2:
"JOE" "MARK" "DAVE"
在此之后,使用String
的toUpperCase()
为每个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));
不幸的是,这更加冗长。
它可以通过以下方式完成:
- 将列表的内容移动到不区分大小写的
TreeSet
, - 感谢
TreeSet#removeAll(Collection> c)
然后删除所有常见的String
不区分大小写 - 最后依赖于
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中。
这应该可以解决你的问题。
问候