Обратный порядок streamа Java 8

Общий вопрос: каков правильный способ обратного streamа? Предполагая, что мы не знаем, из каких типов элементов состоит stream, каков общий способ обратного преобразования любого streamа?

Конкретный вопрос:

IntStream предоставляет метод диапазона для генерации целых чисел в определенном диапазоне IntStream.range(-range, 0) , теперь, когда я хочу изменить его диапазон переключения от 0 до отрицательного, не будет работать, также я не могу использовать Integer::compare

 List list = Arrays.asList(1,2,3,4); list.stream().sorted(Integer::compare).forEach(System.out::println); 

с IntStream Я получу эту ошибку компилятора

Ошибка: (191, 0) ajc: метод sorted sorted() в типе IntStream не применим для аргументов ( Integer::compare )

что мне здесь не хватает?

20 Solutions collect form web for “Обратный порядок streamа Java 8”

Для конкретного вопроса создания обратного IntStream попробуйте что-то вроде этого:

 static IntStream revRange(int from, int to) { return IntStream.range(from, to) .map(i -> to - i + from - 1); } 

Это позволяет избежать бокса и сортировки.

Для общего вопроса о том, как изменить stream любого типа, я не знаю, есть ли «правильный» способ. Есть несколько способов, о которых я могу думать. Оба заканчивают хранение элементов streamа. Я не знаю, как изменить stream без сохранения элементов.

Этот первый способ хранит элементы в массиве и считывает их в stream в обратном порядке. Обратите внимание, что, поскольку мы не знаем тип среды выполнения элементов streamа, мы не можем правильно вводить массив, требуя неконтролируемого переноса.

 @SuppressWarnings("unchecked") static  Stream reverse(Stream input) { Object[] temp = input.toArray(); return (Stream) IntStream.range(0, temp.length) .mapToObj(i -> temp[temp.length - i - 1]); } 

Другой метод использует сборщиков для накопления предметов в обратном списке. Это делает много вставок перед объектами ArrayList , поэтому происходит много копий.

 Stream input = ... ; List 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 этого предпочтительнее использовать ArrayDeque , который эффективно поддерживает вставку спереди. Небольшая морщина заключается в том, что мы не можем использовать трехмерную форму Stream.collect() ; он требует, чтобы содержимое второго arg было объединено с первым аргументом arg, и на Deque нет дополнительной операции с Deque . Вместо этого мы используем addAll() для добавления содержимого первого arg к концу второго, а затем возвращаем второе. Это требует использования метода Collector.of() .

Полный код:

 Deque output = input.collect(Collector.of( ArrayDeque::new, (deq, t) -> deq.addFirst(t), (d1, d2) -> { d2.addAll(d1); return d2; })); 

Результатом является Deque вместо List , но это не должно быть большой проблемой, так как его можно легко повторить или передать в обратном порядке.

Общий вопрос:

Поток не хранит никаких элементов.

Таким образом, итерационные элементы в обратном порядке невозможны без сохранения элементов в некоторой промежуточной коллекции.

 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 неверно, поскольку оно сортируется, НЕ меняет (если stream может иметь неупорядоченные элементы)

Конкретный вопрос:

Я нашел это простым, легким и интуитивно понятным (Копировать комментарий @Holger)

 IntStream.iterate(to - 1, i -> i - 1).limit(to - from) 

Многие из решений здесь сортируют или реверсируют IntStream , но для этого без необходимости требуется промежуточное хранилище. Решение Stuart Marks – это путь:

 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}); } 

Элегантное решение

 List list = Arrays.asList(1,2,3,4); list.stream() .boxed() // Converts Intstream to Stream .sorted(Collections.reverseOrder()) // Method on Stream .forEach(System.out::println); 

без внешней библиотеки …

 import java.util.List; import java.util.Collections; import java.util.stream.Collector; public class MyCollectors { public static  Collector> toListReversed() { return Collectors.collectingAndThen(Collectors.toList(), l -> { Collections.reverse(l); return l; }); } } 

