【问题标题】:How can I find the largest M numbers from N numbers in Java 8?如何从 Java 8 中的 N 个数中找到最大的 M 个数?
【发布时间】:2015-06-11 03:57:40
【问题描述】:

IntStream 可能是最简单的方法,但我只能选择最小的 M 数字,如下所示:

public class Test {
    private static final int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};

    public static void main(String[] args) throws Exception {
        System.out.println(Arrays.asList(IntStream.of(arr).sorted().limit(5).boxed().toArray()));
    }
}

顺便说一句,考虑到算法复杂度并假设 N >> M,“排序 + 限制”方法的复杂度仅为 O(N log(N))。

我认为最好的复杂度可能达到 O(N log(M)) 但我不知道 Java 8 是否有这种流方法或收集器。

【问题讨论】:

标签: java algorithm java-8 java-stream


【解决方案1】:

如果您必须使用 Streams:

IntStream.of(arr).sorted().skip(N-M)

否则使用PriorityQueue 并给自己写一个反相的Comparator。插入将是 O(N(log(N)) 并且 M 元素的删除将是 O(M(log(N))。不是您要求的,而是也许足够接近。

【讨论】:

    【解决方案2】:

    EJP 是对的,我测试了它 - 当输入 2 时产生 8 和 9。

    import java.util.stream.IntStream;
    public class Test {
        private static final int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};
    
        public static void main(String[] args) throws Exception { 
            int n = Integer.parseInt(args[0]);
            System.out.println("Finding "+n+" largest numbers in arr");
            IntStream.of(arr).sorted().skip(arr.length-n).boxed().forEach(big -> System.out.println(big));
        }
    }
    

    【讨论】:

    • 谢谢。但是如何降低算法复杂度呢?
    • 我不确定如何降低复杂性并仍然使用 java-8,我认为 lambda 表达式的一大卖点是没有指定底层实现,为编译器优化。至少这是我的理解。
    【解决方案3】:

    如果您已经在项目中使用 google guava,则可以利用 MinMaxPriorityQueue

    Collection<..> min5 = stream.collect(
        toCollection(MinMaxPriorityQueue.maximumSize(5)::create)
    );
    

    【讨论】:

      【解决方案4】:

      可以使用 JDK PriorityQueue 创建自定义收集器来解决您的任务:

      public static <T> Collector<T, ?, List<T>> maxN(Comparator<? super T> comparator, 
                                                      int limit) {
          BiConsumer<PriorityQueue<T>, T> accumulator = (queue, t) -> {
              queue.add(t);
              if (queue.size() > limit)
                  queue.poll();
          };
          return Collector.of(() -> new PriorityQueue<>(limit + 1, comparator),
                  accumulator, (q1, q2) -> {
                      for (T t : q2) {
                          accumulator.accept(q1, t);
                      }
                      return q1;
                  }, queue -> new ArrayList<>(queue));
      }
      

      用法:

      int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};
      System.out.println(IntStream.of(arr).boxed().collect(maxN(Comparator.naturalOrder(), 2)));
      // [8, 9]
      System.out.println(IntStream.of(arr).boxed().collect(maxN(Comparator.reverseOrder(), 3)));
      // [3, 1, 2]
      

      大数据集和小限制可能会更快,因为它不排序。如果您想要排序结果,可以将排序步骤添加到finisher

      【讨论】:

      • 很棒的代码。但是组合器似乎没用(即(q1, q2) -&gt; null 可以),因为 PriorityQueue 不是线程安全的。
      • @auntyellow:Collector 在使用并行流时不必是线程安全的(除非Collector 具有CONCURRENT 特性)。现有的收集器可以与HashMapArrayList 等并行工作。
      【解决方案5】:

      您可以通过创建值的直方图来实现复杂性目标:

      public static IntStream maxValues(IntStream source, int limit) {
          TreeMap<Integer,Integer> m=new TreeMap<>();
          source.forEachOrdered(new IntConsumer() {
              int size, min=Integer.MIN_VALUE;
              public void accept(int value) {
                  if(value<min) return;
                  m.merge(value, 1, Integer::sum);
                  if(size<limit) size++;
                  else m.compute(min=m.firstKey(), (k,count)->count==1? null: count-1);
              }
          });
          if(m.size()==limit)// no duplicates
              return m.keySet().stream().mapToInt(Integer::valueOf);
          return m.entrySet().stream().flatMapToInt(e->{
              int value = e.getKey(), count = e.getValue();
              return count==1? IntStream.of(value): IntStream.range(0, count).map(i->value);
          });
      }
      

      它创建了一个从 int 值到其相应出现次数的映射,但将其内容限制为所需的值数量,因此,它的操作具有O(log(M)) 复杂性(最坏的情况,如果没有重复),并且由于操作对每个值执行一次,它的整体复杂度是O(N×log(M)),如您所愿。

      你可以用你原来的数组来测试它

      int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};
      maxValues(Arrays.stream(arr), 3).forEach(System.out::println);
      

      但要测试一些极端情况,您可以使用包含重复项的数组,例如

      int[] arr = {8, 5, 3, 4, 2, 2, 9, 1, 7, 9, 8, 6};
      // note that the stream of three max elements contains one of the two eights
      

      如果您追求最大性能,使用原始数据类型将装箱树图替换为适当的数据结构可能是可行的,但这将是一个较小的性能优化,因为该解决方案已经解决了复杂性问题。

      顺便说一下,这个解决方案适用于任意流,即不需要知道N 的值。

      【讨论】:

      • 相比 PriorityQueue,TreeMap 似乎更复杂
      • A PriorityQueue 可能是一个工作组合,只要您没有重复项,但当您有重复项时,它需要为每个人提供 存储空间,而不是简单地计算基于地图的方法的出现次数。无论如何,接受的答案的解决方案更简单,不清楚复杂性的差异(你最初要求的)是否真的会影响性能。
      猜你喜欢
      • 1970-01-01
      • 2014-09-04
      • 2020-09-06
      • 1970-01-01
      • 2021-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多