Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IntStream.iterate with alternating values to add

I want to create a sequence of integers, preferably as an IntStream, which follows a certain condition. some simple examples to explain what I am trying to do:

Sequence of first 10 even numbers starting from 0 (Here i have no problems, I do it with the below snippet)

IntStream.iterate(0, i -> i + 2).limit(10); 

//0,2,4,6,8,10,12,14,16,18

Sequence of first 10 numbers starting from 0 by alternately adding 2 and 3 to the privious number; desired output:

 //0,2,5,7,10,12,15,17,20,22

I would love to use for this scenario also IntStream.iterate() or IntStream.generate() but couldn't make it on my own. I am using classic for loops, which works but is somehow long for such a relative simple task

List<Integer> list = new ArrayList<>();
list.add(0);
for(int i = 1, j = 1; i < 10; i++, j *= -1){
    if(j > 0){
        list.add(list.get(i-1) + 2);
    }
    else{
        list.add(list.get(i-1) + 3);
    }
}

//and then stream over the list to get IntStream
list.stream().mapToInt(Integer::intValue);

Any easy way to achieve the same as above with IntStream.iterate() or IntStream.generate()?

For three and more alternating values I am out of ideas how to do it elegantly. If for example I want to create the Sequence of first 10 numbers starting from 0 by alternately adding +2, +5 and +7 to the previous number with the desired output:

//0,2,7,14,16,21,28,30,35,42

I have thought about using i%3 in a for loop with 3 if-else blocks or switch cases but this will grow when i need more alternating values and i have to add more ifs or cases. Any ideas how to do it? I am open for other approaches too if you think IntStream.iterate() or IntStream.generate() is not the proper way of solving the described task.

like image 329
nopens Avatar asked Dec 09 '22 23:12

nopens


2 Answers

To generate a stream by alternating adding 2 or 3, it can be observed that every other value will be a multiple of 5. Therefore if the previous value is divisible by 5 with no remainder, we should add 2, otherwise we should add 3.

IntStream.iterate(0, i -> (i % 5 == 0) ? (i + 2) : (i + 3)).limit(10)

For two alternating integers, there will be cases where this approach is not possible. If one of the numbers is a factor of the other, that won't be possible. 2 and 4 for example.

For cases like those, you can use a more general approach and maintain a boolean outside the iterator.

It doesn't follow the functional style because your function has a side effect but hey, Java isn't a functional language. It's intuitive enough.

AtomicBoolean isTwo = new AtomicBoolean(true);
IntStream.iterate(0, i -> isTwo.getAndSet(!isTwo.get()) ? (i + 2) : (i + 4))

For 3 alternating values, in the general case you can do a similar thing to the boolean, but with an integer counter that cycles between 0, 1 and 2.

AtomicInteger counter = new AtomicInteger(0);
IntStream.iterate(0, i -> {
        int count = counter.getAndUpdate(cnt -> (cnt + 1) % 3);
        if (count == 0) return i + 2;
        if (count == 1) return i + 5;
        if (count == 2) return i + 7;
        // As long as modulus value == number of if-conditions, this isn't possible
        throw new RuntimeException("Only 3 possible values");
    })
    .limit(10)
like image 137
Michael Avatar answered Dec 27 '22 00:12

Michael


You could use an AtomicInteger to achieve this and keep of list of numbers that are to be added in an alternating manner.

List<Integer> adds = List.of(2,3,7);
AtomicInteger x = new AtomicInteger(0);
int limit = 10;
return IntStream.iterate(1,
   i -> i + adds.get(x.getAndIncrement() % adds.size()))
                .limit(limit)
                .boxed()
                .collect(Collectors.toList());

like image 28
Gautham M Avatar answered Dec 27 '22 01:12

Gautham M