我应该如何检查 Stream<T> 是不是已排序?
Posted
技术标签:
【中文标题】我应该如何检查 Stream<T> 是不是已排序?【英文标题】:How should I check whether a Stream<T> is sorted?我应该如何检查 Stream<T> 是否已排序? 【发布时间】:2015-08-11 02:43:32 【问题描述】:使用Iterable<T>
,很容易:
T last = null;
for (T t : iterable)
if (last != null && last.compareTo(t) > 0)
return false;
last = t;
return true;
但我想不出一种干净的方法来为 Stream<T>
做同样的事情,避免在不必要时消耗所有元素。
【问题讨论】:
"避免在不必要的情况下消耗所有元素" 根据排序检查的定义,它是否必须消耗所有元素? @AndyTurner 对于[2, 1, 3, 4, 5, 6, ...]
,没有理由忽略前两个元素。
哦,当然。但是您必须查看到流的末尾才能知道它 已 排序,例如[1, 2, 3, 4, 5, ............... <many elements later> 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 -> 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<T>) 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<String> 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> 中的方法映射不能应用于给定类型;