[英]Returning List of values with max property using Comparator
什么是獲得最佳方式List<MyCustomObject>
從List<MyCustomObject>
具有某種屬性的最大值?
我可以寫自己的Comparator
:
Comparator<MyCustomObject> cmp = Comparator.comparing(MyCustomObject::getIntField);
然后在stream
使用它:
Optional<MyCustomObject> max = list.stream().max(cmp);
但我只得到一個元素。 有一種簡單的方法可以返回具有最大IntField
而不僅僅是第一個的所有MyCustomObject
嗎?
迭代遍歷List
所有元素后,您只知道相關屬性的最大值,因此找到具有max值的元素的一種方法是將該屬性的元素分組為已排序的Map並獲取最后一個值:
List<MyCustomObject> max = list.stream()
.collect(Collectors.groupingBy (MyCustomObject::getIntField,
TreeMap::new,
Collectors.toList ()))
.lastEntry ()
.getValue ();
但是,這會執行您實際需要的更多工作,並且由於排序而花費O(NlogN)
。 如果您不介意將問題分成兩個步驟(首先找到最大值然后收集具有該值的屬性的元素),您將有更好的運行時間( O(N)
)。
由於我沒有你的自定義對象,我無法測試上面的代碼,但是我測試類似的代碼,需要一個Stream
的String
S和返回所有String
小號具有最大長度:
List<String> max = Stream.of ("ddd","aa","EEEE","a","BB","CCC1")
.collect(Collectors.groupingBy (String::length,
TreeMap::new,
Collectors.toList ()))
.lastEntry ()
.getValue ();
System.out.println (max);
返回:
[EEEE, CCC1]
要避免兩次運行,您可以提供自己的Collector
來收集流。
我們來使用吧
示例數據類
static class MyCustomObject {
private int intField;
MyCustomObject(int field) {
intField = field;
}
public int getIntField() {
return intField;
}
@Override
public String toString() {
return Integer.toString(intField);
}
}
您創建自己的Collector
是使用工廠方法之一, Collector#of
。 我們將使用更復雜的一個 。
這就是它的樣子:
Collector<MyCustomObject, Intermediate, List<MyCustomObject>> collector
與MyCustomObject
你收集的對象, Intermediate
是當前最大和列表將存儲類MyCustomObject
s表示有這樣的最大值和List<MyCustomObject>>
與最大對象的期望的最終結果。
中間
這是中間階級:
// simple enough
class Intermediate {
Integer val = null;
List<MyCustomObject> objects = new ArrayList<>();
}
這將保持最大和相應的對象。 它將提供
Supplier<Intermediate> supplier = () -> new Intermediate();
(或簡稱中級::新)。
累加器
accumulator
需要將新的MyCustomObject
累積到現有的Intermediate
。 這是計算最大值的邏輯進入的地方。
BiConsumer<Intermediate, MyCustomObject> accumulator = (i, c) -> {
System.out.printf("accumulating %d into %d%n", c.intField, i.value);
if (i.value != null) {
if (c.intField > i.value.intValue()) {
// new max found
System.out.println("new max " + c.intField);
i.value = c.intField;
i.objects.clear();
} else if (c.intField < i.value) {
// smaller than previous max: ignore
return;
}
} else {
i.value = c.intField;
}
i.objects.add(c);
};
合
combiner
用於組合兩個Intermediate
值。 這用於並行流。 如果您在下面進行簡單的測試,則不會觸發它。
BinaryOperator<Intermediate> combiner = (i1, i2) -> {
System.out.printf("combining %d and %d%n", i1.value, i2.value);
Intermediate result = new Intermediate();
result.value = Math.max(i1.value, i2.value);
if (i1.value.intValue() == result.value.intValue()) {
result.objects.addAll(i1.objects);
}
if (i2.value.intValue() == result.value.intValue()) {
result.objects.addAll(i2.objects);
}
return result;
};
整理
最后,我們需要使用finisher
從最終的Intermediate
提取我們真正想要的List<MyCustomObject>
Function<Intermediate, List<MyCustomObject>> finisher = i -> i.objects;
這一切都集合在一起為Collector
Collector<MyCustomObject, Intermediate, List<MyCustomObject>> collector =
Collector.of(supplier, accumulator, combiner, finisher);
並進行簡單的測試運行
List<MyCustomObject> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 3; j++) {
list.add(new MyCustomObject(i));
}
}
Collections.shuffle(list);
System.out.println(list.stream().collect(collector));
產量
[9,9,9]
我們只迭代一次,所以它應該是O(n)作為兩次運行的解決方案; 我不完全確定這一點,因為在中間步驟中發生的所有添加列表。
對於實際的Comparator
版本,您還必須調整Intermediate
對象; 那么最好使用Intermediate
的MyCustomObject
來進行比較。
這是一個版本 ,包括將累加器重構為Intermediate
類。
最后,歸結為這種工廠方法:
public static <T> Collector<T, ?, List<T>> max(Comparator<T> compare) {
class Intermediate {
T value = null;
List<T> objects = new ArrayList<>();
void add(T c) {
if (objects.isEmpty()) {
value = c;
} else {
int compareResult = compare.compare(c, objects.get(0));
if (compareResult > 0) {
// new max found
System.out.println("new max " + c + ", dropping " + objects.size() + " objects");
value = c;
objects.clear();
} else if (compareResult < 0) {
return;
}
}
objects.add(c);
}
}
BinaryOperator<Intermediate> combiner = (i1, i2) -> {
Optional<T> max = Stream.of(i1, i2).filter(Objects::nonNull).filter(i -> !i.objects.isEmpty())
.map(i -> i.objects.get(0)).max(compare);
Intermediate r = max.map(m -> {
Intermediate result = new Intermediate();
result.value = max.get();
if (i1 != null && i1.value != null && compare.compare(i1.value, m) == 0) {
result.objects.addAll(i1.objects);
}
if (i2 != null && i2.value != null && compare.compare(i2.value, m) == 0) {
result.objects.addAll(i2.objects);
}
return result;
}).orElse(null);
System.out.printf("combining %s and %s - result %s%n", i1, i2, r);
return r;
};
return Collector.of(Intermediate::new, Intermediate::add, combiner, i -> i.objects);
}
我提供這個簡單的解決方案 首先從給定列表objs
檢索max元素。 然后檢索所有等於max的元素。
public static <T> List<T> getAllMax(List<T> objs, Comparator<T> comp) {
T max = objs.stream().max(comp).get();
return objs.stream().filter(e -> comp.compare(e, max) == 0).collect(Collectors.toList());
}
我們只給兩次循環給定列表而沒有額外的內存分配 因此,我們有O(n)復雜性。
此問題的代碼段:
List<MyCustomObject> maxList = new ArrayList<>();
MyCustomObject max = list.stream().max(cmp).orElse(null);
if (null != max) {
maxList.add(max);
list.remove(max);
while (list.stream().max(cmp).orElse(null) != null) {
maxList.add(max);
list.remove(max);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.