繁体   English   中英

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

[英]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) )。

由于我没有你的自定义对象,我无法测试上面的代码,但是我测试类似的代码,需要一个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);

返回:

[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对象; 那么最好使用IntermediateMyCustomObject来进行比较。

这是一个版本 ,包括将累加器重构为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.

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