Коллекция удалитьВсе случаи игнорирования? - PullRequest
12 голосов
/ 07 августа 2009

Хорошо, вот моя проблема. Я должен HashSet, я использую метод removeAll, чтобы удалить значения, которые существуют в одном наборе из другого.

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

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

Пример:

List1:

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

List2:

"JOE"
"MARK"
"DAVE"

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

Set1.removeAll(set2);

Set1:
    "BOB"
    "JOHN"
    "BILL"

Мне нужно, чтобы список снова выглядел так:

"BOB"
"john"
"Bill"

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

Ответы [ 5 ]

13 голосов
/ 07 августа 2009

В своем первоначальном ответе я бездумно предложил использовать 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));

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

3 голосов
/ 02 августа 2016

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

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

Выход:

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), которое мы можем найти в AbstractCollection, так что этот подход будет фактически работать с любыми коллекциями, реализация которых retainAll(Collection<?> c) имеет такое же поведение.

1 голос
/ 22 декабря 2009

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

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

1 голос
/ 07 августа 2009

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

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

, чтобы получить исходный случай, это так же просто, как HashMap.get ("UPPERCASENAME").

А согласно документации :

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

Таким образом, HashMap.keyset (). RemoveAll будет влиять на хэш-карту:)

РЕДАКТИРОВАТЬ: используйте решение McDowell. Я упустил из виду тот факт, что вам не нужны буквы в верхнем регистре: P

0 голосов
/ 07 августа 2009

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

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

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

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

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

1011 * привет *

...