【问题标题】:Partially sort an array in descending order using Java Stream API使用 Java Stream API 按降序对数组进行部分排序
【发布时间】:2020-01-08 08:50:01
【问题描述】:

我需要知道如何使用 Stream API 按降序对原始唯一整数数组进行部分排序。例如,如果有一个像 {1,2,3,4,5} 这样的数组,我想得到 {5,4,3, 1,2} - 首先是 3 个最大的元素,然后是其余的。甚至可以使用流吗?我检查了文档 - 有两种方法 skiplimit 但它们会更改流内容并从数组的开头开始工作。

我可以像这样对整个数组进行排序

Arrays.stream(arr)
.boxed()
.sorted(Collections.reverseOrder())
.mapToInt(Integer::intValue)
.toArray();

但是如何使这种排序局部化?我说 Stream API 是因为我希望它写得很好。

我也直觉地觉得concat 可能会在这里一展身手。我可以考虑的另一种方法是使用自定义比较器来限制排序元素的数量。你怎么看?

附:我不是 Java 专家。

【问题讨论】:

  • 你将如何用纯 Java 解决它?输入是数组还是集合?
  • 您的示例输入是{1,2,3,4,5}。输入是否保证按升序排序?
  • @Andrew Tobilko 输入是一个数组int[]。在纯 Java 中,我会使用任何众所周知的算法,如插入排序等,在需要的元素按 desc 顺序排序后停止。问题是我需要通过一个测试用例并使用这种天真的算法我得到超时。
  • “其他”元素是否有必要按其原始顺序排列?如果不是,那么普通排序不会基本上解决这个问题吗?编辑:我刚刚看到你关于超时的评论。
  • @curveball 我非常怀疑你会通过将它们重写为 Stream API 来加快速度......

标签: java arrays sorting java-stream


【解决方案1】:

我需要知道如何使用 Stream API 按降序对原始整数数组进行部分排序。

没有可让您在 Java 中执行此操作的内置工具。在 Stream API 和 Collections API 中都没有。你要么需要自己实现它,要么改变你的方法。

我说 Stream API 是因为我希望它写得很好。

使用 Java 8 Streams 并不意味着您的代码会写得很好。流是不是通用工具。有时它们提供增强的可读性,有时您必须使用其他东西。

我可以考虑的另一种方法是使用自定义比较器来限制排序元素的数量。

那无法做到,因为Comparator 不知道有多少元素已排序。简单地计算调用次数不会为您提供这方面的任何有意义的信息。


我的建议是实现类似于C++std::partial_sort,这很可能基于heap approach

