要按大于最旧元素的平均值筛选的Java 8流
我试图过滤城市中的一群要按大于最旧元素的平均值筛选的Java 8流,java,filter,java-8,java-stream,deque,Java,Filter,Java 8,Java Stream,Deque,我试图过滤城市中的一群人,使他们的平均年龄大于数据库中创建时间戳最早的人的年龄 我在做下面的事情 LinkedBlockingDeque<Person> allAges = null; LinkedBlockingDeque<Person> filteredAges = new LinkedBlockingDeque<Person>(); allAges = ageStorage.getAllAgesByCityOrderByInsertionTime("c
人
,使他们的平均年龄大于数据库中创建时间戳最早的人的年龄
我在做下面的事情
LinkedBlockingDeque<Person> allAges = null;
LinkedBlockingDeque<Person> filteredAges = new LinkedBlockingDeque<Person>();
allAges = ageStorage.getAllAgesByCityOrderByInsertionTime("city A");
allAges.stream()
.filter(this.getFirstInsertedAgeGreaterThanAverage(allAges))
.forEach(filteredAges::add);
我想这里有一些不太正确的地方,但不确定是什么…有没有一种方法可以不用getfirstinsertedegreeaterthanaverage
方法来实现这一点从你的问题中不清楚你到底想要哪个子集。只包括一个年龄最大的人(如果恰好是第一个,则不包括任何人)是一个有效的答案。所以我假设你想要得到最大可能的子集。正如@tobias_k所注意到的,这可以通过按年龄排序、递减和选择平均值不超过限制的最长前缀来解决
不幸的是,使用标准流API无法在单个流中解决这一问题。可能的解决方案如下所示:
public static List<Person> maxSubSetWithGreaterAverage(Collection<Person> persons,
int averageLimit) {
List<Person> list = new ArrayList<>(persons);
// Sort people by age, decreasing
list.sort(Comparator.comparingInt(Person::getAge).reversed());
// get all the ages
int[] ages = list.stream().mapToInt(Person::getAge).toArray();
// transform them to cumulative sums
Arrays.parallelPrefix(ages, Integer::sum);
// Find the longest prefix for which the cumulative sum is bigger
// than average
int length = IntStream.range(0, ages.length)
.filter(count -> ages[count] <= averageLimit * (count + 1)).findFirst()
.orElse(ages.length);
// return the corresponding subList
return list.subList(0, length);
}
List<Person> filtered = StreamEx.of(allAges)
.chain(takeWhileAverageGreater(Person::getAge, allAges.peekFirst().getAge()))
.toList();
使用我的库,可以定义自定义中间操作,该操作将在单个流中执行必要的过滤,尽管这需要高级魔术:
public static <T> UnaryOperator<StreamEx<T>> takeWhileAverageGreater(
ToIntFunction<? super T> keyExtractor, int averageLimit) {
return s -> takeWhileAverageGreater(
s.sorted(Comparator.comparingInt(keyExtractor).reversed()),
keyExtractor, 0L, 0L, averageLimit);
}
private static <T> StreamEx<T> takeWhileAverageGreater(StreamEx<T> input,
ToIntFunction<? super T> keyExtractor, long count, long cumulativeSum,
int averageLimit) {
return input.headTail((head, tail) -> {
// head is the first element, tail is the Stream of the rest
// update current sum
long newSum = cumulativeSum + keyExtractor.applyAsInt(head);
// short-circuit via null if the limit is reached
// otherwise call myself for the tail prepending with head
return newSum <= averageLimit * (count + 1) ? null :
takeWhileAverageGreater(tail, keyExtractor, count + 1, newSum, averageLimit)
.prepend(head);
});
}
结果是一样的。可能有多种解决方案。您希望保留哪一个?如果您希望保留最大的此类子集,请按年龄对人员进行排序,并将人员添加到集合中,从最年长的开始,直到平均值刚好高于另一个人的年龄。此外,是否必须使用流?在这里,使用一个常规的for循环和一些helper变量可能会容易得多。这应该是一个技巧性的问题吗?平均值不可能大于最大值。通过选择最大/最早的元素本身(仅此),可以得到的最大平均值就是最大值。或者,如果存在多个元素,则可能所有元素都与最古老的元素具有相同的年龄。当然,选择任何其他项都会有一个较小的平均值。@g0c00l.g33k如果任务是返回集合的平均年龄是否大于第一个插入的人的年龄,这是另一回事。您编写的谓词
代码为每个元素返回相同的结果,因为它不检查传递到谓词中的Person p->
的任何值。
public static List<Person> maxSubSetWithGreaterAverage(Collection<Person> persons,
int averageLimit) {
List<Person> list = new ArrayList<>(persons);
list.sort(Comparator.comparingInt(Person::getAge).reversed());
int cumulativeAge = 0;
for(int i=0; i<list.size(); i++) {
cumulativeAge += list.get(i).getAge();
if(cumulativeAge <= averageLimit * (i + 1) )
return list.subList(0, i);
}
return list;
}
public static <T> UnaryOperator<StreamEx<T>> takeWhileAverageGreater(
ToIntFunction<? super T> keyExtractor, int averageLimit) {
return s -> takeWhileAverageGreater(
s.sorted(Comparator.comparingInt(keyExtractor).reversed()),
keyExtractor, 0L, 0L, averageLimit);
}
private static <T> StreamEx<T> takeWhileAverageGreater(StreamEx<T> input,
ToIntFunction<? super T> keyExtractor, long count, long cumulativeSum,
int averageLimit) {
return input.headTail((head, tail) -> {
// head is the first element, tail is the Stream of the rest
// update current sum
long newSum = cumulativeSum + keyExtractor.applyAsInt(head);
// short-circuit via null if the limit is reached
// otherwise call myself for the tail prepending with head
return newSum <= averageLimit * (count + 1) ? null :
takeWhileAverageGreater(tail, keyExtractor, count + 1, newSum, averageLimit)
.prepend(head);
});
}
List<Person> filtered = StreamEx.of(allAges)
.chain(takeWhileAverageGreater(Person::getAge, allAges.peekFirst().getAge()))
.toList();