簡體   English   中英

Java 8 流逆序

[英]Java 8 stream reverse order

一般問題:反轉流的正確方法是什么? 假設我們不知道流由什么類型的元素組成,那么反轉任何流的通用方法是什么?

具體問題:

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: IntStream類型中的方法sorted()不適用於參數 ( Integer::compare )

我在這里想念什么?

對於生成反向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 不存儲任何元素。

因此,如果不將元素存儲在某個中間集合中,就不可能以相反的順序迭代元素。

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排序或反轉,但這不必要地需要中間存儲。 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});
}

沒有外部庫...

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

}

如果實現Comparable<T> (例如IntegerStringDate ),您可以使用Comparator.reverseOrder()

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

您可以定義自己的收集器,以相反的順序收集元素:

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

如何不這樣做:

  • 不要使用.sorted(Comparator.reverseOrder()).sorted(Collections.reverseOrder()) ,因為它只會按降序對元素進行排序。
    將它用於給定的整數輸入:
    [1, 4, 2, 5, 3]
    輸出如下:
    [5, 4, 3, 2, 1]
    對於字符串輸入:
    ["A", "D", "B", "E", "C"]
    輸出如下:
    [E, D, C, B, A]
  • 不要使用.sorted((a, b) -> -1) (最后解釋)

正確執行此操作的最簡單方法:

List<Integer> list = Arrays.asList(1, 4, 2, 5, 3);
Collections.reverse(list);
System.out.println(list);

輸出:
[3, 5, 2, 4, 1]

String

List<String> stringList = Arrays.asList("A", "D", "B", "E", "C");
Collections.reverse(stringList);
System.out.println(stringList);

輸出:
[C, E, B, D, A]

不要使用.sorted((a, b) -> -1)
它違反了比較器合同,可能僅適用於某些情況,即。 僅在單線程上,而不是在並行上。
洋基解釋:

(a, b) -> -1破壞了Comparator的契約。 這是否有效取決於排序算法的實現。 JVM 的下一個版本可能會打破這一點。 實際上我已經可以在我的機器上使用IntStream.range(0, 10000).parallel().boxed().sorted((a, b) -> -1).forEachOrdered(System.out::println);

//Don't use this!!!
List<Integer> list = Arrays.asList(1, 4, 2, 5, 3);
List<Integer> reversedList = list.stream()
        .sorted((a, b) -> -1)
        .collect(Collectors.toList());
System.out.println(reversedList);

正例輸出:
[3, 5, 2, 4, 1]

並行流或其他 JVM 實現中的可能輸出:
[4, 1, 2, 3, 5]

String

//Don't use this!!!
List<String> stringList = Arrays.asList("A", "D", "B", "E", "C");
List<String> reversedStringList = stringList.stream()
        .sorted((a, b) -> -1)
        .collect(Collectors.toList());
System.out.println(reversedStringList);

正例輸出:
[C, E, B, D, A]

並行流或其他 JVM 實現中的可能輸出:
[A, E, B, D, C]

cyclops -react StreamUtils 有一個反向流方法( javadoc )。

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

它的工作原理是收集到一個 ArrayList,然后利用可以在任一方向迭代的 ListIterator 類來向后迭代列表。

如果你已經有一個List,效率會更高

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

我建議使用jOOλ ,它是一個很棒的庫,它為 Java 8 流和 lambdas 添加了許多有用的功能。

然后,您可以執行以下操作:

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

就那么簡單。 這是一個非常輕量級的庫,非常值得添加到任何 Java 8 項目中。

這是我想出的解決方案:

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

似乎適用於所有情況而不會重復。

List newStream = list.stream().sorted(Collections.reverseOrder()).collect(Collectors.toList());
        newStream.forEach(System.out::println);

最簡單的方法(簡單收集 - 支持並行流):

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 advance way沒有這個問題,它也保留了流的初始特征,例如SORTED ,因此,它是反向后與其他流操作一起使用的方式。

可以編寫一個以相反順序收集元素的收集器:

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 } => first,middle,last

我開始使用來自comonad 的帶有collectingAndThen的中間集合或Stuart MarksArrayDeque收集器,盡管我對中間集合不滿意,然后再次流式傳輸

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

所以我迭代了使用Collector.of工廠的 Stuart Marks 答案,它有一個有趣的終結者lambda。

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方法。

關於生成反向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,但如果您結合使用 guava 的 Lists.reverse() 方法,您可以輕松實現這一點:

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

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

反轉字符串或任何數組

(Stream.of("abcdefghijklm 1234567".split("")).collect(Collectors.collectingAndThen(Collectors.toList(),list -> {Collections.reverse(list);return list;}))).stream().forEach(System.out::println);

split 可以根據分隔符或空格進行修改

回答與 IntStream 反轉的具體問題,以下對我有用:

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

最簡單的解決方案是使用List::listIteratorStream::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);

此方法適用於任何 Stream 並且符合 Java 8:

Stream<Integer> myStream = Stream.of(1, 2, 3, 4, 5);
myStream.reduce(Stream.empty(),
        (Stream<Integer> a, Integer b) -> Stream.concat(Stream.of(b), a),
        (a, b) -> Stream.concat(b, a))
        .forEach(System.out::println);
How about reversing the Collection backing the stream prior?

import java.util.Collections;
import java.util.List;

public void reverseTest(List<Integer> sampleCollection) {
    Collections.reverse(sampleCollection); // remember this reverses the elements in the list, so if you want the original input collection to remain untouched clone it first.

    sampleCollection.stream().forEach(item -> {
      // you op here
    });
}

我就是這樣做的。

我不喜歡創建一個新集合並反向迭代它的想法。

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

在所有這些中,我沒有看到我首先要回答的答案。

這並不完全是問題的直接答案,但它是問題的潛在解決方案。

只需首先向后構建列表。 如果可以,請使用 LinkedList 而不是 ArrayList,並且在添加項目時使用“Push”而不是添加。 該列表將以相反的順序構建,然后無需任何操作即可正確流式傳輸。

這不適合處理已經以各種方式使用但在數量驚人的情況下運行良好的原始數組或列表的情況。

基於@stuart-marks 的回答,但沒有強制轉換,函數返回從結尾開始的列表元素流:

public static <T> Stream<T> reversedStream(List<T> tList) {
    final int size = tList.size();
    return IntStream.range(0, size)
            .mapToObj(i -> tList.get(size - 1 - i));
}

// usage
reversedStream(list).forEach(System.out::println);

What's the proper generic way to reverse a stream?

如果流未指定遇到順序,請不要。 ( !s.spliterator().hasCharacteristics(java.util.Spliterator.ORDERED) )

反轉列表的最通用和最簡單的方法是:

public static <T> void reverseHelper(List<T> li){

 li.stream()
.sorted((x,y)-> -1)
.collect(Collectors.toList())
.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);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM