从List<MyCustomObject>
中获得具有某些属性最大值的List<MyCustomObject>
的最佳方法是什么?
我可以写自己的Comparator
:
Comparator<MyCustomObject> cmp = Comparator.comparing(MyCustomObject::getIntField);
然后在stream
中使用它:
Optional<MyCustomObject> max = list.stream().max(cmp);
但是我只得到一个要素。是否有一种简单的方法来返回所有具有最大MyCustomObject
且不仅是第一个最大值的IntField
?
答案 0 :(得分:4)
在迭代List
的所有元素之后,您才知道相关属性的最大值,因此查找具有最大值的元素的一种方法是按该属性将元素分组为已排序的元素映射并获取最后一个值:
List<MyCustomObject> max = list.stream()
.collect(Collectors.groupingBy (MyCustomObject::getIntField,
TreeMap::new,
Collectors.toList ()))
.lastEntry ()
.getValue ();
但是,这将执行您实际需要的更多工作,并且由于排序而花费O(NlogN)
。如果您不介意将问题分为两个步骤(首先找到最大值,然后收集具有该值的属性的元素),则会有更好的运行时间(O(N)
)。
由于我没有您的自定义对象,因此无法测试上面的代码,但是我测试了类似的代码,它们使用Stream
个String
并返回所有{{1} }个具有最大长度:
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);
答案 1 :(得分:2)
为避免两次运行,您可以提供自己的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
。我们将使用the more complex one。
它是这样的:
Collector<MyCustomObject, Intermediate, List<MyCustomObject>> collector
使用MyCustomObject
收集的对象,Intermediate
一个类,它将存储当前最大值以及具有该最大值的MyCustomObject
个列表,以及List<MyCustomObject>>
具有该最大值的对象所需的最终结果。
中级
这是中级班:
// simple enough
class Intermediate {
Integer val = null;
List<MyCustomObject> objects = new ArrayList<>();
}
将保留最大数量和相应的对象。 它将随附
Supplier<Intermediate> supplier = () -> new Intermediate();
(或简短的Intermediate :: new)。
累加器
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;
};
整理器
最后,我们需要使用List<MyCustomObject>
从最后的Intermediate
中提取我们真正想要的finisher
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
对象;那么最好在MyCustomObject
中使用Intermediate
进行比较。
Here is a version for this,包括将累加器重构为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);
}
答案 2 :(得分:1)
我提供了这个简单的解决方案。首先从给定列表objs
中检索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)复杂性。
答案 3 :(得分:0)
此问题的代码段:
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);
}
}