我应该如何检查 Stream<T> 是不是已排序?

Posted

技术标签:

【中文标题】我应该如何检查 Stream<T> 是不是已排序?【英文标题】:How should I check whether a Stream<T> is sorted?我应该如何检查 Stream<T> 是否已排序? 【发布时间】:2015-08-11 02:43:32 【问题描述】:

使用Iterable&lt;T&gt;,很容易:

T last = null;
for (T t : iterable) 
    if (last != null && last.compareTo(t) > 0) 
        return false;
    
    last = t;

return true;

但我想不出一种干净的方法来为 Stream&lt;T&gt; 做同样的事情,避免在不必要时消耗所有元素。

【问题讨论】:

"避免在不必要的情况下消耗所有元素" 根据排序检查的定义,它是否必须消耗所有元素? @AndyTurner 对于[2, 1, 3, 4, 5, 6, ...],没有理由忽略前两个元素。 哦,当然。但是您必须查看到流的末尾才能知道它 排序,例如[1, 2, 3, 4, 5, ............... &lt;many elements later&gt; 0]. 不确定这个问题是否有意义:流可能是无限的,并且不能保证流能够被消费两次。所以......也许 Streams 不是你正在做的事情的选择? @GPI 我不必使用它两次,而且我确信这个特定的流不是无限的。使用Stream API 已经使实现非常漂亮和简洁,现在我只想测试它...... 【参考方案1】:

您可以获取 Stream 的底层拆分器并检查它是否具有 SORTED 特征。由于它是终端操作,因此您不能在之后使用 Stream(但您可以从此拆分器创建另一个,另见 Convert Iterable to Stream using Java 8 JDK)。

例如:

Stream<Integer> st = Stream.of(1, 2, 3);
//false
boolean isSorted = st.spliterator().hasCharacteristics(Spliterator.SORTED);

Stream<Integer> st = Stream.of(1, 2, 3).sorted();
//true
boolean isSorted = st.spliterator().hasCharacteristics(Spliterator.SORTED);

我的示例显示SORTED 特征仅在您从报告SORTED 特征的源获取流或您在管道上的某个点调用sorted() 时才会出现。

有人可能会争辩说Stream.iterate(0, x -&gt; x + 1); 创建了一个SORTED 流,但不知道迭代应用的函数的语义。这同样适用于Stream.of(...)

如果管道是无限的,那么它是唯一知道的方法。如果不是,并且拆分器没有报告此特征,则您需要遍历元素并查看它是否不满足您正在寻找的排序特征。

这是您已经使用迭代器方法完成的操作,但是您需要消耗 Stream 的一些元素(在最坏的情况下,所有元素)。您可以使用一些额外的代码使任务可并行化,然后由您决定是否值得...

【讨论】:

如您的示例所示,可以在没有 SORTED 特征的情况下对流进行有效排序...如果流具有 SORTED 特征,则对其进行排序,但如果没有,则可能或者可能无法排序... 这两个流都已排序。 @assylias 那么您需要迭代其他所有元素,但除非流是有限的,否则您无法确定。如果您有无限的管道,那么这是不可能的。 那么唯一的方法是遍历元素,直到找到一个不尊重合同的元素,就像你已经做的那样。还要在您的问题中明确说明您对来源所做的假设。 另外,即使 spliterator 是 SORTED 也不意味着它是按照自然顺序排序的。您至少也应该检查getComparator()【参考方案2】:

您可以将allMatch 与多行 lambda 一起使用,并根据前一个值检查当前值。不过,您必须将最后一个值包装到一个数组中,以便 lambda 可以修改它。

// infinite stream with one pair of unsorted numbers
IntStream s = IntStream.iterate(0, x -> x != 1000 ? x + 2 : x - 1);
// terminates as soon as the first unsorted pair is found
int[] last = Integer.MIN_VALUE;
boolean sorted = s.allMatch(x -> 
    boolean b = x >= last[0]; last[0] = x; return b; 
);

或者,只需从流中获取 iterator 并使用简单的循环。

【讨论】:

