Java 8 поток в обратном порядке
общий вопрос: Как правильно повернуть поток вспять? Предполагая, что мы не знаем, из какого типа элементов состоит этот поток, каков общий способ обращения любого потока?
конкретный вопрос:
IntStream предоставляет метод диапазона для генерации целых чисел в определенном диапазоне IntStream.range(-range, 0), теперь, когда я хочу изменить его диапазон переключения от 0 до отрицательного не будет работать, также я не могу использовать Integer::compare
List<Integer> list = Arrays.asList(1,2,3,4);
list.stream().sorted(Integer::compare).forEach(System.out::println);
С IntStream Я получу этот компилятор ошибка
ошибка: (191, 0) ajc: метод
sorted()типаIntStreamне применяется для Аргументов (Integer::compare)
чего мне здесь не хватает?
21 ответов:
для конкретного вопроса генерации обратного
IntStreamпопробуйте что-то вроде этого:static IntStream revRange(int from, int to) { return IntStream.range(from, to) .map(i -> to - i + from - 1); }это позволяет избежать бокса и сортировки.
для общего вопроса о том, как обратить поток любого типа, я не знаю, есть ли "правильный" способ. Есть несколько способов, которые я могу придумать. Оба в конечном итоге хранения элементов потока. Я не знаю способа обратить поток без сохранения элементов.
это первый способ сохраняет элементы в массив и считывает их в поток в обратном порядке. Обратите внимание, что поскольку мы не знаем тип времени выполнения элементов потока, мы не можем правильно ввести массив, требуя непроверенного приведения.
@SuppressWarnings("unchecked") static <T> Stream<T> reverse(Stream<T> input) { Object[] temp = input.toArray(); return (Stream<T>) IntStream.range(0, temp.length) .mapToObj(i -> temp[temp.length - i - 1]); }другой метод использует коллекторы для накопления элементов в обратном списке. Это делает много вставок в передней части
ArrayListобъекты, так что есть много копирования происходит.Stream<T> input = ... ; List<T> output = input.collect(ArrayList::new, (list, e) -> list.add(0, e), (list1, list2) -> list1.addAll(0, list2));вероятно, можно написать гораздо более эффективный реверс коллектор, использующий какую-то настроенную структуру данных.
обновление 2016-01-29
так как этот вопрос получил немного внимания в последнее время, я считаю, что я должен обновить свой ответ, чтобы решить проблему с вставкой в передней части
ArrayList. Это будет ужасно неэффективно с большим количеством элементов, требующих O (N^2) копирования.лучше использовать
ArrayDequeвместо этого, который эффективно поддерживает вставку спереди. Один небольшая морщинка заключается в том, что мы не можем использовать форму с тремя аргамиStream.collect(); для этого требуется, чтобы содержимое второго arg было объединено в первый arg, и нет массовой операции "добавить все спереди" наDeque. Вместо этого мы используемaddAll()чтобы добавить содержимое первого arg к концу второго, а затем мы возвращаем второй. Для этого необходимо использоватьCollector.of()метод фабрики.полный код такой:
Deque<String> output = input.collect(Collector.of( ArrayDeque::new, (deq, t) -> deq.addFirst(t), (d1, d2) -> { d2.addAll(d1); return d2; }));в результате
DequeвместоList, но это не должно быть большой проблемой, так как ее можно легко повторить или передать в обратном порядке.
элегантное решение
List<Integer> list = Arrays.asList(1,2,3,4); list.stream() .boxed() // Converts Intstream to Stream<Integer> .sorted(Collections.reverseOrder()) // Method on Stream<Integer> .forEach(System.out::println);
Общий Вопрос:
поток не хранит никаких элементов.
таким образом, итерация элементов в обратном порядке невозможна без сохранения элементов в некоторой промежуточной коллекции.
Stream.of("1", "2", "20", "3") .collect(Collectors.toCollection(ArrayDeque::new)) // or LinkedList .descendingIterator() .forEachRemaining(System.out::println);обновление: изменен LinkedList на ArrayDeque (лучше) смотрите здесь для деталей
принты:
3 20 2 1кстати, используя
sortметод не является правильным, поскольку он сортирует, а не реверсирует (предполагая, что поток может быть неупорядоченным элементы)Конкретный Вопрос:
Я нашел это простым, легким и интуитивно понятным(скопированный комментарий @ Holger)
IntStream.iterate(to - 1, i -> i - 1).limit(to - from)
многие из решений здесь сортируют или отменяют
IntStream, но это излишне требует промежуточного хранения. решение Стюарта Маркса путь:static IntStream revRange(int from, int to) { return IntStream.range(from, to).map(i -> to - i + from - 1); }он правильно обрабатывает переполнение, а также, проходя этот тест:
@Test public void testRevRange() { assertArrayEquals(revRange(0, 5).toArray(), new int[]{4, 3, 2, 1, 0}); assertArrayEquals(revRange(-5, 0).toArray(), new int[]{-1, -2, -3, -4, -5}); assertArrayEquals(revRange(1, 4).toArray(), new int[]{3, 2, 1}); assertArrayEquals(revRange(0, 0).toArray(), new int[0]); assertArrayEquals(revRange(0, -1).toArray(), new int[0]); assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE).toArray(), new int[0]); assertArrayEquals(revRange(MAX_VALUE, MAX_VALUE).toArray(), new int[0]); assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE + 1).toArray(), new int[]{MIN_VALUE}); assertArrayEquals(revRange(MAX_VALUE - 1, MAX_VALUE).toArray(), new int[]{MAX_VALUE - 1}); }
без внешнего lib...
import java.util.List; import java.util.Collections; import java.util.stream.Collector; public class MyCollectors { public static <T> Collector<T, ?, List<T>> toListReversed() { return Collectors.collectingAndThen(Collectors.toList(), l -> { Collections.reverse(l); return l; }); } }
вы можете определить свой собственный коллектор, который собирает элементы в обратном порядке:
public static <T> Collector<T, List<T>, List<T>> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::<T>reverse); }и использовать его как:
stream.collect(inReverse()).forEach(t -> ...)Я использую ArrayList в прямом порядке, чтобы эффективно вставить сбор элементов (в конце списка) и списки Guava.обратный, чтобы эффективно дать обратный вид списка, не делая еще одну копию его.
вот несколько тестовых случаев для пользовательского коллектора:
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import org.hamcrest.Matchers; import org.junit.Test; import com.google.common.collect.Lists; public class TestReverseCollector { private final Object t1 = new Object(); private final Object t2 = new Object(); private final Object t3 = new Object(); private final Object t4 = new Object(); private final Collector<Object, List<Object>, List<Object>> inReverse = inReverse(); private final Supplier<List<Object>> supplier = inReverse.supplier(); private final BiConsumer<List<Object>, Object> accumulator = inReverse.accumulator(); private final Function<List<Object>, List<Object>> finisher = inReverse.finisher(); private final BinaryOperator<List<Object>> combiner = inReverse.combiner(); @Test public void associative() { final List<Object> a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List<Object> r1 = finisher.apply(a1); final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); final List<Object> a3 = supplier.get(); accumulator.accept(a3, t2); final List<Object> r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r1, Matchers.equalTo(r2)); } @Test public void identity() { final List<Object> a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List<Object> r1 = finisher.apply(a1); final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List<Object> r2 = finisher.apply(combiner.apply(a2, supplier.get())); assertThat(r1, equalTo(r2)); } @Test public void reversing() throws Exception { final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List<Object> a3 = supplier.get(); accumulator.accept(a3, t3); accumulator.accept(a3, t4); final List<Object> r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r2, contains(t4, t3, t2, t1)); } public static <T> Collector<T, List<T>, List<T>> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::<T>reverse); } }
Циклоп-реагировать StreamUtils имеет метод обратного потока (документация).
StreamUtils.reverse(Stream.of("1", "2", "20", "3")) .forEach(System.out::println);Он работает, собирая в ArrayList, а затем используя класс ListIterator, который может повторяться в любом направлении, чтобы повторять назад по списку.
Если у вас уже есть список, он будет более эффективным!--7-->
StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3")) .forEach(System.out::println);
если реализовано сопоставимое
(ex. Целое число, строка, дата), вы можете сделать это с помощью компаратор.reverseOrder(). List<Integer> list = Arrays.asList(1, 2, 3, 4); list.stream() .sorted(Comparator.reverseOrder()) .forEach(System.out::println);
вот решение, которое я придумал:
private static final Comparator<Integer> BY_ASCENDING_ORDER = Integer::compare; private static final Comparator<Integer> BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed();затем с помощью этих компараторов:
IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc...
самый простой способ (простой сбор-поддерживает параллельные потоки):
public static <T> Stream<T> reverse(Stream<T> stream) { return stream .collect(Collector.of( () -> new ArrayDeque<T>(), ArrayDeque::addFirst, (q1, q2) -> { q2.addAll(q1); return q2; }) ) .stream(); }функции (поддерживает параллельные потоки в непрерывном режиме):
public static <T> Stream<T> reverse(Stream<T> stream) { Objects.requireNonNull(stream, "stream"); class ReverseSpliterator implements Spliterator<T> { private Spliterator<T> spliterator; private final Deque<T> deque = new ArrayDeque<>(); private ReverseSpliterator(Spliterator<T> spliterator) { this.spliterator = spliterator; } @Override @SuppressWarnings({"StatementWithEmptyBody"}) public boolean tryAdvance(Consumer<? super T> action) { while(spliterator.tryAdvance(deque::addFirst)); if(!deque.isEmpty()) { action.accept(deque.remove()); return true; } return false; } @Override public Spliterator<T> trySplit() { // After traveling started the spliterator don't contain elements! Spliterator<T> prev = spliterator.trySplit(); if(prev == null) { return null; } Spliterator<T> me = spliterator; spliterator = prev; return new ReverseSpliterator(me); } @Override public long estimateSize() { return spliterator.estimateSize(); } @Override public int characteristics() { return spliterator.characteristics(); } @Override public Comparator<? super T> getComparator() { Comparator<? super T> comparator = spliterator.getComparator(); return (comparator != null) ? comparator.reversed() : null; } @Override public void forEachRemaining(Consumer<? super T> action) { // Ensure that tryAdvance is called at least once if(!deque.isEmpty() || tryAdvance(action)) { deque.forEach(action); } } } return StreamSupport.stream(new ReverseSpliterator(stream.spliterator()), stream.isParallel()); }Примечание Вы можете быстро распространяется на другие типы потоков (IntStream, ...).
тестирование:
// Use parallel if you wish only revert(Stream.of("One", "Two", "Three", "Four", "Five", "Six").parallel()) .forEachOrdered(System.out::println);результаты:
Six Five Four Three Two Oneдополнительная информация: The
simplest wayэто не так полезно при использовании с другие операции потока (соединение collect нарушает параллелизм). Элементadvance wayне имеет этой проблемы, и он сохраняет также начальные характеристики потока, напримерSORTED, и так, это способ перейти к использованию с другими потоковыми операциями после обратного.
Я бы предложил использовать jOOλ, Это отличная библиотека, которая добавляет много полезных функций для потоков Java 8 и лямбд.
затем вы можете сделать следующее:
List<Integer> list = Arrays.asList(1,2,3,4); Seq.seq(list).reverse().forEach(System.out::println)просто. Это довольно легкая библиотека, и ее стоит добавить в любой проект Java 8.
можно было бы написать коллекционером, который собирает элементы в обратном порядке:
public static <T> Collector<T, ?, Stream<T>> reversed() { return Collectors.collectingAndThen(Collectors.toList(), list -> { Collections.reverse(list); return list.stream(); }); }и использовать его так:
Stream.of(1, 2, 3, 4, 5).collect(reversed()).forEach(System.out::println);оригинальный ответ (содержит ошибку - он не работает правильно для параллельных потоков):
метод обратного потока общего назначения может выглядеть так:
public static <T> Stream<T> reverse(Stream<T> stream) { LinkedList<T> stack = new LinkedList<>(); stream.forEach(stack::push); return stack.stream(); }
Для справки, я смотрел на ту же проблему, я хотел присоединиться к строкового значения элементов потока в обратном порядке.
itemList = { last, middle, first } => первый, средний, последний
Я начал использовать промежуточную коллекцию с
collectingAndThenС comonad илиArrayDequeколлекционер Стюарт Марки, хотя я не был доволен промежуточной коллекцией, и потоковое сноваitemList.stream() .map(TheObject::toString) .collect(Collectors.collectingAndThen(Collectors.toList(), strings -> { Collections.reverse(strings); return strings; })) .stream() .collect(Collector.joining());поэтому я повторил Стюарт Маркс отвечает, что использовал
Collector.ofфабрика, которая имеет интересное финишер лямда.itemList.stream() .collect(Collector.of(StringBuilder::new, (sb, o) -> sb.insert(0, o), (r1, r2) -> { r1.insert(0, r2); return r1; }, StringBuilder::toString));так как в этом случае поток не параллелен, объединитель не имеет значения, что много, я использую
insertво всяком случае, ради согласованности кода, но это не имеет значения, поскольку это будет зависеть от того, какой stringbuilder строится первым.Я посмотрел на StringJoiner, однако он не имеет
insertметод.
отвечая на конкретный вопрос обращения с IntStream, ниже работал для меня:
IntStream.range(0, 10) .map(x -> x * -1) .sorted() .map(Math::abs) .forEach(System.out::println);
Как насчет этого метода?
public static <T> Stream<T> getReverseStream(List<T> list) { final ListIterator<T> listIt = list.listIterator(list.size()); final Iterator<T> reverseIterator = new Iterator<T>() { @Override public boolean hasNext() { return listIt.hasPrevious(); } @Override public T next() { return listIt.previous(); } }; return StreamSupport.stream(Spliterators.spliteratorUnknownSize( reverseIterator, Spliterator.ORDERED | Spliterator.IMMUTABLE), false); }Кажется, работает со всеми случаями без дублирования.
что касается конкретного вопроса о создании вспять
IntStream-Java 9 представил альтернативуIntStream.iterate(...)метод, который можно легко использовать для итерации в обратном порядке:IntStream.iterate(10, x -> x >= 0, x -> x - 1).forEach(System.out::println); // Out: 10 9 8 7 6 5 4 3 2 1 0способ описание:
IntStream.iterate(int seed, IntPredicate hasNext, IntUnaryOperator next);
seed- начальный элемент;hasNext- предикат для применения к элементам, чтобы определить, когда поток должен завершиться;next- функция, которая будет применена к предыдущему элементу для получения новый элемент.
не чисто Java8, но если вы используете списки гуавы.метод reverse() в сочетании, вы можете легко достичь этого:
List<Integer> list = Arrays.asList(1,2,3,4); Lists.reverse(list).stream().forEach(System.out::println);
в Java 8 способ сделать это:
List<Integer> list = Arrays.asList(1,2,3,4); Comparator<Integer> comparator = Integer::compare; list.stream().sorted(comparator.reversed()).forEach(System.out::println);
вот как я это делаю.
мне не нравится идея создания новой коллекции и обратного ее повторения.
идея IntStream#map довольно аккуратна, но я предпочитаю метод IntStream#iterate, поскольку я думаю, что идея обратного отсчета до нуля лучше выражена с помощью метода iterate и легче понять с точки зрения ходьбы по массиву сзади вперед.
import static java.lang.Math.max; private static final double EXACT_MATCH = 0d; public static IntStream reverseStream(final int[] array) { return countdownFrom(array.length - 1).map(index -> array[index]); } public static DoubleStream reverseStream(final double[] array) { return countdownFrom(array.length - 1).mapToDouble(index -> array[index]); } public static <T> Stream<T> reverseStream(final T[] array) { return countdownFrom(array.length - 1).mapToObj(index -> array[index]); } public static IntStream countdownFrom(final int top) { return IntStream.iterate(top, t -> t - 1).limit(max(0, (long) top + 1)); }вот некоторые тесты, чтобы доказать это работает:
import static java.lang.Integer.MAX_VALUE; import static org.junit.Assert.*; @Test public void testReverseStream_emptyArrayCreatesEmptyStream() { Assert.assertEquals(0, reverseStream(new double[0]).count()); } @Test public void testReverseStream_singleElementCreatesSingleElementStream() { Assert.assertEquals(1, reverseStream(new double[1]).count()); final double[] singleElementArray = new double[] { 123.4 }; assertArrayEquals(singleElementArray, reverseStream(singleElementArray).toArray(), EXACT_MATCH); } @Test public void testReverseStream_multipleElementsAreStreamedInReversedOrder() { final double[] arr = new double[] { 1d, 2d, 3d }; final double[] revArr = new double[] { 3d, 2d, 1d }; Assert.assertEquals(arr.length, reverseStream(arr).count()); Assert.assertArrayEquals(revArr, reverseStream(arr).toArray(), EXACT_MATCH); } @Test public void testCountdownFrom_returnsAllElementsFromTopToZeroInReverseOrder() { assertArrayEquals(new int[] { 4, 3, 2, 1, 0 }, countdownFrom(4).toArray()); } @Test public void testCountdownFrom_countingDownStartingWithZeroOutputsTheNumberZero() { assertArrayEquals(new int[] { 0 }, countdownFrom(0).toArray()); } @Test public void testCountdownFrom_doesNotChokeOnIntegerMaxValue() { assertEquals(true, countdownFrom(MAX_VALUE).anyMatch(x -> x == MAX_VALUE)); } @Test public void testCountdownFrom_givesZeroLengthCountForNegativeValues() { assertArrayEquals(new int[0], countdownFrom(-1).toArray()); assertArrayEquals(new int[0], countdownFrom(-4).toArray()); }
самый общий и самый простой способ отменить список будет:
public static <T> void reverseHelper(List<T> li){ li.stream() .sorted((x,y)-> -1) .collect(Collectors.toList()) .forEach(System.out::println); }
Comments
<p>IntStream.iterate(arr.length - 1, i -> i >= 0, i -> i - 1).forEach(i -> System.out.println(arr[i]));</p>