4

This question is just for fun.

I have this method:

private static String getBaseDomain(String fullDomain) {
    // we take the base domain from the usual xxx.yyy.basedomain.tld: we 
    // want only the last 2 elements: basedomain.tld
    List<String> elements = Arrays.asList(fullDomain.split("\\."));
    if( elements.size() > 2){
        elements = elements.subList(elements.size()-2, elements.size());
    }
    return String.join(".", elements);
}

And I'm wondering how to get the same result using the java stream API (actually, I'm wondering which method would be the most resource-efficient).

I can't figure how to get only the last 2 elements from a stream: limit(2) will give me the first two, and for skip(XXX) I don't know how to extract the size of the stream "inline".

Can you tell me how you would do?

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Gui13
  • 12,993
  • 17
  • 57
  • 104
  • I suggest to use some kind of fold with queue as accumulator and remove element from it when it reach certain size. – talex Oct 19 '16 at 15:44

4 Answers4

7

You could use skip:

elements.stream().skip(elements.size() - 2)

From the API:

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.

Probably useless example:

// a list made of a, b, c and d
List<String> l = Arrays.asList("a", "b", "c", "d");

// prints c and d
l.stream().skip(l.size() - 2).forEach(System.out::println);

Probably useless note:

As mentioned by a few, this only works if you have a size to work with, i.e. if you're streaming from a collection.

Quoting Nicolas, a stream doesn't have a size.

Community
  • 1
  • 1
Mena
  • 47,782
  • 11
  • 87
  • 106
  • If `elements` is `Stream` you can't do that. Because you use it twice. – talex Oct 19 '16 at 15:45
  • @talex oh, yes typo - fixed - elements is a `List` here. – Mena Oct 19 '16 at 15:46
  • Please note that this only works with a `collection` not with any `Stream` since a `Stream` doesn't have size – Nicolas Filotto Oct 19 '16 at 15:59
  • 1
    @NicolasFilotto absolutely. But I assume the use case here is to stream *from* a collection, see OP's scenario. – Mena Oct 19 '16 at 16:03
  • if it is really limited to a `List` like in the OP example, he had better to keep his code since using `subList` will still be better than using a `Stream`. – Nicolas Filotto Oct 19 '16 at 16:08
  • Agreed, but as the question states, it's "just for fun" :) – Mena Oct 19 '16 at 16:10
  • 2
    Probably the worst application of the double-curly-brace anti-pattern ever. How about `List l = Arrays.asList("a", "b", "c", "d");`? Or if it has to be an `ArrayList`: `List l = new ArrayList<>(); Collections.addAll(l, "a", "b", "c", "d");`… – Holger Oct 19 '16 at 18:13
  • @Holger fair enough, but this is a dirty example within a self-declared "useless example", answering a question "just for fun" - didn't think you'd get high expectations from the signals there. Anyway, yeah none of this is production-worthy, I think I made that clear enough. – Mena Oct 19 '16 at 20:20
  • I’m quite sure that a lot of SO code, not meant to be production-ready, will actually show up in production code, maybe with changes, but probably not fixing the really bad aspects. The other point is the habit showing through when being so used to the `{{…}}` anti-pattern that cleaner and even simpler alternatives are not considered. It’s easy to fix one piece of code, but very hard to get rid of a bad habit… – Holger Oct 20 '16 at 08:19
  • @Holger you're probably right. Sometimes it's hard not to be lazy though... – Mena Oct 20 '16 at 08:43
2

You could do a bit of inlining of your original approach, which I think shortens it up nicely, and you don't even have to use a stream:

    String[] a = fullDomain.split("\\.");
    return String.join(".", Arrays.asList(a)
                                  .subList(Math.max(0, a.length-2), a.length));

If you really want to use a stream, you can use the array-subrange stream source:

    String[] a = fullDomain.split("\\.");
    return Arrays.stream(a, Math.max(0, a.length-2), a.length)
                 .collect(Collectors.joining("."));

If all you have is a stream, whose size you don't have in advance, I'd just dump the elements into an ArrayDeque:

    final int N = 2;
    Stream<String> str = ... ;

    Deque<String> deque = new ArrayDeque<>(N);
    str.forEachOrdered(s -> {
        if (deque.size() == N) deque.removeFirst();
        deque.addLast(s);
    });
    return String.join(".", deque);

Of course, this isn't as general as writing a collector, but for simple cases it's probably just fine.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
1

If elements is a stream, you can write a custom collector to keep just the last K elements (there may well be a collector like this already):

List<?> lastK = ints.stream().collect(
    Collector.of(
        LinkedList::new,
        (listA, el) -> {
            listA.add(el);
            if (listA.size() > K) {
              listA.remove(0);
            }
        },
        (listA, listB) -> {
            while (listB.size() < K && !listA.isEmpty()) {
              listB.addFirst(listA.removeLast());
            }
            return listB;
        }));
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 2
    You could even use an `ArrayDeque` instead of `LinkedList` improving the performance of the accumulate and merge function, but at the expense of needing an `ArrayList::new` finisher if the result type has to be a `List`. – Holger Oct 19 '16 at 18:42
1

If it is an indexed collection you could use

IntStream.range(elements.size() - 2, elements.size()).mapToObj(elements::get).forEach(System.out::print);
Saravana
  • 12,647
  • 2
  • 39
  • 57