14

With an Iterable<T>, it's easy:

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

But I can't think of a clean way to do the same thing for a Stream<T> that avoids consuming all the elements when it doesn't have to.

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118
  • 1
    "avoids consuming all the elements when it doesn't have to" By definition of a sorted check, doesn't it have to consume all the elements? – Andy Turner May 28 '15 at 14:17
  • 1
    @AndyTurner For `[2, 1, 3, 4, 5, 6, ...]`, there's no reason to look past the first two elements. – Tavian Barnes May 28 '15 at 14:18
  • 3
    Oh, sure. But you'd have to look until the end of the stream to know that it *is* sorted, e.g. `[1, 2, 3, 4, 5, ............... 0]`. – Andy Turner May 28 '15 at 14:18
  • @AndyTurner So that case falls under "when it has to," But mine falls under "when it doesn't have to." – Tavian Barnes May 28 '15 at 14:19
  • 3
    Not sure the question does make sense : streams may be infinite, and streams are not guaranteed to be able to be consummed twice. So... Maybe Streams are not an option for what you're doing ? – GPI May 28 '15 at 14:19
  • 1
    @GPI I don't have to consume it twice, and I know for sure that this particular stream is not infinite. Using the `Stream` API has made the implementation very pretty and concise, now I just want to test it... – Tavian Barnes May 28 '15 at 14:21
  • I think you can throw an exception to make the stream stop iterating. – eckes May 28 '15 at 14:48

7 Answers7

10

There are several methods to iterate over the successive pairs of the stream. For example, you can check this question. Of course my favourite method is to use the library I wrote:

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

It's short-circuit operation: it will finish as soon as it find the misorder. Also it works fine with parallel streams.

Community
  • 1
  • 1
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
3

You can grab the Stream's underlying spliterator and check it it has the SORTED characteristic. Since it's a terminal operation, you can't use the Stream after (but you can create another one from this spliterator, see also Convert Iterable to Stream using Java 8 JDK).

For example:

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

My example shows that the SORTED characteristic appears only if you get the Stream from a source's that reports the SORTED characteristic or you call sorted() at a point on the pipeline.

One could argue that Stream.iterate(0, x -> x + 1); creates a SORTED stream, but there is no knowledge about the semantic of the function applied iteratively. The same applies for Stream.of(...).

If the pipeline is infinite then it's the only way to know. If not, and that the spliterator does not report this characteristic, you'd need to go through the elements and see if it does not satisfy the sorted characteristic you are looking for.

This is what you already done with your iterator approach but then you need to consume some elements of the Stream (in the worst case, all elements). You can make the task parallelizable with some extra code, then it's up to you to see if it's worth it or not...

Community
  • 1
  • 1
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
  • 3
    As your example shows, a stream could be effectively sorted without having the SORTED characteristic... IF the stream has the SORTED characteristic THEN it is sorted, but IF it doesn't have it THEN it may or may not be sorted... – assylias May 28 '15 at 14:24
  • 1
    Both those streams are sorted. – Tavian Barnes May 28 '15 at 14:25
  • @assylias Well then you need to iterate other all the elements but you can't be sure unless the stream is finite. If you have an infinite pipeline then it's not possible. – Alexis C. May 28 '15 at 14:25
  • @TavianBarnes Technically, the first is not sorted. Here's you sees it because I used a finite number of values. But imagine that you don't have access to a method that returns a `Stream` (using `Stream.of` or not is not relevant), how would you know if it's sorted? – Alexis C. May 28 '15 at 14:28
  • I know it's not `SORTED`, but it is *sorted*. And I don't need access to a method that returns a `Stream` (although in this case I do), I can easily check if a single stream is sorted with `for (T t : (Iterable) stream::iterator) { ...`. I'm looking for a non-gross way to do that. – Tavian Barnes May 28 '15 at 14:31
  • @TavianBarnes And what if the pipeline is infinite? – Alexis C. May 28 '15 at 14:32
  • @AlexisC. Why the hypothetical? `Iterable`s can be infinite too, but it makes sense to check if a finite one is sorted. – Tavian Barnes May 28 '15 at 14:32
  • So tell me how you can check an infinite source of numbers to be sorted? Nothing tells you that if the first N elements are sorted, then the N+1 will be. – Alexis C. May 28 '15 at 14:34
  • Of course you can't do that. But as I *don't* have an infinite stream, it's largely irrelevant. – Tavian Barnes May 28 '15 at 14:35
  • 1
    Then the only way is to iterate through the elements and until you find one that doesn't respect the contract you stop, as you already do. Also make it clear in your question about the assumptions you made about the source. – Alexis C. May 28 '15 at 14:36
  • 2
    Also even if spliterator is `SORTED` it does not mean that it's sorted according to the natural order. You should at least check the `getComparator()` as well. – Tagir Valeev May 28 '15 at 17:03
