50

I am wondering is there an alternative to

List<X> lastN = all.subList(Math.max(0, all.size() - n), all.size()); 

with stream usage?

3
  • 1
    I don't think this is generally possible with streams, as a stream's size may not be known a priori, or it may even be infinite. And if you create the stream from a list, just use sublist, as you did. Commented May 27, 2015 at 8:04
  • 1
    @tobias_k the OP seems to have a finite list however... Commented May 27, 2015 at 8:06
  • 1
    If you already have a list, then subList is the way to go. You can then copy it, stream it, whatever else you want. Commented May 27, 2015 at 16:28

4 Answers 4

45

Use Stream.skip()

Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. If this stream contains fewer than n elements then an empty stream will be returned.

all.stream().skip(Math.max(0, all.size() - n)).forEach(doSomething); 
Sign up to request clarification or add additional context in comments.

3 Comments

This doesn't return the last n elements. It skips the first n. not what OP asked for.
what if we do not know about stream size (assume worst case)?
This throws IllegalArgumentException if all.size() < n. The Math.max(0, all.size() - n) from the original question will be necessary here as well.
33

A custom collector can be written like this:

public static <T> Collector<T, ?, List<T>> lastN(int n) { return Collector.<T, Deque<T>, List<T>>of(ArrayDeque::new, (acc, t) -> { if(acc.size() == n) acc.pollFirst(); acc.add(t); }, (acc1, acc2) -> { while(acc2.size() < n && !acc1.isEmpty()) { acc2.addFirst(acc1.pollLast()); } return acc2; }, ArrayList::new); } 

And use it like this:

List<String> lastTen = input.stream().collect(lastN(10)); 

3 Comments

You don’t need to write ArrayList<T>::new, just ArrayList::new is enough as method references always use type inference rather than raw types (like if the “diamond operator” was always present when using ::new). You already use it with ArrayDeque::new. Btw. I’d prefer removeFirst/removeLast over pollFirst/pollLast here…
@Holger, first I wrote ArrayList::new, but Eclipse displayed a warning about unchecked constructor. Well, probably that's an ECJ-specific problem.
Interestingly, the ArrayDeque::new would benefit from a type witness, as using ArrayDeque<T>::new would make the type witness at the Collector.of call obsolete (and <T> is simpler than <T, Deque<T>, List<T>>) whereas the type witness at ArrayList<T>::new is not necessary as its type can be inferred from the target type.
5

Sometimes I need a "oneliner" (in this case a three liner) as creating a collector is just too much fuss.

If the stream is small then it is possible to reverse, limit and reverse again without much sacrificing performance. This will result the last n elements.

It is useful if filtering is required as in that case it is not possible to specify the size.

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9) .filter(i -> i % 2 == 0) .sorted(Comparator.reverseOrder()) .limit(2) .sorted(Comparator.naturalOrder()) .forEach(System.out::println); // prints 6 8 

2 Comments

Shouldn't the second sort use natural order instead of reverse order?
Yes, you are correct. I updated the answer. Also it is important to note that my this solution works on ordered lists.
4

In case the stream has unknown size, there's probably no way around consuming the entire stream and buffering the last n elements encountered so far. You can do this using some kind of deque, or a specialized ring-buffer automatically maintaining its maximum size (see this related question for some implementations).

public static <T> List<T> lastN(Stream<T> stream, int n) { Deque<T> result = new ArrayDeque<>(n); stream.forEachOrdered(x -> { if (result.size() == n) { result.pop(); } result.add(x); }); return new ArrayList<>(result); } 

All of those operations (size, pop, add) should have complexity of O(1), so the overall complexity for a stream with (unknown) length n would be O(n).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.