好的,这是我的问题。我必须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"
任何想法都会非常感激。我知道它很糟糕,原始列表应该有一个标准,但这不是我决定的。
答案 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)
可以通过以下方式完成:
TreeSet
s,String
不区分大小写的感谢TreeSet#removeAll(Collection<?> c)
ArrayList#retainAll(Collection<?> c)
将迭代列表元素的事实,并且对于每个元素,它将在提供的集合上调用contains(Object o)
以了解是否应保留该值并且在这里,由于集合不区分大小写,我们将只保留与{1}}实例中的内容不区分大小写的String
。相应的代码:
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”)一样简单。
返回键的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。
这可以解决你的问题。
问候