我必须用流完成这个简单的操作:给出一个列表,得到前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用于这个简单操作的左右两侧。
有没有办法使用流以更优雅和简洁的方式执行此操作?
答案 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)。