【问题标题】:Java 8 Streams to filter by average greater than oldest elementJava 8 Streams 按平均大于最旧元素进行过滤
【发布时间】:2016-03-16 15:41:45
【问题描述】:

我正在尝试过滤一个城市中的一组person,以使他们的平均年龄大于数据库中具有最早created_at 时间戳的人的年龄。

我正在做类似下面的事情,

LinkedBlockingDeque<Person> allAges = null;
LinkedBlockingDeque<Person> filteredAges = new LinkedBlockingDeque<Person>();

allAges = ageStorage.getAllAgesByCityOrderByInsertionTime("city A");

allAges.stream()
       .filter(this.getFirstInsertedAgeGreaterThanAverage(allAges))
       .forEach(filteredAges::add);

getFirstInsertedAgeGreaterThanAverage如下,

private static Predicate<Integer> getFirstInsertedAgeGreaterThanAverage(LinkedBlockingDeque<Person> personList){
    return p -> (personList.stream().mapToInt(Person::getAge).average() >
     personList.peekFirst().getAge());
}

我猜这里有些东西不太对劲,但不确定是什么...有没有办法可以在没有 getFirstInsertedAgeGreaterThanAverage 方法的情况下完成此操作

【问题讨论】:

  • 可能有多种解决方案。你想保留哪一个?
  • 如果您想要最大的此类子集,请按年龄对人员进行排序并将人员添加到集合中,从最老的开始,直到平均值略高于其他人的年龄。
  • 另外,它必须使用流吗?在这里使用常规的旧 for 循环和一些辅助变量可能会容易得多。
  • 这应该是一个技巧问题吗?平均值不可能大于最大值。通过选择最大/最旧元素本身(仅此而已),您可以获得的最大平均值就是最大值。或者,如果有多个元素,则可能所有元素的年龄都与最古老的元素相同。当然,选择其他任何东西都会有一个较小的平均值。
  • @g0c00l.g33k 如果任务是返回集合的平均年龄是否大于第一个插入的人的年龄,那是另一回事。您的Predicate&lt;Person&gt; 代码如所写为每个元素返回相同的结果,因为它没有检查您传递给谓词的 Person p-&gt; 的任何值。

标签: java filter java-8 java-stream deque


【解决方案1】:

从您的问题中不清楚您想要哪个子集。仅包括一个年龄最大的人(如果恰好是第一个,则不包括)是一个有效的答案。所以我假设你想获得最大可能的这样的子集。正如@tobias_k 所注意到的,这可以通过按年龄对输入进行排序、递减并选择平均值不超过限制的最长前缀来解决。

很遗憾,使用标准 Stream API 在单个 Stream 中无法解决此问题。可能的解决方案可能如下所示:

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 = maxSubSetWithGreaterAverage(allAges, 
            allAges.peekFirst().getAge());

但是,如果不使用 Stream API 和 parallelPrefix,该解决方案看起来会更好、运行速度更快并且占用的内存更少:

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;
}

使用我的 StreamEx 库,可以定义自定义中间操作,该操作将在单个 Stream 中执行必要的过滤,但这需要高级魔法:

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);
    });
}

现在可以像这样使用新的takeWhileAverageGreater 操作:

List<Person> filtered = StreamEx.of(allAges)
        .chain(takeWhileAverageGreater(Person::getAge, allAges.peekFirst().getAge()))
        .toList();

结果是一样的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-28
    • 1970-01-01
    • 2020-03-26
    • 2018-01-01
    • 1970-01-01
    • 2017-04-27
    • 1970-01-01
    相关资源
    最近更新 更多