a) IMO 这比for (T t : (Iterable&lt;T&gt;) stream::iterator) ... 更严重 b) allMatch 的文档明确声明谓词必须是无状态的。 现在试试IntStream s = IntStream.of(1, 2, 3);s.parallel().allMatch(...).. @AlexisC。从未说过它可以与并行流一起使用(并且 OP 从未要求过它)。我知道这也是一个相当老套的解决方案。也许要点是:只需使用迭代器。 @tobias_k 我不关心顺序与并行,但我确实关心遵守 Stream 的 API 合同。但是,是的,似乎“只使用迭代器”是解决方案,我只是希望有一种看起来不错的方式来做到这一点。【参考方案3】:

您可以劫持减少操作以保存最后一个值并将其与当前值进行比较,如果未排序则抛出异常:

.stream().reduce((last, curr) -> 
   if (((Comparable)curr).compareTo(last) < 0) 
       throw new Exception();
    

    return curr;
);

编辑:我分叉了另一个答案的示例并将其替换为我的代码,以显示它只执行必要数量的检查。

http://ideone.com/ZMGnVW

【讨论】:

这是一种很老套的方式。异常和流通常不能很好地混合(尤其是在调试时)。 这绝对是 hacky,虽然它有一个(小)优势我还没有在另一个解决方案中看到。这种方法的目标是避免按照其他一些解决方案访问外部属性。 据我所知,它并没有遍历整个流,请参阅我的工作示例.. 它不会遍历整个流,但异常的开销可能会扼杀短路的所有潜在性能增益。 这种方法应该被认为是不可靠的。 “reduce”函数的文档说明“但不限于按顺序执行”。我已经看到并行拆分器会发生这种情况。【参考方案4】:

一个简单的解决方案使用流的迭代器:

public static <T extends Comparable<T>> boolean isSorted(Stream<T> stream) 
    Iterator<T> i = stream.iterator();
    if(!i.hasNext()) return true;
    T current = i.next();
    while(i.hasNext()) 
        T next = i.next();
        if(current == null || current.compareTo(next) > 0) return false;
        current = next;
    
    return true;

编辑:也可以使用拆分器来并行化任务,但收益值得怀疑,而且复杂性的增加可能不值得。

【讨论】:

这个解决方案并不幼稚。但我会删除 null-check 因为它不一致。【参考方案5】:

有几种方法可以迭代流的连续对。例如,您可以查看this question。当然我最喜欢的方法是使用the library我写道:

boolean unsorted = StreamEx.of(sourceStream)
                           .pairMap((a, b) -> a.compareTo(b) > 0)
                           .has(true);

这是短路操作:一旦发现错误就会结束。它也适用于并行流。

【讨论】:

【参考方案6】:

这是一个顺序的、状态保持的解决方案:

IntStream stream = IntStream.of(3, 3, 5, 6, 6, 9, 10);
final AtomicInteger max = new AtomicInteger(Integer.MIN_VALUE);
boolean sorted = stream.allMatch(n -> n >= max.getAndSet(n));

并行化需要引入范围。状态,max 可能会以其他方式处理,但上面似乎最简单。

【讨论】:

allMatch 的文档明确声明谓词必须是无状态的。 @TavianBarnes 可并行化 - 我说它是顺序的。 文档仍然对顺序流施加了这个要求(除非我错过了某处声明的异常)。 我想知道是什么原因......我认为在单线程上下文中没有危险......【参考方案7】:

我不知道它有多好,但我有一个想法:

    从您的 Stream 、 Integer 或 Strings 或任何东西中创建一个列表。 我为List&lt;String&gt; listOfStream写了这篇文章:
        long countSorted = IntStream.range(1, listOfStream.size())
                .map(
                        index -> 
                            if (listOfStream.get(index).compareTo(listOfStream.get(index-1)) > 0) 
                                return 0;
                            
                            return index;
                        )
                .sum();

【讨论】:

所以如果你得到 countSorted == 0 ,, 它是排序的,如果 countSorted > 0 你的 Stream 没有排序

以上是关于我应该如何检查 Stream<T> 是不是已排序?的主要内容,如果未能解决你的问题,请参考以下文章

java 中类型前面有个<String> Stream是啥意思

将 CompletableFuture<Stream<T>> 转换为 Publisher<T> 是不是正确?

如何检查 IQueryable<T>.Element 类型是不是为接口

接口 java.util.stream.Stream<T> 中的方法映射不能应用于给定类型;

为啥 Iterable<T> 不提供 stream() 和 parallelStream() 方法?

java stream