Я открыт для использования библиотеки lib. Я просто хочу что-то простое, чтобы различать две коллекции по другим критериям, чем обычная функция равенства.
Сейчас я использую что-то вроде:
collection1.stream()
.filter(element -> !collection2.stream()
.anyMatch(element2 -> element2.equalsWithoutSomeField(element)))
.collect(Collectors.toSet());
и я бы хотел что-то вроде:
Collections.diff(collection1, collection2, Foo::equalsWithoutSomeField);
(править) Больше контекста:
Следует упомянуть, что я ищу то, что уже существует, а не кодирую это сам. Я мог бы написать небольшую утилиту из ваших идей, если ничего не существует.
Кроме того, в моем случае настоящие дубликаты невозможны: коллекции - это наборы. Однако дубликаты в соответствии с обычным равенством возможны и не должны удаляться этой операцией. Кажется, это ограничение во многих возможных решениях.
Я думаю, что ключевым моментом здесь является пользовательская функция равенства без переопределения равенства, а не сама разница.
@JoseDaSilva Если это было так, в вопросе следует сделать много более ясным. Хотя тогда ответ будет BiPredicate<T, T> p = (t0,t1) -> t0.equalsWithoutSomeField(t1);, что является довольно "тривиальной" синтаксической деталью ...
Я согласен, если у него уже есть метод Foo::equalsWithoutSomeField, правильным ответом будет простая статическая функция, использующая этот BiPredicate. Решение может использовать тот же метод сравнения, который он уже предоставил, потому что он работает нормально. В противном случае и даже для того, чтобы помочь людям с аналогичной проблемой, у которых нет этого equalsWithoutSomeField, ключ будет здесь, в этом методе.
@ Marco13, конечно, я могу это сделать. Я искал некоторые скрытые утилиты, о которых я не знал, которые будут делать этот OOTB, передавая только коллекции и предикат. Как я уже упоминал в вопросе.




