简体   繁体   English

使用Comparator返回具有max属性的值列表

[英]Returning List of values with max property using Comparator

What is the best way to get List<MyCustomObject> from List<MyCustomObject> that have maximum value of some property? 什么是获得最佳方式List<MyCustomObject>List<MyCustomObject>具有某种属性的最大值?

I can write my own Comparator : 我可以写自己的Comparator

Comparator<MyCustomObject> cmp = Comparator.comparing(MyCustomObject::getIntField);

Then use it in a stream : 然后在stream使用它:

Optional<MyCustomObject> max = list.stream().max(cmp);

But I'm getting only one element. 但我只得到一个元素。 Is there a simple way to return all MyCustomObject that have maximum IntField and not just the first one? 有一种简单的方法可以返回具有最大IntField而不仅仅是第一个的所有MyCustomObject吗?

You only know the maximum value of the relevant property after you iterate over all the elements of the List , so one way of find the elements having the max value is to group the elements by that property into a sorted Map and get the last value: 迭代遍历List所有元素后,您只知道相关属性的最大值,因此找到具有max值的元素的一种方法是将该属性的元素分组为已排序的Map并获取最后一个值:

List<MyCustomObject> max = list.stream()
                               .collect(Collectors.groupingBy (MyCustomObject::getIntField,
                                                               TreeMap::new,
                                                               Collectors.toList ()))
                               .lastEntry ()
                               .getValue ();

However, this performs more work then you actually need, and costs O(NlogN) due to the sorting. 但是,这会执行您实际需要的更多工作,并且由于排序而花费O(NlogN) If you don't mind splitting the problem into two steps (first finding the maximum value and then collecting the elements having a property with that value), you'll have better running time ( O(N) ). 如果您不介意将问题分成两个步骤(首先找到最大值然后收集具有该值的属性的元素),您将有更好的运行时间( O(N) )。

Since I don't have your custom object, I couldn't test the above code, but I tested similar code that takes a Stream of String s and returns all the String s having the max length: 由于我没有你的自定义对象,我无法测试上面的代码,但是我测试类似的代码,需要一个StreamString 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);

This returns: 返回:

[EEEE, CCC1]

To avoid two runs, you can provide your own Collector to collect the stream. 要避免两次运行,您可以提供自己的Collector来收集流。

Let's use 我们来使用吧

Sample data class 示例数据类

static class MyCustomObject {
    private int intField;

    MyCustomObject(int field) {
        intField = field;
    }

    public int getIntField() {
        return intField;
    }

    @Override
    public String toString() {
        return Integer.toString(intField);
    }
}

What you do to create your own Collector is use one of the factory methods, Collector#of . 您创建自己的Collector是使用工厂方法之一, Collector#of We'll use the more complex one . 我们将使用更复杂的一个

This is what it will look like: 这就是它的样子:

Collector<MyCustomObject, Intermediate, List<MyCustomObject>> collector

with MyCustomObject the objects you're collecting, Intermediate a class that will store the current maximum and the list of MyCustomObject s that had that maximum, and List<MyCustomObject>> the desired end result of objects with that maximum. MyCustomObject你收集的对象, Intermediate是当前最大和列表将存储类MyCustomObject s表示有这样的最大值和List<MyCustomObject>>与最大对象的期望的最终结果。

Intermediate 中间

Here's the intermediate class: 这是中间阶级:

// simple enough
class Intermediate {
    Integer val = null;
    List<MyCustomObject> objects = new ArrayList<>();
}

which will keep the maximum and corresponding objects. 这将保持最大和相应的对象。 It will be supplied with 它将提供

Supplier<Intermediate> supplier = () -> new Intermediate();

(or short Intermediate::new). (或简称中级::新)。

Accumulator 累加器

The accumulator needs to accumulate a new MyCustomObject into an existing Intermediate . accumulator需要将新的MyCustomObject累积到现有的Intermediate This is where the logic calculating the max value comes in. 这是计算最大值的逻辑进入的地方。

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

The combiner is used to combine two Intermediate values. combiner用于组合两个Intermediate值。 This is used for parallel streams. 这用于并行流。 If you do the simple test run below you won't trigger it. 如果您在下面进行简单的测试,则不会触发它。

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 整理

Finally, we need to extract the List<MyCustomObject> we really want from the final Intermediate using the finisher 最后,我们需要使用finisher从最终的Intermediate提取我们真正想要的List<MyCustomObject>

Function<Intermediate, List<MyCustomObject>> finisher = i -> i.objects;

This all comes together for the Collector 这一切都集合在一起为Collector

Collector<MyCustomObject, Intermediate, List<MyCustomObject>> collector =
    Collector.of(supplier, accumulator, combiner, finisher);

And for a simple test run 并进行简单的测试运行

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

Output 产量

[9, 9, 9] [9,9,9]

We do only iterate once, so it should be O(n) as the solution with two runs; 我们只迭代一次,所以它应该是O(n)作为两次运行的解决方案; I'm not entirely sure about this though because of all the adding to lists that happens in the intermediate steps. 我不完全确定这一点,因为在中间步骤中发生的所有添加列表。

See it tied together 把它绑在一起

For an actual Comparator version, you'd have to also adjust the Intermediate object; 对于实际的Comparator版本,您还必须调整Intermediate对象; then it's probably best to use a MyCustomObject in the Intermediate to use for the comparison as well. 那么最好使用IntermediateMyCustomObject来进行比较。

Here is a version for this , including a refactoring of the accumulator into the Intermediate class. 这是一个版本 ,包括将累加器重构为Intermediate类。

In the end, it boils down to this factory method: 最后,归结为这种工厂方法:

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

I offer this simple solution. 我提供这个简单的解决方案 First retrieve max element from the given list objs . 首先从给定列表objs检索max元素。 And then retrieve all elements, that equal to 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());
}

We loop given list only twice without extra memory allocation. 我们只给两次循环给定列表而没有额外的内存分配 Therefore we have O(n) complexity. 因此,我们有O(n)复杂性。

Code snippet for this problem: 此问题的代码段:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM