12

Is there a way to peek the next element in a stream? The idea rose from a stream of a list of objects, where two following objects should be compared (to smooth some diffs, but that shouldn't matter here). As an old for loop this would look like:

List<Car> autobahn = getCars();
for (int i = 0; i < autobahn.size()-1; i++) {
    if(autobahn.get(i).speed>autobahn.get(i+1).speed)
        autobahn.get(i).honk();
}

The best way so far as stream would be:

autobahn.stream()
            .limit(autobahn.size()-1)
            .filter(car -> car.speed < autobahn.get(autobahn.indexOf(car)+1).speed)
            .forEach(car -> car.honk());

The main-problem with this solution is the indexOf method, since there might be twice the same car on the autobahn. A better solution would be some way to peek the next (or the one before) element (with an helping class, this might be even possible, but looks horrible)

BoxedCar boxedCar = new BoxedCar(autobahn.get(0));
autobahn.stream()
            .skip(1)
            .filter(car -> boxedCar.setContent(car))
            .forEach(car -> car.winTheRace());

with helperclass

class BoxedCar {

    Car content;

    BoxedCar(Car content) {
        this.content = content;
    }
    boolean setContent(Car content) {
        double speed = this.content.speed;
        this.content = content;
        return content.speed > speed;
    }
}

or to divert the Stream<Car> into a kind of Stream<(Car,Car)> with the second stream somehow created by the first one (this sounds also awful and here I have no idea, how this would look).

Is there a nice way to do this with streams, or are we stuck to the for-loop?

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
ctst
  • 1,610
  • 1
  • 13
  • 28
  • 3
    The for loop is not ridiculous at all. Actually, this is probably the cleaner code you'll be able to write for this. You could always use a Stream over the indexes but that's it. – Tunaki Mar 02 '16 at 19:01
  • @Tunaki I said, the streams would look ridiculous, not the for-loop. So far I also stick with the for-loop for obvious reasons. I am just wondering, if there is a nice possibility to achieve this also with streams. Edit, you can understand that part wrong, I delete it. – ctst Mar 02 '16 at 19:02
  • Ah I misunderstood sorry. – Tunaki Mar 02 '16 at 19:03
  • 2
    No, there isn't a good way to do it with streams; streams are not designed to be used for this sort of operation. – Louis Wasserman Mar 02 '16 at 19:07
  • 1
    Actually, honking because the car before you is slower than yours is an abuse of the warning signal. – Holger Mar 03 '16 at 10:51

3 Answers3

3

Sticking with the for loop wouldn't be a bad idea. The Stream API isn't designed for this type of requirement. You can refer to that answer for more insight.

However, a simple way to do this using the Stream API would be to use a Stream over the indexes of your list, supposing that you have random access.

IntStream.range(0, autobahn.size() - 1)
         .filter(i -> autobahn.get(i).speed > autobahn.get(i+1).speed)
         .forEach(i -> autobahn.get(i).honk());

Note that this highly resemble the for loop.

Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • 2
    Resembles the `for` loop but makes parallel processing possible. – Erick G. Hagstrom Mar 02 '16 at 19:25
  • As @ErickG.Hagstrom mentioned, this might has a better performance. Somehow I just oversaw this elegant and simple solution. Also the link has some interesting answers to this aspect. I am not sure, if this answer is actually faster in my use-case (LinkedList), but it definitly satisfies my question. – ctst Mar 02 '16 at 20:09
  • 1
    @ctst If you have a `LinkedList`, this is definitely not the good answer, performance-wise. A LinkedList does not have random access so accessing by index is disastrous. In that case, it would be better to first convert to an `ArrayList`, or use a for loop with an iterator. – Tunaki Mar 02 '16 at 20:29
1

Using my free StreamEx library:

StreamEx.of(autobahn)
        .pairMap((car, nextCar) -> car.speed < nextCar.speed ? car : null)
        .nonNull()
        .forEach(Car::honk);

Here non-standard pairMap operation is used which can map the adjacent pair of elements to the single element. This works for any stream source (not only random-access indexed list) and can be parallelized pretty well.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
0

what about using an IntStream instead of a loop:

IntStream.range(0, autobahn.size() - 1)
        .filter(i -> autobahn.get(i).speed < autobahn.get(i + 1).speed)
        .forEach(i -> autobahn.get(i).honk());
Raphael Roth
  • 26,751
  • 15
  • 88
  • 145