在Java中使用相同的列表和流两次

时间:2015-03-12 12:05:49

标签: java java-8 java-stream

我必须用流完成这个简单的操作:给出一个列表,得到前20个元素的总和和。

这就是我的想法

IntStream stream = obj.stream().mapToInt(d->d.getInt());
stream.limit(20).sum() / stream.sum();

但是我不能这样做,因为我被告知我不能重复使用流,所以..我尝试了以下内容:

List<Integer> counts = obj.stream()
    .mapToInt(d -> d.getInt())
    .boxed().collect(Collectors.toList());

counts.stream().limit(20).sum() / counts.stream().sum();

但是我被告知我不能在Stream上使用sum,所以我需要再次mapToInt用于这个简单操作的左右两侧。

有没有办法使用流以更优雅和简洁的方式执行此操作?

3 个答案:

答案 0 :(得分:9)

有一种很酷的方法可以在单个并行传递中执行此操作,但不使用流。

int[] nums = obj.stream().mapToInt(Integer::intValue).toArray();
Arrays.parallelPrefix(nums, Integer::sum);
int sumOfFirst20 = nums[19];
int sumOfAll = nums[nums.length-1];

parallelPrefix方法将在适当的位置计算数组上指定函数(此处为plus)的部分和的序列。所以第一个元素是a0;第二个是a0 + a1,第三个是a0 + a1 + a2等。

答案 1 :(得分:6)

您可以将List<Integer>转换为int[],而不是收集toArray()中的数字。这样,代码就会更加紧凑,您无需一直打包和取消装箱,并且可以将int[]直接转换为IntStream

int[] nums = obj.stream().mapToInt(d -> d.getInt()).toArray();
IntStream.of(nums).limit(20).sum() / IntStream.of(nums).sum();

答案 2 :(得分:3)

没有必要使事情过于复杂。只需从列表中获取两次流:

int totalSum = obj.stream().mapToInt(i -> i).sum();
int first20 = obj.stream().limit(20).mapToInt(i -> i).sum();

是的,它会在列表中进行两次传递(第二次仅用于20个第一个元素,所以没什么大不了的),所以我希望这是他们想要你做的。它简单,高效且易读。


要在一次通过中执行此操作,您可以使用收集器。举个例子,你可以这样做:

 Map<Boolean, Integer> map = IntStream.range(0, obj.size())
                                      .boxed()
                                      .collect(partitioningBy(i -> i < 20, 
                                               mapping(i -> obj.get(i), 
                                                       summingInt(i -> i))));

int first20 = map.get(true);
int totalSum = map.get(true) + map.get(false);

基本上,您首先流式传输索引。然后为每个指标;你将它们分成两个列表中的地图,一个用于20个第一个索引,另一个用于另一个列表;导致Map<Boolean, List<Integer>>

然后将每个索引映射到原始List中的值(使用mapping收集器)。

最后,您通过将每个List<Integer>的值汇总为一个Integer来对其进行转换。然后你得到总和。

自定义收集器的另一种解决方法:

public static Collector<Integer, Integer[], Integer[]> limitSum(int limit, List<Integer> list) {
    return Collector.of(() -> new Integer[]{0, 0},
        (a, t) -> { a[0] += list.get(t); if(t < limit) a[1] += list.get(t); },
        (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
        a -> a);
}

以及使用示例:

List<Integer> obj = Stream.iterate(0, x -> x + 1).limit(5).collect(toList()); //[0, 1, 2, 3, 4]
Integer[] result = IntStream.range(0, obj.size())
                            .boxed()
                            .collect(limitSum(4, obj));
System.out.println(Arrays.toString(result)); //[10, 6]

<小时/> 正如你所看到的那样,它不是非常易读,正如评论中所提到的,在这种情况下,for循环可能是合适的(尽管你被要求使用Streams)。