Подтвердить что ты не робот

Коллекция removeAll игнорирует случай?

Хорошо, вот моя проблема. Мне нужно 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"

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

  • Перемещение содержимого ваших списков без учета регистра TreeSet s,
  • затем удалив все обычные String без учета регистра, спасибо TreeSet#removeAll(Collection<?> c)
  • и, наконец, полагаясь на то, что 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) имеет такое же поведение.

Ответ 3

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

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

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

И в соответствии с документация:

Возвращает заданное представление ключей содержащихся в этой карте. Множество подкрепляется картой, поэтому изменения в карты отражены в множестве, и наоборот. Набор поддерживает элемент удаление, которое удаляет соответствующее отображение из этого отображения, через Iterator.remove, Set.remove, removeAll, saveAll и clear операции. Он не поддерживает add или addAll.

Итак, HashMap.keyset(). removeAll будет влиять на hashmap:)

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

Ответ 4

Это было бы интересно решить с помощью 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).

Ответ 5

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

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

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

то вы можете добавить свой собственный класс в hashmap.

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

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