11

I want to generate a list of numbers using lambda expressions and not a for-loop.

So let's say I want to generate a list of all triangular numbers under 100. Triangular numbers are numbers which follow the formula: (n*n+n)/2

What is the best way of doing this? Currently I have this:

    Stream.iterate(1, n -> n + 1).limit(100)
            .map(n -> (n * n + n) / 2)
            .filter(a -> a < 100)
            .map(a -> a + "")
            .collect(Collectors.joining(", ", "Numbers: ", "."));

But this seems unnecessarily overkill with the amount of calculations. I iterate n over 1 to 100 (because lets assume I do not know what the max value for n is), then I map the triangle number function of that list and then I check which numbers are under 100. Is there a more efficient way in doing this? Also: can I generate the triangle numbers using only the iterate function of Stream instead of using iterate, limit and then map?

EDIT: So the main point here is: how can calculation of traingle numbers stop as soon as one of the triangle numbers exceeds 100? Usually I would write it like this:

ArrayList<Integer> triangles = new ArrayList<>(); 
for (int n=1;true;n++) {
    int num = (n*n+n)/2;

    if (num>100) break;

    triangles.add(num);
}

which stops as soon as a triangle number exceeds 100, which is very efficient; how can I retain this efficiency in lambda expression?

Héctor van den Boorn
  • 1,218
  • 13
  • 32
  • 2
    `Stream.iterate(1, n->n+1).limit(100)` can be rewritten as `IntStream.rangeClosed(1, 100)` which is probably more readable. – Pshemo Jun 07 '15 at 13:22
  • why are you using both a limit and a filter? I believe the second filter will limit the output based on the calculation, so you'll only get results that are less than 100, rather than inputs less than 100. – John Ament Jun 07 '15 at 13:23
  • What is the point? Or is it just curiosity? –  Jun 07 '15 at 13:23

2 Answers2

6

In general case what you're looking for is take-while. Unfortunately, it has no default implementation in Java 8 streams. See a question about take-while.

Community
  • 1
  • 1
hotkey
  • 140,743
  • 39
  • 371
  • 326
-2

If all you're looking to do is convert the given sequence into the triangle (as you describe), this does it much simpler.

List<Integer> l = IntStream.rangeClosed(1, 100)
            .mapToObj(n -> (n*n + n) / 2)
            .collect(Collectors.toList());

the primitive stream wrappers need an extra step to up-convert to objects, hence the mapToObj method.

If you're looking to stop filtering when you hit 100, easiest way I can think of is

    IntFunction<Integer> calc =n -> (n*n+n) / 2; 
    List<Integer> l = IntStream.rangeClosed(1, 100)
            .filter(n -> calc.apply(n) < 100)
            .mapToObj(calc)
            .collect(Collectors.toList());

Based on the changes to your question, I think this is also pretty important to point out. If you want to mirror what you used to do, that would look like this:

    List<Integer> results = new ArrayList<>(100);
    IntStream.rangeClosed(1, 100).forEach(i -> {
        int tri =calc.apply(i);
        if(tri < 100) {
            results.add(tri);
        }
    });

It's worth pointing out that streams are not necessarily ordered (though the default implementation follows the iterator). If this were converted to a parallel stream you would see the difference (and power of streams). You can't break from the execution because then you're assuming a certain amount about the processing order. By filtering early (in my second form) you'll ensure that you only end up with a result stream of 13 entries in it before the final calculation. Take this parallel option as a note as well.

    List<Integer> l = IntStream.rangeClosed(1, 100).parallel()
            .filter(n -> calc.apply(n) < 100)
            .mapToObj(calc)
            .collect(Collectors.toList());

You'll see they're still ordered, but the computation of them was done on multiple threads.

John Ament
  • 11,595
  • 1
  • 36
  • 45
  • 1
    OP is asking how to stop streaming when value of `(n*n + n) / 2` will start to become greater than 100, because of hight amount of cases which will need to be filtered. So question is "if we know that values after some n-th value will not be needed how can we skip them". – Pshemo Jun 07 '15 at 13:37
  • Op hasn't responded to any questions yet. It's not clear what he's asking for yet. – John Ament Jun 07 '15 at 13:39
  • 3
    @JohnAment if it's not clear to you what the question is, that's a good reason not to answer. – JB Nizet Jun 07 '15 at 13:41
  • Your solution is worse than the one of the OP: it performs the computation twice for every number leading to a result < 100: once to filter, and once to map. – JB Nizet Jun 07 '15 at 13:51
  • Your updated code still will need to stream over all elements and do possibly expensive calculations. Actually it even needs to do them twice now. I don't know how it is improvement. – Pshemo Jun 07 '15 at 13:52
  • No one has ever said that a stream is always faster than code build from before streams. – John Ament Jun 07 '15 at 13:56
  • 2
    @JohnAment that's not the problem here. Your code, using streams, is slower than the OP's code, also using streams, and it doesn't do what the OP wants: stop iterating and doing the computations as soon as a result >= 100 is found. – JB Nizet Jun 07 '15 at 14:04