2009-08-06 3 views
12

Итак, вот моя проблема. У меня есть HashSet, я использую метод removeAll для удаления значений, которые существуют в одном наборе от другого.Коллекция removeAll игнорируя случай?

До вызова метода я, очевидно, добавляю значения к Set. Я вызываю .toUpperCase() на каждый String перед добавлением, потому что значения имеют разные случаи в обоих списках. В этом случае нет рифмы или причины.

Как только я звоню removeAll, мне нужно вернуть оригинальные чехлы для значений, оставшихся в Set. Есть ли эффективный способ сделать это, не просматривая исходный список и используя CompareToIgnoreCase?

Пример:

List1:

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

List2:

"JOE" 
"MARK" 
"DAVE" 

После этого, создать отдельный HashSet для каждого списка с использованием toUpperCase() на String с. Затем позвоните removeAll.

Set1.removeAll(set2); 

Set1: 
    "BOB" 
    "JOHN" 
    "BILL" 

Мне нужно, чтобы получить список, чтобы посмотреть, как это снова:

"BOB" 
"john" 
"Bill" 

Любые идеи будут оценены. Я знаю, что это плохо, должен быть стандарт для первоначального списка, но это не для меня.

ответ

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

Это, к сожалению, более многословным.

+4

Не нужно катить собственный компаратор. Класс String предоставляет один для вас: http://java.sun.com/javase/6/docs/api/java/lang/String.html#CASE_INSENSITIVE_ORDER – banjollity

+0

@bankollity. Благодаря! - это было там с Java 1.2, и я никогда не замечал этого. Код изменен. – McDowell

+1

Ничего себе, что было очень просто реализовать, хотя в документации вы верите, что компаратор используется исключительно для сортировки. TreeSet (Comparator c): Создает новый пустой набор, отсортированный в соответствии с указанным компаратором. http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeSet.html#TreeSet%28java.util.Comparator%29. Я рад, что это сработало, благодарю вас за ваш ответ! – user84786

1

Вы можете использовать hashmap и использовать капитал, указанный в качестве ключей, которые сопоставляются с набором смешанных футляров.

Ключи хэшмапов уникальны, и вы можете получить их набор с помощью HashMap.keyset();

для извлечения оригинального чехла, это так же просто, как HashMap.get ("UPPERCASENAME").

И по documentation:

Возвращает набор вид ключей , содержащихся в этой карте. Комплект с картой, поэтому изменения в карте отражены в комплекте, и наоборот. Набор поддерживает элемент удаления, который удаляет соответствующее отображение из этой карты, через Iterator.remove, Set.remove, RemoveAll, retainAll и четких операций. Он не поддерживает операции добавления или добавления .

Так HashMap.keyset() RemoveAll будет эффект HashMap :)

EDIT:. Использовать решение Макдауэлл. Я упустил из виду тот факт, что на самом деле вам не нужны буквы в верхнем регистре: P

0

Насколько я знаю, hashset использует метод hashCode объекта, чтобы отличать их друг от друга. , вы должны, таким образом, переопределить этот метод в своем объекте, чтобы отличать случаи.

Если вы действительно используете строку, вы не можете переопределить этот метод, поскольку вы не можете расширить класс String.

поэтому вам нужно создать свой собственный класс, содержащий строку как атрибут, который вы заполняете своим контентом. вы можете захотеть получить метод getValue() и setValue (String), чтобы изменить строку.

Затем вы можете добавить свой собственный класс в хэш-карту.

это должно решить вашу проблему.

рассматривает

1

Это было бы интересно решить, используя google-collections. Вы могли бы иметь постоянный Predicate так:

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

То есть:

  • Построить прописные case-only версии «to discard»
  • Применить фильтр к исходному списку, сохранив только те предметы, чей u Значение ppercased равно , а не в списке только в верхнем регистре.

Обратите внимание, что выход Collections2.transform не является эффективным Set реализации, так что если вы имеете дело с большим количеством данных и стоимостями зондирования, что список будет вам больно, вы можете использовать вместо этого

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

, который восстановит эффективный поиск, возвращая фильтрацию O (n) вместо O (n^2).

3

Это может быть сделано путем:

  1. Перемещение содержимого списков в регистронезависимых TreeSet с,
  2. затем удаление всех общих String сек регистронезависимо благодаря TreeSet#removeAll(Collection<?> c)
  3. и, наконец, опираясь на тот факт, что ArrayList#retainAll(Collection<?> c) будет перебирать элементы списка, и для каждого элемента он назовет contains(Object o) на предоставленную коллекцию, чтобы узнать, должно ли оно храниться или нет, и здесь, поскольку коллекция нечувствительна к регистру, мы сохраним только String s, которые не учитывают регистр без учета того, что у нас есть в предоставленном экземпляре 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) зависит от размера обеих коллекций, если размер th текущая коллекция строго больше, чем размер предоставленной коллекции, то она будет вызывать непосредственно remove(Object o) в текущей коллекции, чтобы удалить каждый элемент, в этом случае предоставленная коллекция может быть списком. Но если это наоборот, то он вызовет contains(Object o) в предоставленной коллекции, чтобы узнать, должен ли данный элемент быть удален или нет, если он не является сборкой без учета регистра, мы не получим ожидаемый результат.

NB 2: Поведение метода ArrayList#retainAll(Collection<?> c), описанного выше, является такой же, как и поведение по умолчанию реализации метода retainAll(Collection<?> c), что мы можем найти в AbstractCollection таким образом, что такой подход действительно будет работать с любыми коллекциями, чья реализация retainAll(Collection<?> c) имеет такое же поведение.

+0

Очень приятно. Интересно, как метод keepAll знает, чтобы сохранить значения, хотя в set1 они являются разновидностями – Muky

+0

@Muky thx для обратной связи, я улучшил свой ответ, чтобы дать понять, надеясь, что он будет достаточно хорошим сейчас –

Смежные вопросы