【问题标题】:Returning List of values with max property using Comparator使用比较器返回具有 max 属性的值列表
【发布时间】:2018-11-30 15:22:45
【问题描述】:

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

我可以自己写Comparator:

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

然后在stream中使用它:

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

但我只得到一个元素。有没有一种简单的方法可以返回所有MyCustomObject 最大IntField 而不仅仅是第一个?

【问题讨论】:

  • 不能有多个最大值,可以吗?或者您是否希望所有将 intField 设置为 max(intField) 的对象?
  • 没错,我的想法可能表述有误。
  • 那么你应该更新你的问题,以便在不阅读 cmets 的情况下清楚你在问什么。找到最大值后,您可以再次流式传输:list.stream().filter(element -&gt; element.equals(max.get())).collect(toList());

标签: java arraylist java-stream comparator


【解决方案1】:

这个问题的代码sn-p:

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

【讨论】:

    【解决方案2】:

    您只有在遍历List 的所有元素后才知道相关属性的最大值,因此找到具有最大值的元素的一种方法是按该属性将元素分组到排序的 Map 中并获取最后一个值:

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

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

    由于我没有您的自定义对象,我无法测试上面的代码,但我测试了类似的代码,它采用 Strings 的 Stream 并返回所有具有最大长度的 Strings :

    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]
    

    【讨论】:

    【解决方案3】:

    我提供了这个简单的解决方案。首先从给定列表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) 复杂度。

    【讨论】:

      【解决方案4】:

      为避免两次运行,您可以提供自己的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 是一个将存储当前最大值和具有该最大值的 MyCustomObjects 列表的类,以及 List&lt;MyCustomObject&gt;&gt; 具有该最大值的对象的所需最终结果最大。

      中级

      这是中间类:

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

      整理者

      最后,我们需要使用finisher从最终的Intermediate中提取出我们真正想要的List&lt;MyCustomObject&gt;

      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 对象;那么最好在Intermediate 中使用MyCustomObject 来进行比较。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多