2016-03-16 32 views
2

我想過濾一堆person在一個城市,使他們的年齡的平均值大於年齡最大的人的年齡created_at時間戳在數據庫。Java 8 Streams按平均過濾大於最老的元素

我在做類似的下面,

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方法

+2

可能有多種解決方案。你想保留哪一個? – Tunaki

+0

如果你想要最大的這樣的子集,按照他們的年齡對人進行排序並且將人添加到集合中,從最老的開始,直到平均值剛好高於其他人的年齡。 –

+0

另外,它是否必須使用流?在這裏使用常規的舊for循環和一些輔助變量可能更容易。 –

回答

1

這可以做到這一點,這是不明確的從你的問題,你不知道你想要的子集。只包括一個最大年齡的人(或者如果它恰好是第一個人),這是一個有效的答案。所以我假設你想獲得最大可能的這樣的子集。正如@tobias_k所注意到的,這可以通過按年齡分類輸入,減少並選擇平均不超過限制的最長前綴來解決。

不幸的是,這不能在使用標準流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()); 

但是不使用流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庫可以定義自定義intermed

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

的結果是一樣的。