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

时间:2018-06-21 10:19:17

标签: java arraylist java-stream comparator

List<MyCustomObject>中获得具有某些属性最大值的List<MyCustomObject>的最佳方法是什么?

我可以写自己的Comparator

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

然后在stream中使用它:

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

但是我只得到一个要素。是否有一种简单的方法来返回所有具有最大MyCustomObject且不仅是第一个最大值的IntField

4 个答案:

答案 0 :(得分:4)

在迭代List的所有元素之后,您才知道相关属性的最大值,因此查找具有最大值的元素的一种方法是按该属性将元素分组为已排序的元素映射并获取最后一个值:

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

但是,这将执行您实际需要的更多工作,并且由于排序而花费O(NlogN)。如果您不介意将问题分为两个步骤(首先找到最大值,然后收集具有该值的属性的元素),则会有更好的运行时间(O(N))。

由于我没有您的自定义对象,因此无法测试上面的代码,但是我测试了类似的代码,它们使用StreamString并返回所有{{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)作为两次运行的解;但由于中间步骤中所有添加到列表中,我对此并不完全确定。

See it tied together

对于实际的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);
  }
}