3

This is a sequential, state holding solution:

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

Parallelizing would need to introduce ranges. The state, max might be dealt with otherwise, but the above seems most simple.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
2

You could hijack a reduction operation to save the last value and compare it to the current value and throw an exception if it isn't sorted:

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

    return curr;
});

EDIT: I forked another answer's example and replaced it with my code to show it only does the requisite number of checks.

http://ideone.com/ZMGnVW

Necreaux
  • 9,451
  • 7
  • 26
  • 43
  • 4
    That is quite a hacky way. Exceptions and Streams usually don't mix well (especially when debugging). – llogiq May 28 '15 at 14:37
  • This is definitely hacky, although it has one (small) advantage I have yet to see in another solution. The goal in this approach was to avoid accessing an external property as per some of the other solutions. – Necreaux May 28 '15 at 14:41
  • 1
    It doesn't traverse the whole stream as far as I can tell, see my working example.. – Necreaux May 28 '15 at 15:00
  • It doesn’t traverse the whole stream but the overhead of the exception will likely kill all potential performance gain of the short-circuiting. – Holger May 28 '15 at 16:31
  • This approach should be considered unreliable. The documentation for the "reduce" function states "but is not constrained to execute sequentially". I've seen this happen with parallel split Spliterators. – astellin Mar 31 '20 at 16:42
1

You could use allMatch with a multi-line lambda, checking the current value against the previous one. You'll have to wrap the last value into an array, though, so the lambda can modify it.

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

Alternatively, just get the iterator from the stream and use a simple loop.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • a) IMO this is even grosser than `for (T t : (Iterable) stream::iterator) { ...` b) The docs for `allMatch` explicitly state the predicate must be stateless. – Tavian Barnes May 28 '15 at 14:37
  • 2
    Now try with `IntStream s = IntStream.of(1, 2, 3);` and `s.parallel().allMatch(...)`.. – Alexis C. May 28 '15 at 14:38
  • @AlexisC. Never said it worked with parallel streams (and OP never asked for it). I am aware that this, too, is a rather hacky solution. Maybe the takeaway is: Just use the iterator. – tobias_k May 28 '15 at 14:49
  • @tobias_k I don't care about sequential vs. parallel specifically, but I do care about obeying the API contract of Stream. But yeah it seems like "just use the iterator" is the solution, I just wish there was a halfway nice-looking way to do that. – Tavian Barnes May 28 '15 at 15:23
1

A naive solution uses the stream's Iterator:

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

Edit: It would also be possible to use a spliterator to parallelize the task, but the gains would be questionable and the increase in complexity is probably not worth it.

llogiq
  • 13,815
  • 8
  • 40
  • 72
0

I don't know how good it is , but i have just got an idea:

  1. Make a list out of your Stream , Integer or Strings or anything.
  2. i have written this for a 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();

Mori
  • 107
  • 9
  • so if you get countSorted == 0 ,,, it is sorted and if countSorted > 0 your Stream is not sorted – Mori Nov 29 '19 at 13:32