使用交替值的IntStream.iterate添加值

4

我想创建一系列整数,最好是一个IntStream,满足某种条件。以下是一些简单的示例来解释我要做什么:

前10个从0开始的偶数序列(在这里我没有问题,我可以使用下面的代码片段)

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

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

按照交替加2和3的规律,从0开始输出前10个数字的序列;期望输出:

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

我也很想在这种情况下使用IntStream.iterate()IntStream.generate(),但是我自己搞不定。我使用经典的for循环,虽然可以工作,但对于这样一个相对简单的任务来说有点冗长。
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);

有没有一种使用IntStream.iterate()IntStream.generate()实现与上述相同的简单方法?

对于三个或更多的交替值,我不知道如何优雅地实现它。例如,如果我想通过交替添加+2、+5和+7到前一个数字来创建从0开始的前10个数字序列,并得到所需的输出:

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

我考虑在for循环中使用i%3和3个if-else块或switch case,但是当我需要更多交替值时,这些块将会增长,我需要添加更多的ifscases。有什么想法吗?如果您认为IntStream.iterate()IntStream.generate()不是解决所描述任务的合适方法,我也可以接受其他方法。

3个回答

6

生成一个通过交替加2或3的流,可以观察到每隔一个值将会是5的倍数。因此,如果上一个值能够被5整除且没有余数,我们应该加2,否则我们应该加3。

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

对于两个交替的整数,有时不可能采用这种方法。例如,如果其中一个数字是另一个数字的因子,则不可能使用此方法,例如2和4。

对于这些情况,您可以使用更通用的方法并在迭代器外部维护一个布尔值。

由于函数具有副作用,所以它不遵循函数式风格,但是嘿,Java不是一种函数式语言。 这种方法已经足够直观了。

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

对于3个交替值,在一般情况下,您可以使用整数计数器进行类似的操作,但该计数器在0、1和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)

在这种情况下,原子性和线程安全方面并不相关,我们只需要一些可变的持有者来保存一个值(分别为布尔值和整数),我们可以保持对其的常量引用(被lambda捕获的变量必须是final)。如果有MutableBoolean或MutableInt这样的东西,那么它们会更好地满足我们的需求。但是Java中的所有基本包装类都是不可变的。对于我们的情况,AtomicInteger/Boolean已经足够好了。如果你编写自己的MutableBoolean,可能会有非常轻微的性能提升,因为你可以避免锁定,但这并不重要。 - Michael

4
你可以使用 AtomicInteger 来实现这一点,并保持一个交替添加数字的列表。
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());


非常感谢。一个非常好的方法。学到了一些关于 AtomicInteger 的新知识。 - nopens

3
你可以从数学角度来看待这个问题。假设你想要交替相加x1,x2,x3 ... xn。
前n项可以写成:
(0 * sum of all x's)
(0 * sum of all x's + x1)
(0 * sum of all x's + x1 + x2)
(0 * sum of all x's + x1 + x2 + x3)
...
(0 * sum of all x's + sum of all x's - xn)

下面的n个术语可以用同样的方式编写,只需将所有0替换为1即可。
(1 * sum of all x's)
(1 * sum of all x's + x1)
(1 * sum of all x's + x1 + x2)
(1 * sum of all x's + x1 + x2 + x3)
...
(1 * sum of all x's + sum of all x's - xn)

接下来的n个数字,将所有1替换为2,以此类推。

因此,我们只需生成流(1, 2, 3, 4...),并将每个元素i flatMap 到流中

(i * sum of all x's)
(i * sum of all x's + x1)
(i * sum of all x's + x1 + x2)
(i * sum of all x's + x1 + x2 + x3)
...
(i * sum of all x's + sum of all x's - xn)

使用此模式,您可以像这样编写2、5、7流:
final int a = 2, 
final int b = 5, 
final int c = 7;
final int sum = a + b + c;
IntStream.iterate(0, i -> i + 1).flatMap(
    i -> IntStream.of(i * sum, i * sum + a, i * sum + a + b)
).limit(10);

如果您想要使用2、3、5、7来替代:
final int a = 2, 
final int b = 3, 
final int c = 5; 
final int d = 7;
final int sum = a + b + c + d;
IntStream.iterate(0, i -> i + 1).flatMap(
    i -> IntStream.of(i * sum, i * sum + a, i * sum + a + b, i * sum + a + b + c)
).limit(10);

我将把这留给你,让你将其推广到任何数字的int[]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接