Вы можете определить свой собственный сборщик, который собирает элементы в обратном порядке:

 public static  Collector, List> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::reverse); } 

И используйте его как:

 stream.collect(inReverse()).forEach(t -> ...) 

Я использую ArrayList в прямом порядке, чтобы эффективно вставлять элементы (в конце списка) и Guava Lists.reverse, чтобы эффективно отображать обратный просмотр списка без создания другой копии.

Вот несколько тестовых примеров для пользовательского коллектора:

 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, List> inReverse = inReverse(); private final Supplier> supplier = inReverse.supplier(); private final BiConsumer, Object> accumulator = inReverse.accumulator(); private final Function, List> finisher = inReverse.finisher(); private final BinaryOperator> combiner = inReverse.combiner(); @Test public void associative() { final List a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List r1 = finisher.apply(a1); final List a2 = supplier.get(); accumulator.accept(a2, t1); final List a3 = supplier.get(); accumulator.accept(a3, t2); final List r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r1, Matchers.equalTo(r2)); } @Test public void identity() { final List a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List r1 = finisher.apply(a1); final List a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List r2 = finisher.apply(combiner.apply(a2, supplier.get())); assertThat(r1, equalTo(r2)); } @Test public void reversing() throws Exception { final List a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List a3 = supplier.get(); accumulator.accept(a3, t3); accumulator.accept(a3, t4); final List r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r2, contains(t4, t3, t2, t1)); } public static  Collector, List> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::reverse); } } 

cyclops- react StreamUtils имеет обратный метод Stream ( javadoc ).

  StreamUtils.reverse(Stream.of("1", "2", "20", "3")) .forEach(System.out::println); 

Он работает путем сбора в ArrayList, а затем с использованием classа ListIterator, который может выполнять итерацию в любом направлении, итератировать назад по списку.

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

  StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3")) .forEach(System.out::println); 

Если реализовано Comparable (например, Integer, String, Date), вы можете сделать это с помощью Comparator.reverseOrder () .

 List list = Arrays.asList(1, 2, 3, 4); list.stream() .sorted(Comparator.reverseOrder()) .forEach(System.out::println); 

Вот решение, которое я придумал:

 private static final Comparator BY_ASCENDING_ORDER = Integer::compare; private static final Comparator BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed(); 

затем используя эти компараторы:

 IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc... 

Простейший способ (простой сбор – поддерживает параллельные streamи):

 public static  Stream reverse(Stream stream) { return stream .collect(Collector.of( () -> new ArrayDeque(), ArrayDeque::addFirst, (q1, q2) -> { q2.addAll(q1); return q2; }) ) .stream(); } 