【讨论】:

  • “使用 Java 8 Streams 并不意味着你的代码会写得很好。” - 简洁和表达性是它的目的之一,但不是主要目的。
  • “Comparator 不知道有多少元素已排序” - 它可能知道它,这将使它成为有状态的。但是,Stream API 希望我们编写无状态的Comparators。顺便说一句,我不会对任何答案投反对票。
  • @AndrewTobilko 我完全同意,但仅仅使用 Streams 不会使您的代码更好。他们可能,当然,但这不是一个规则。并非所有可以都可以使用 Streams 重写。这些情况很少见,但确实会发生。
  • @AndrewTobilko 有状态的比较器违反了比较器本身的契约,无论您使用哪个 API。在当前的实现中,不管你使用Stream.sortedList.sort 还是Arrays.sort,它最终都会以相同的代码结束,这将与有状态的比较器中断。但是一旦你教会了你的比较器如何记住最大的 3 个数字,你无论如何都做了关键的工作。将记住 3 个最大数字的功能与单个线性遍历相结合比排序操作更有效。
  • @AndrewTobilko 实际上,重复评估返回相同结果的要求是如此基本,甚至没有明确提及。但所有其他要求,如sgn(compare(x, y)) == -sgn(compare(y, x))” and “((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0 都建立在它之上。比较器可能会在不影响顺序时静默记录遇到的值,但这会比仅进行线性传递效率低。
【解决方案2】:

您将无法使用流很好地做到这一点。这是一种方法:

public static void main(String[] args) {

    Integer[] arr = {1, 2, 3, 4, 5};
    List<Integer> originalValues = new ArrayList<>(Arrays.asList(arr));

    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        originalValues.stream().max(Integer::compareTo).ifPresent(v -> {
            list.add(v);
            originalValues.remove(v);
        });
    }
    list.addAll(originalValues);

    System.out.println(list);
    // [5, 4, 3, 1, 2]
}

【讨论】:

  • 它在.max(Integer::compareTo).ifPresent(v -&gt; { 处给出类似Exception in thread "main" java.lang.ClassCastException: [I cannot be cast to java.lang.Integer 的错误
  • 不确定如何。这对我来说可以。也许试试.stream().mapToInt(v-&gt;v).max()
  • @curveball 此代码使用Integer[],而不是int[]int[] 不能与 Arrays.asList 一起使用。
【解决方案3】:

这是一种使用流的方法。

int[] sortPartially(int[] inputArray, int limit) {
    Map<Integer, Long> maxValues = IntStream.of(inputArray)
                                            .boxed()
                                            .sorted(Comparator.reverseOrder())
                                            .limit(limit)
                                            .collect(Collectors.groupingBy(x -> x, LinkedHashMap::new, Collectors.counting()));

    IntStream head = maxValues.entrySet()
                              .stream()
                              .flatMapToInt(e -> IntStream.iterate(e.getKey(), i -> i)
                                                          .limit(e.getValue().intValue()));

    IntStream tail = IntStream.of(inputArray)
                              .filter(x -> {
        Long remainingDuplication = maxValues.computeIfPresent(x, (y, count) -> count - 1);
        return remainingDuplication == null || remainingDuplication < 0;
    });

    return IntStream.concat(head, tail).toArray();
}

上面的例子当然是对整个输入数组进行排序,但保持未排序元素的顺序稳定。

另一个使用优先级队列的流示例(正如其他人提到的)降低了运行时复杂性:

Collection<Integer> sortPartially(int[] inputArray, int sortedPartLength) {
    Queue<Integer> pq = new PriorityQueue<>(sortedPartLength);

    Deque<Integer> result = IntStream.of(inputArray).boxed().map(x -> {
        pq.add(x);
        return pq.size() > sortedPartLength ? pq.poll() : null;
    }).filter(Objects::nonNull).collect(Collectors.toCollection(ArrayDeque::new));

    Stream.generate(pq::remove).limit(sortedPartLength).forEach(result::addFirst);

    return result;
}

如果输入数组中有重复,未排序元素的顺序可以改变。

【讨论】:

  • 抱歉,IntStream 是什么?我使用 java 8,它会引发错误。
  • @curveball java.util.stream.IntStream 是 JDK 8 的一部分
  • 问题并不明显,但如果数组包含重复项,例如 {1,2,3,4,5,5,5},您可能会丢失一两个最大的元素
  • @Eritrean 他们是独一无二的,我更正了原帖。
  • @Eritrean 我测试了 {1,2,3,4,5,5,5} 并期望 {5,5,5,1,2,3,4} 作为结果。这就是函数的结果。什么意思?
【解决方案4】:

我会将三个最大的元素保存在一个集合中,然后定义我自己的比较器。

 public static void main(String[] args){
    int[] input = {1,2,3,4,5};
    Set<Integer> set = Arrays.stream(input).boxed().sorted(Comparator.reverseOrder()).limit(3).collect(Collectors.toSet());
    Comparator<Integer> customComp = (a,b) -> { 
        if(set.contains(a) && set.contains(b)){ return a.compareTo(b);}
        else if(set.contains(a)){ return 1;}
        else if(set.contains(b)){ return -1;}
        else { return 0;}
    };
    int[] sorted = Arrays.stream(input).boxed().sorted(customComp.reversed()).mapToInt(i->i).toArray();
    System.out.println(Arrays.toString(sorted));
}

【讨论】:

    【解决方案5】:

    虽然代码比公认的答案长,但它的排序要少得多:对于大数组,这很重要:

    private static int[] partiallySorted(int[] input, int bound) {
        int[] result = new int[input.length];
    
        int i = -1;
        PriorityQueue<Integer> pq = new PriorityQueue<>(bound, Comparator.naturalOrder());
        for (int x : input) {
            pq.add(x);
            if (pq.size() > bound) {
                int el = pq.poll();
                result[bound + ++i] = el;
            }
        }
    
        while (!pq.isEmpty()) {
            result[--bound] = pq.poll();
        }
    
        return result;
    }
    

    【讨论】:

    • 谢谢!稍后我将更深入地研究这一点。现在我对所有这些 java 的东西不知所措......
    • 除了流之外,确实没有几个方法可以解决这个问题。除此之外,这是否在输入上进行了测试?似乎不适合代码中的 result[--b] = elresult[bound++] = i 组合。稍后会与您联系并提供详细信息
    • @Naman 已编辑。实际上是的,测试了您的输入和 OP。
    • 我想说,这比公认的答案更有效,即使对于小型数组也是如此。接受的答案的解决方案甚至在开始时对装箱值进行完整的排序操作,然后再做很多事情来模拟部分排序操作。虽然它确实保持原始顺序,但根据this comment,这不是必需的。
    猜你喜欢
    • 2011-02-09
    • 2014-02-16
    • 2013-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-12
    • 2021-12-28
    • 1970-01-01
    相关资源
    最近更新 更多