Обратный порядок потока 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 sorted()
в типе IntStream
не применим для аргументов (Integer::compare
)
что мне здесь не хватает?
Ответы
Ответ 1
Для конкретного вопроса создания обратного 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()
factory.
Полный код:
Deque<String> output =
input.collect(Collector.of(
ArrayDeque::new,
(deq, t) -> deq.addFirst(t),
(d1, d2) -> { d2.addAll(d1); return d2; }));
Результатом является Deque
вместо List
, но это не должно быть большой проблемой, поскольку его можно легко повторить или передать в текущем порядке.
Ответ 2
Элегантное решение
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);
Ответ 3
Многие из решений здесь сортируют или реверсируют 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});
}
Ответ 4
Общий вопрос:
Stream не хранит никаких элементов.
Поэтому итерация элементов в обратном порядке невозможна без сохранения элементов в некоторой промежуточной коллекции.
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)
Ответ 5
Если реализовано Comparable<T>
(например, Integer
, String
, Date
), вы можете сделать это с помощью Comparator.reverseOrder()
.
List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.stream()
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
Ответ 6
без внешней библиотеки...
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;
});
}
}
Ответ 7
Вы можете определить свой собственный коллекционер, который собирает элементы в обратном порядке:
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 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<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);
}
}
Ответ 8
cyclops-react StreamUtils имеет обратный метод Stream (javadoc).
StreamUtils.reverse(Stream.of("1", "2", "20", "3"))
.forEach(System.out::println);
Он работает путем сбора в ArrayList, а затем с использованием класса ListIterator, который может выполнять итерацию в любом направлении, итератировать назад по списку.
Если у вас уже есть Список, он будет более эффективным
StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3"))
.forEach(System.out::println);
Ответ 9
Вот решение, которое я придумал:
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...
Ответ 10
Я бы предложил использовать jOOλ, это отличная библиотека, которая добавляет много полезных функций для потоков Java 8 и lambdas.
Затем вы можете сделать следующее:
List<Integer> list = Arrays.asList(1,2,3,4);
Seq.seq(list).reverse().forEach(System.out::println)
Просто. Это довольно легкая библиотека, и ее стоит добавить в любой проект на Java 8.
Ответ 11
Самый простой способ (простой сбор - поддержка параллельных потоков):
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
Дополнительные примечания: simplest way
он не очень полезен при использовании с другими потоковыми операциями (сборное соединение разрывает parallelism). advance way
не имеет этой проблемы, и он сохраняет также начальные характеристики потока, например SORTED
, и, таким образом, он способ использовать для других операций потока после обратного.
Ответ 12
Можно написать коллекционер, который собирает элементы в обратном порядке:
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();
}
Ответ 13
Как насчет этого метода утилиты?
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);
}
Кажется, что он работает со всеми случаями без дублирования.
Ответ 14
Что касается конкретного вопроса генерации реверса 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
- функция, применяемая к предыдущему элементу для создания
новый элемент.
Ответ 15
Для справки я рассматривал ту же проблему, я хотел присоединиться к строковому значению элементов потока в обратном порядке.
itemList = {последний, средний, первый} = > первый, средний, последний
Я начал использовать промежуточную коллекцию с collectingAndThen
из comonad или коллектора ArrayDeque
Stuart Marks, хотя я был недоволен промежуточной коллекцией и снова потоковым
itemList.stream()
.map(TheObject::toString)
.collect(Collectors.collectingAndThen(Collectors.toList(),
strings -> {
Collections.reverse(strings);
return strings;
}))
.stream()
.collect(Collector.joining());
Итак, я повторил ответ Стюарта Маркса, который использовал Collector.of
factory, у которого есть интересная лямбда-финишер.
itemList.stream()
.collect(Collector.of(StringBuilder::new,
(sb, o) -> sb.insert(0, o),
(r1, r2) -> { r1.insert(0, r2); return r1; },
StringBuilder::toString));
Так как в этом случае поток не параллелен, объединитель не очень уместен, я использую insert
в любом случае ради согласованности кода, но это не имеет значения, поскольку он будет зависеть от того, какой построитель построил сначала.
Я посмотрел на StringJoiner, однако у него нет метода insert
.
Ответ 16
Отвечая на конкретный вопрос об обращении с IntStream, ниже работал у меня:
IntStream.range(0, 10)
.map(x -> x * -1)
.sorted()
.map(Math::abs)
.forEach(System.out::println);
Ответ 17
Не чисто Java8, но если вы используете метод guava Lists.reverse(), вы можете легко достичь этого:
List<Integer> list = Arrays.asList(1,2,3,4);
Lists.reverse(list).stream().forEach(System.out::println);
Ответ 18
ArrayDeque
быстрее в стеке, чем Stack или LinkedList. "push()" вставляет элементы в передней части Deque
protected <T> Stream<T> reverse(Stream<T> stream) {
ArrayDeque<T> stack = new ArrayDeque<>();
stream.forEach(stack::push);
return stack.stream();
}
Ответ 19
Реверсивная строка или любой массив
(Stream.of("abcdefghijklm 1234567".split("")).collect(Collectors.collectingAndThen(Collectors.toList(),list -> {Collections.reverse(list);return list;}))).stream().forEach(System.out::println);
разделение может быть изменено в зависимости от разделителя или пробела
Ответ 20
самое простое решение - использовать List::listIterator
и Stream::generate
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
ListIterator<Integer> listIterator = list.listIterator(list.size());
Stream.generate(listIterator::previous)
.limit(list.size())
.forEach(System.out::println);
Ответ 21
Вот как я это делаю.
Мне не нравится идея создания новой коллекции и обратного ее повторения.
Идея карты 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 <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());
}
Ответ 22
Во всем этом я не вижу ответа, на который я бы пошел первым.
Это не совсем прямой ответ на этот вопрос, но это потенциальное решение проблемы.
Сначала создайте список в обратном порядке. Если вы можете, используйте LinkedList вместо ArrayList, и когда вы добавляете элементы, используйте "Push" вместо добавления. Список будет построен в обратном порядке и затем будет корректно работать без каких-либо манипуляций.
Это не подходит для случаев, когда вы имеете дело с примитивными массивами или списками, которые уже используются различными способами, но хорошо работают в удивительном числе случаев.
Ответ 23
Предполагая, что этот Stream
имеет List
:
Вы можете сравнить по индексу элементов, а затем использовать reversed()
. Если вы хотите вернуть его в List
:
list = list.stream()
.sorted(Comparator.comparing(list::indexOf).reversed())
.collect(Collectors.toList());
Если вы хотите записать его в Stream<T>
, где T
- тип List
:
Stream<T> stream = list.stream()
.sorted(Comparator.comparing(list::indexOf)
.reversed());
Пример:
List<String> list = new ArrayList<>(Arrays.asList("First", "Second", "Third", "Last"));
list.stream()
.sorted(Comparator.comparing(list::indexOf).reversed())
.collect(Collectors.toList())
.forEach(System.out::println);
Выход:
Last
Third
Second
First
Или с пользовательским классом:
public class Foo {
int num;
public Foo(int foo) {
num = foo;
}
public int getNum() {
return num;
}
}
А потом:
List<Foo> list = new ArrayList<>(Arrays.asList(new Foo(1), new Foo(2), new Foo(3), new Foo(4)));
list = list.stream()
.sorted(Comparator.comparing(list::indexOf).reversed())
.collect(Collectors.toList());
list.stream().map(Foo::getNum).forEach(System.out::println);
Выход:
4
3
2
1
Ответ 24
Самый общий и самый простой способ изменить список:
public static <T> void reverseHelper(List<T> li){
li.stream()
.sorted((x,y)-> -1)
.collect(Collectors.toList())
.forEach(System.out::println);
}
Ответ 25
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);