13

好的,这是我的问题。我必须HashSet,我使用该removeAll方法从另一组中删除存在于一组中的值。

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

一旦我打电话removeAll,我需要把原来的情况下留在Set. 有没有一种有效的方法可以在不遍历原始列表和使用的情况下执行此操作CompareToIgnoreCase

例子:

清单 1:

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

清单 2:

"JOE"
"MARK"
"DAVE"

在此之后,使用on sHashSet为每个 List创建一个单独的。然后调用。toUpperCase()StringremoveAll

Set1.removeAll(set2);

Set1:
    "BOB"
    "JOHN"
    "BILL"

我需要让列表再次看起来像这样:

"BOB"
"john"
"Bill"

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

4

5 回答 5

13

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

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

不幸的是,这更冗长。

于 2009-08-06T21:16:59.397 回答
4

可以通过以下方式完成:

  1. 将列表的内容移动到不区分大小写TreeSet的 s,
  2. String然后不区分大小写地删除所有 common s 谢谢TreeSet#removeAll(Collection<?> c)
  3. ArrayList#retainAll(Collection<?> c)最后依赖于将遍历列表元素的事实,并且对于每个元素,它将调用contains(Object o)提供的集合以知道是否应该保留该值,并且这里的集合不区分大小写,我们将只保留Strings 与我们在提供的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);
}

输出:

BOB
john
Bill

NB 1:TreeSet如果当前集合的TreeSet#removeAll(Collection<?> c)大小严格大于提供集合的大小,然后它将直接调用remove(Object o)当前集合以删除每个元素,在这种情况下,提供的集合可以是一个列表。但如果相反,它将调用contains(Object o)提供的集合来知道是否应该删除给定元素,因此如果它不是不区分大小写的集合,我们将不会得到预期的结果。

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

于 2016-08-02T16:10:02.473 回答
1

您可以使用哈希图并将大写集用作映射到混合大小写集的键。

hashmap 的键是唯一的,您可以使用 HashMap.keyset() 获取一组键;

要检索原始大小写,就像 HashMap.get("UPPERCASENAME") 一样简单。

并根据文档

返回此映射中包含的键的集合视图。集合由地图支持,因此对地图的更改会反映在集合中,反之亦然。该集合支持元素删除,即通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作从此映射中删除相应的映射。它不支持 add 或 addAll 操作。

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

编辑:使用麦克道尔的解决方案。我忽略了一个事实,即您实际上并不需要字母为大写:P

于 2009-08-06T21:09:42.110 回答
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)。

于 2009-12-22T10:20:26.717 回答
0

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

如果你真的在使用字符串,你不能重写这个方法,因为你不能扩展字符串类。

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

然后您可以将自己的类添加到哈希图中。

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

问候

于 2009-08-06T21:10:29.147 回答