Удаление и сбор элементов с потоками Java - программирование

Удаление и сбор элементов с потоками Java

Предположим, что у меня есть Collection и a Predicate, которые соответствуют элементам, которые я хотел бы удалить из Collection. Но я не просто хочу их отбросить, я хочу переместить элементы, соответствующие друг другу, в новую коллекцию. Я бы сделал что-то вроде this в Java 7:

List<E> removed = new LinkedList<>();
for (Iterator<E> i = data.iterator(); i.hasNext();) {
    E e = i.next();
    if (predicate.test(e)) {
        removed.add(e);
        i.remove();
    }
}

Мне любопытно, есть ли способ streams/Java 8 для этого. Collections.removeIf(), к сожалению, просто возвращает boolean (даже не подсчет количества удаленных элементов?) Плохо. Я представляю себе что-то вроде это (хотя, конечно, .removeAndYield(Predicate) не существует):

List<E> removed = data.removeAndYield(predicate).collect(Collectors.toList());

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

Изменить: Очевидно, что мы можем разделить задачу на два отдельных шага, и при условии, что соответствующие структуры данных будут эффективными. Вопрос в том, может ли это быть сделано для произвольных коллекций (которые могут не иметь эффективных .contains() и т.д.).

4b9b3361

Ответ 1

Если вы не против, позвольте мне немного сгибать ваши требования.: -)

Одной характеристикой желаемого результата является то, что элементы соответствия должны заканчиваться в одном наборе, а несогласованные элементы должны заканчиваться в другой коллекции. В мутативном мире до Java-8 самый простой способ подумать о создании коллекции несоответствующих элементов - это удалить соответствующие элементы из исходной коллекции.

Но удаление - модификация исходного списка - неотъемлемая часть требования?

Если это не так, результат может быть достигнут с помощью простой операции разбиения:

Map<Boolean, List<E>> map = data.stream().collect(partitioningBy(predicate));

Результирующая карта представляет собой по существу два списка, которые содержат элементы соответствия (key = true) и non-matching (key = false).

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

Ответ 2

Я бы сохранил это просто:

Set<E> removed = set.stream()
    .filter(predicate)
    .collect(Collectors.toSet());

set.removeAll(removed);

Ответ 3

Если вам нужен функциональный способ сделать это, вы можете написать свой собственный метод.

static <E> Set<E> removeIf(Collection<? extends E> collection, Predicate<? super E> predicate) {
    Set<E> removed = new HashSet<>();
    for (Iterator<? extends E> i = collection.iterator(); i.hasNext();) {
        E e = i.next();
        if (predicate.test(e)) {
            removed.add(e);
            i.remove();
        }
    }
    return removed;
}

Это можно использовать для удаления всех нечетных чисел из List.

Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6));
Set<Integer> removed = removeIf(set, i -> i % 2 != 0);
System.out.println(set);
System.out.println(removed);

Ответ 4

Просто напишите себе функцию повторного использования следующим образом:

/**
 * Removes all matching element from iterator
 * 
 * @param it
 * @param predicate
 */
public static <E> void removeMatching(final Iterator<E> it, final Predicate<E> predicate) {
    while (it.hasNext()) {
        final E e = it.next();
        if (predicate.test(e)) {
            it.remove();
        }
    }
}

Я также не нашел ранее существовавшего решения для Streams. Использование iterator.remove() требует меньше памяти, чем временного набора.