问题 我该如何检查Stream 是否已排序?


Iterable<T>, 这很容易:

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

但我想不出一个干净的方法来做同样的事情 Stream<T> 避免在不必要时消耗所有元素。


6447
2018-05-28 14:14


起源

“避免在没有必要时使用所有元素”通过定义排序检查,是否必须消耗所有元素? - Andy Turner
@AndyTurner For [2, 1, 3, 4, 5, 6, ...],没有理由看过前两个元素。 - Tavian Barnes
哦没问题。但是你必须直到流的末尾才能知道它 是 排序,例如 [1, 2, 3, 4, 5, ............... <many elements later> 0]。 - Andy Turner
@AndyTurner所以那个案子属于“什么时候必须”,但我的情况属于“当它没有必要时”。 - Tavian Barnes
不确定这个问题是否有意义:流可能是无限的,并且流不能保证能够被消除两次。那么......也许Streams不是你正在做的选择吗? - GPI


答案:


有几种方法可以迭代连续的流对。例如,你可以检查 这个问题。当然我最喜欢的方法是使用 图书馆 我写了:

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

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


8
2018-05-28 14:43





你可以抓住Stream的底层分裂器并检查它是否具有SORTED特性。由于它是一个终端操作,你不能在之后使用Stream(但是你可以从这个spliterator创建另一个,参见 使用Java 8 JDK将Iterable转换为Stream)。

例如:

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 -> x + 1); 创造一个 SORTED 流,但没有关于迭代应用的函数的语义的知识。这同样适用 Stream.of(...)

如果管道是无限的,那么这是唯一知道的方法。如果没有,并且分裂者没有报告此特征,则需要浏览元素并查看它是否不满足您要查找的排序特征。

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


3
2018-05-28 14:23



如您的示例所示,可以有效地对流进行排序,而不具有SORTED特征...如果流具有SORTED特征,那么它将被排序,但如果它没有它那么它可能会也可能不会被排序...... - assylias
这两个流都是排序的。 - Tavian Barnes
@assylias那么你需要迭代其他所有元素但你不能确定除非流是有限的。如果你有一个无限的管道那么它是不可能的。 - Alexis C.
然后唯一的方法是迭代元素,直到找到一个不尊重你停止的合同的元素,就像你已经做的那样。还要在您的问题中明确说明您对来源的假设。 - Alexis C.
即使是分裂者也是如此 SORTED 它并不意味着它按照自然顺序排序。你应该至少检查一下 getComparator() 同样。 - Tagir Valeev


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

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

    return curr;
});

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

http://ideone.com/ZMGnVW


2
2018-05-28 14:36



这是一个非常黑客的方式。异常和流通常不能很好地混合(特别是在调试时)。 - llogiq
这绝对是hacky,虽然它有一个(小)优势,我还没有在另一个解决方案中看到。这种方法的目标是避免按照其他一些解决方案访问外部属性。 - Necreaux
据我所知,它不会遍历整个流,请参阅我的工作示例。 - Necreaux
它不会遍历整个流,但异常的开销可能会消除短路的所有潜在性能增益。 - Holger
@Necreaux:错了,对不起。 - Tagir Valeev


你可以用 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 从流中使用一个简单的循环。


1
2018-05-28 14:34



a)IMO甚至比这更大 for (T t : (Iterable<T>) stream::iterator) { ...  b)文档 allMatch 显式声明谓词必须是无状态的。 - Tavian Barnes
现在试试吧 IntStream s = IntStream.of(1, 2, 3); 和 s.parallel().allMatch(...).. - Alexis C.
@AlexisC。从未说它适用于并行流(OP从未要求它)。我知道这也是一个相当狡猾的解决方案。也许外卖是:只需使用迭代器。 - tobias_k
@tobias_k我并不特别关心顺序与并行,但我确实关心服从Stream的API契约。但是,似乎“只是使用迭代器”是解决方案,我只是希望有一个中途漂亮的方式来做到这一点。 - Tavian Barnes


一个天真的解决方案使用流的迭代器:

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

编辑:也可以使用分裂器来并行化任务,但增益会有问题,复杂性的增加可能不值得。


1
2018-05-28 14:36



这个解决方案并不天真。但我会删除 null - 检查它是不一致的。 - Holger


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

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 可能会以其他方式处理,但上述内容似乎最简单。


1
2018-05-28 15:30



的文档 allMatch 显式声明谓词必须是无状态的。 - Tavian Barnes
@TavianBarnes可并行化 - 我说它是顺序的。 - Joop Eggen
文档仍然强制要求顺序流(除非我错过了某处的异常)。 - Tavian Barnes