[英]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.