Мы используем аналогичные методы в нашем проекте, чтобы сократить повторяющуюся фильтрацию коллекции. Мы начали с нескольких основных строительных блоков:
static <T> boolean anyMatch(Collection<T> set, Predicate<T> match) {
for (T object : set)
if (match.test(object))
return true;
return false;
}
Исходя из этого, мы можем легко реализовать такие методы, как noneMatch, и более сложные, такие как isSubset или ваш diff:
static <E> Collection<E> disjunctiveUnion(Collection<E> c1, Collection<E> c2, BiPredicate<E, E> match)
{
ArrayList<E> diff = new ArrayList<>();
diff.addAll(c1);
diff.addAll(c2);
diff.removeIf(e -> anyMatch(c1, e1 -> match.test(e, e1))
&& anyMatch(c2, e2 -> match.test(e, e2)));
return diff;
}
Обратите внимание, что некоторые возможности для настройки производительности наверняка есть. Но разделение его на небольшие методы помогает с легкостью понять и использовать их. В коде они читаются довольно хорошо.
Затем вы использовали бы его, как вы уже сказали:
CollectionUtils.disjunctiveUnion(collection1, collection2, Foo::equalsWithoutSomeField);
Принимая во внимание предложение Хосе да Силвы, вы даже можете использовать Comparator для построения ваших критериев на лету:
Comparator<E> special = Comparator.comparing(Foo::thisField)
.thenComparing(Foo::thatField);
BiPredicate specialMatch = (e1, e2) -> special.compare(e1, e2) == 0;
Есть ли причина сначала добавить элементы все, а затем удалить ненужные, вместо того, чтобы удалять только те из c1, которые уже содержатся в c2?
Создаю новую коллекцию, чтобы не менять входные параметры. Если, например, в настоящее время выполняется итерация одного из входных параметров, мы не хотим изменять его и запускать ConcurrentModificationExceptions. Вы, конечно, можете добавить только соответствующие элементы в новую коллекцию, но removeIf сохраняет ее очень читаемой (imho) для целей SO. Попав в свою кодовую базу, вы можете улучшить ее для повышения производительности.
Конечно, нет изменение входных коллекций важно как часть контракта, но я думаю, что независимо от этого реализацию можно было бы сделать более эффективной. Однако вы уже упомянули о возможных улучшениях, так что все в порядке.
static <T> Collection<T> diff(Collection<T> minuend, Collection<T> subtrahend, BiPredicate<T, T> equals) {
Set<Wrapper<T>> w1 = minuend.stream().map(item -> new Wrapper<>(item, equals)).collect(Collectors.toSet());
Set<Wrapper<T>> w2 = subtrahend.stream().map(item -> new Wrapper<>(item, equals)).collect(Collectors.toSet());
w1.removeAll(w2);
return w1.stream().map(w -> w.item).collect(Collectors.toList());
}
static class Wrapper<T> {
T item;
BiPredicate<T, T> equals;
Wrapper(T item, BiPredicate<T, T> equals) {
this.item = item;
this.equals = equals;
}
@Override
public int hashCode() {
// all items have same hash code, check equals
return 1;
}
@Override
public boolean equals(Object that) {
return equals.test(this.item, ((Wrapper<T>) that).item);
}
}
Вы можете привести пример, показывающий, как это предполагается использовать?
Прежде чем сравнивать мыслительные элементы, необходимо преобразовать их. Отредактировано для использования настраиваемого предиката.
Вы можете использовать UnifiedSetWithHashingStrategy из Коллекции Eclipse. UnifiedSetWithHashingStrategy позволяет создавать набор с помощью собственного HashingStrategy.
HashingStrategy позволяет пользователю использовать собственные hashCode() и equals(). hashCode() и equals() Объекта не используются.
Изменить в соответствии с требованиями OP через комментарий:
Вы можете использовать reject() или removeIf() в зависимости от ваших требований.
Пример кода:
// Common code
Person person1 = new Person("A", "A");
Person person2 = new Person("B", "B");
Person person3 = new Person("C", "A");
Person person4 = new Person("A", "D");
Person person5 = new Person("E", "E");
MutableSet<Person> personSet1 = Sets.mutable.with(person1, person2, person3);
MutableSet<Person> personSet2 = Sets.mutable.with(person2, person4, person5);
HashingStrategy<Person> hashingStrategy =
HashingStrategies.fromFunction(Person::getLastName);
1) Использование reject(): Создает новый Set, содержащий все элементы, которые удовлетворяют нетPredicate.
@Test
public void reject()
{
MutableSet<Person> personHashingStrategySet = HashingStrategySets.mutable.withAll(
hashingStrategy, personSet2);
// reject creates a new copy
MutableSet<Person> rejectSet = personSet1.reject(personHashingStrategySet::contains);
Assert.assertEquals(Sets.mutable.with(person1, person3), rejectSet);
}
2) Использование removeIf(): мутирует исходный Set с помощью удаление элементов, которые удовлетворяют требованиям Predicate.
@Test
public void removeIfTest()
{
MutableSet<Person> personHashingStrategySet = HashingStrategySets.mutable.withAll(
hashingStrategy, personSet2);
// removeIf mutates the personSet1
personSet1.removeIf(personHashingStrategySet::contains);
Assert.assertEquals(Sets.mutable.with(person1, person3), personSet1);
}
Ответ перед требованием от OP через комментарий: Сохранено для справки, если другие сочтут это полезным.
3) Использование Sets.differenceInto() API, доступного в коллекциях Eclipse:
В приведенном ниже коде set1 и set2 - это два набора, в которых используются Person и equals()hashCode(). differenceSet - это UnifiedSetWithHashingStrategy, поэтому он использует lastNameHashingStrategy для определения уникальности. Следовательно, хотя set2 не содержит person3, но имеет то же последнее имя, что и person1, differenceSet содержит только person1.
@Test
public void differenceTest()
{
MutableSet<Person> differenceSet = Sets.differenceInto(
HashingStrategySets.mutable.with(hashingStrategy),
set1,
set2);
Assert.assertEquals(Sets.mutable.with(person1), differenceSet);
}
Класс человека, общий для обоих блоков кода:
public class Person
{
private final String firstName;
private final String lastName;
public Person(String firstName, String lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName()
{
return firstName;
}
public String getLastName()
{
return lastName;
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
Person person = (Person) o;
return Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}
@Override
public int hashCode()
{
return Objects.hash(firstName, lastName);
}
}
Документы Javadoc:MutableSet, UnifiedSet, UnifiedSetWithHashingStrategy, ХешированиеСтратегии, Наборы, отклонять, removeIf
Примечание: Я участник коллекций Eclipse
Я думал, что это все ... но я не хочу удалять «дубликаты» в соответствии с пользовательской функцией хеширования. Set должен быть построен с использованием реальной функции хеширования, а затем функция diff, которая мне нужна, будет использовать пользовательскую. Итак, в вашем тестовом примере я хотел бы, чтобы в окончательном наборе были person1 и person3. Но закройте одно, может быть полезно для других.
Я обновил ответ, который удовлетворяет вашим требованиям. Пожалуйста, дайте мне знать, если это сработает. Спасибо!
Не так просто, как то, что я искал, но действительно интересно!
Я думаю, что ваш подход хорош и максимально эффективен. Вы можете просто обернуть его в метод (заменив вызов
equals...соответствующимBiPredicate). Для удобочитаемости я бы заменил вложенную потоковую передачу в критерии фильтрации вызовом такого метода, какboolean contains(Collection<T>, T, BiPredicate<T, T>), но в остальном здесь вроде бы все в порядке.