簡體   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