Расширенный способ (поддерживает параллельные streamи в непрерывном режиме):

 public static  Stream reverse(Stream stream) { Objects.requireNonNull(stream, "stream"); class ReverseSpliterator implements Spliterator { private Spliterator spliterator; private final Deque deque = new ArrayDeque<>(); private ReverseSpliterator(Spliterator 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 trySplit() { // After traveling started the spliterator don't contain elements! Spliterator prev = spliterator.trySplit(); if(prev == null) { return null; } Spliterator 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()); } в public static  Stream reverse(Stream stream) { Objects.requireNonNull(stream, "stream"); class ReverseSpliterator implements Spliterator { private Spliterator spliterator; private final Deque deque = new ArrayDeque<>(); private ReverseSpliterator(Spliterator 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 trySplit() { // After traveling started the spliterator don't contain elements! Spliterator prev = spliterator.trySplit(); if(prev == null) { return null; } Spliterator 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()); } 

Обратите внимание, что вы можете быстро перейти к другим типам streamов (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 

Дополнительные примечания: simplest way не так полезен при использовании с другими streamовыми операциями (объединение сбоев прерывает параллелизм). advance way не имеет этой проблемы, и он также сохраняет исходные характеристики streamа, например SORTED , и поэтому он может использоваться для использования с другими streamовыми операциями после обратного.

Я бы предложил использовать jOOλ , это отличная библиотека, которая добавляет много полезных функций для streamов Java 8 и lambdas.

Затем вы можете сделать следующее:

 List list = Arrays.asList(1,2,3,4); Seq.seq(list).reverse().forEach(System.out::println) 

Просто как тот. Это довольно легкая библиотека, и ее стоит добавить в любой проект на Java 8.

Можно написать коллекционер, который собирает элементы в обратном порядке:

 public static  Collector> 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); 

Исходный ответ (содержит ошибку – он не работает правильно для параллельных streamов):

Обратный метод streamа общего назначения может выглядеть так:

 public static  Stream reverse(Stream stream) { LinkedList stack = new LinkedList<>(); stream.forEach(stack::push); return stack.stream(); } 

Отвечая на конкретный вопрос об обращении с IntStream, ниже работал для меня:

 IntStream.range(0, 10) .map(x -> x * -1) .sorted() .map(Math::abs) .forEach(System.out::println); 

Как насчет этого метода утилиты?

 public static  Stream getReverseStream(List list) { final ListIterator listIt = list.listIterator(list.size()); final Iterator reverseIterator = new Iterator() { @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); } 

Кажется, что он работает со всеми случаями без дублирования.

Что касается конкретного вопроса создания обратного IntStreamJava 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 – предикат для применения к элементам, чтобы определить, когда stream должен завершиться;
  • next – функция, которая будет применяться к предыдущему элементу для создания нового элемента.

Не чисто Java8, но если вы используете метод list.reverse () в guava, вы можете легко достичь этого:

 List list = Arrays.asList(1,2,3,4); Lists.reverse(list).stream().forEach(System.out::println); 

Java 8 способ сделать это:

  List list = Arrays.asList(1,2,3,4); Comparator comparator = Integer::compare; list.stream().sorted(comparator.reversed()).forEach(System.out::println); 

Для справки я рассматривал ту же проблему, я хотел присоединиться к строковому значению элементов streamа в обратном порядке.

itemList = {последний, средний, первый} => первый, средний, последний

Я начал использовать промежуточную коллекцию с collectAndThen из comonad или коллекционера ArrayDeque Stuart Marks , хотя я был недоволен промежуточной коллекцией и снова streamовым

 itemList.stream() .map(TheObject::toString) .collect(Collectors.collectingAndThen(Collectors.toList(), strings -> { Collections.reverse(strings); return strings; })) .stream() .collect(Collector.joining()); 

Поэтому я повторил ответ Стюарта Маркса, который использовал Collector.of фабрики, у которого есть интересная lambda- финишер .

 itemList.stream() .collect(Collector.of(StringBuilder::new, (sb, o) -> sb.insert(0, o), (r1, r2) -> { r1.insert(0, r2); return r1; }, StringBuilder::toString)); 

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

Я посмотрел на StringJoiner, однако у него нет метода insert .

Вот как я это делаю.

Мне не нравится идея создания новой коллекции и обратного ее повторения.

Идея карты IntStream # довольно аккуратная, но я предпочитаю метод итерации IntStream #, так как я думаю, что идея обратного отсчета к Zero лучше выражается с помощью метода итерации и легче понять с точки зрения перехода массива назад.

 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  Stream 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  void reverseHelper(List li){ li.stream() .sorted((x,y)-> -1) .collect(Collectors.toList()) .forEach(System.out::println); } 
  • Группировать по и суммировать объекты, как в SQL с Java lambdas?
  • Почему filter () после того, как flatMap () «не полностью» ленив в streamах Java?
  • forEach vs forEachOrdered в Java 8 Stream
  • Почему этот Java-stream работает дважды?
  • Потоки Java 8: почему параллельный stream медленнее?
  • Итерация двух Java-8-streamов вместе
  • Почему комбайнер необходим для метода уменьшения, который преобразует тип в java 8
  • Java 8 stream .min () и .max (): зачем это компилируется?
  • Пользовательский пул streamов в параллельном streamе Java 8
  • Есть ли сжатый способ перебора streamа с индексами в Java 8?
  • Можно ли использовать функцию объединителя коллектора для последовательных streamов?
  • Давайте будем гением компьютера.