如何在Java中仅使用Lambda表达式对整数数组进行升序和降序排序

18
int[] arr2 = new int[] {54, 432, 53, 21, 43};

我正在使用这个来进行排序,但是出现了一个错误。


(I am using this to sort, but an error occurred.)
Arrays.sort(arr2, (a, b) -> a - b);

这也报错了。

arr2.sort((a, b) -> a - b);

17
不要使用像(a,b)->a-b这样的比较器来比较int值。虽然在某些情况下它可以工作,但是两个int值之间的差可能大于int值的范围,并导致溢出。因此,应该使用(a,b) -> Integer.compare(a, b)或者Integer::compare。要反转顺序,请使用(a,b) -> Integer.compare(b, a)。不要对比较函数返回的值取反,因为如果值是Integer.MIN_VALUE,这将失败。 - Holger
4
“giving error”是什么错误?请具体说明这个信息。 - Ian Kemp
3
在这种情况下,很明显。数组没有sort方法,并且sort(T[] a, Comparator<? super T> c)方法不支持原始类型数组。 - Holger
3
@Holger 感谢你继续为消除用减法比较两个整数值的方式而努力。 - Stuart Marks
4
在Java中,二维数组是一个数组的数组。由于数组是一个对象,因此二维数组也是对象的数组。换句话说,当你有 int[][] array 时,array instanceof Object[] 也将为 true。所以,int[][] 是一个对象数组,你可以使用泛型方法 sort​(T[] a, Comparator<? super T> c)。但是请再次阅读 这条评论,使用减号作为比较器在一些(小)值上有效,但通常是错误的,会导致难以追踪的错误。 - Holger
显示剩余2条评论
6个回答

21

您可以按以下方式对类型为Integer[]的输入进行排序:

Integer[] arr2 = new Integer[] {54,432,53,21,43};
Arrays.sort(arr2, Comparator.reverseOrder());

或者可能使用原始类型,例如:

int[] arr2 = new int[]{54, 432, 53, 21, 43};
int[] sortedArray = Arrays.stream(arr2)
        .boxed()
        .sorted(Comparator.reverseOrder()) // just use 'sorted()' for ascending order
        .mapToInt(Integer::intValue)
        .toArray();

或者进一步使用现有答案中的技巧(请注意,应该小心地处理边界值):

int[] sortedArray = Arrays.stream(arr2)
        .map(i -> -i).sorted().map(i -> -i) // just use 'sorted()' for ascending order
// Edit - use map(i -> ~i).sorted().map(i -> ~i) to be safe from the issue with Integer.MIN_VALUE
        .toArray();

编辑: 要进行原地升序排序,只需执行以下操作:

int[] arr2 = new int[]{54, 432, 53, 21, 43};
Arrays.sort(arr2);

3
对于升序,您可以使用 Arrays.stream(arr2).sorted().toArray() 或直接使用 Arrays.sort(arr2) 原地排序。但是,请注意,如果一个或多个 Integer.MIN_VALUE 的值出现在值中,则否定值的技巧将无法使用。 - Holger
@Holger 确实,这就是我在 map.sort.map 解决方案之前关于边界值的注释所指的。 - Naman
4
根据Stuart Marks的回答所述,使用二进制非(~)代替减号(-)可以解决Integer.MIN_VALUE的问题。 - Holger
@Holger 感谢分享。我一定会花些时间后来阅读 Stuart 的回答。 - Naman

12

假设

int[] array = ... ;

要升序排列,只需执行

Arrays.sort(array);

这里有一个漂亮的降序排序方法:

Arrays.setAll(array, i -> ~array[i]);
Arrays.sort(array);
Arrays.setAll(array, i -> ~array[i]);

这比升序排序再反转数组慢一点点;它需要在数组上进行额外的一次扫描。对于任何大小的数组,运行时间都由排序主导,因此不太可能引起注意。

这个方法通过在排序前后对int值进行按位取反来完成。这提供了每个可能的int值顺序的精确、无损反转。要理解这一点,您必须了解Java int使用二进制补码表示法。考虑如果int只有三位。所有的值都将如下所示:

         100  101  110  111  000  001  010  011
          -4   -3   -2   -1   0    1    2    3
MIN_VALUE ^
位非运算符~将每个位取反。你可以通过检查表在-1和0之间的中心点来看到这一点,因此-4变成了3,-3变成了2等等。另外,再次执行位非操作会恢复原始值。因此,在对被位非运算后的值进行升序排序时,就是对原始值进行降序排序。
请注意,这与否定运算符-不同,在这里它不能正确地工作。它反映了零的表,所以零的否定是零,-1的否定是1等等。这是不对称的,因为MIN_VALUE的否定是MIN_VALUE。因此,尝试使用否定运算符进行降序排序是行不通的。
最后,装箱并使用比较器可以实现,但速度要慢得多,并且为(几乎)每个int值分配一个单独的对象。我建议避免装箱。

2
你好!需要用位来完成所有这些工作,这是否意味着需要一种新的方法?例如Arrays.sortDescending。 - fps
4
很可能。添加一些原始数组操作可能会很有用。这似乎是一个经常被问到的问题,常见答案(如涉及装箱的答案)表现相当差。 - Stuart Marks

7

按升序排序:

  1. int[] ascArr = Arrays.stream(arr2).boxed().sorted(Comparator.naturalOrder())
                                      .mapToInt(Integer::intValue).toArray();
    
  2. int[] ascArr = IntStream.of(arr2).boxed().sorted((a, b) -> Integer.compare(a, b))
                                     .mapToInt(Integer::intValue).toArray();
    
  3. int[] ascArr = Arrays.stream(arr2).sorted().toArray();


按降序排序:

  1. int[] descArr = Arrays.stream(arr2).boxed().sorted(Comparator.reverseOrder())
                                       .mapToInt(Integer::intValue).toArray();
    
  2. int[] descArr = IntStream.of(arr2).boxed().sorted((a, b) -> Integer.compare(b, a))
                                      .mapToInt(Integer::intValue).toArray();
    

4

有不同的方法可以使用流从未排序的int数组中创建一个排序的数组,正如其他答案所示。这些方法的缺点是要求装箱/拆箱(以便可以使用Comparator<Integer>),或者需要新数组来容纳元素。

下面是一种无需装箱/拆箱即可就地排序的方法。首先按升序排列:

int[] arr2 = ...
Arrays.sort(arr2);

很遗憾,没有办法使用单行操作来就地以降序排序。您需要先按升序排序,然后反转数组:

int[] arr2 = ...
Arrays.sort(arr2);

int size = arr2.length;
for (int left = 0; left < size / 2; left++) {
    int right = size - i - 1;
    int temp = arr2[right];
    arr2[right] = arr2[left];
    arr2[left] = temp;
}

编辑: 如评论区@Holger指出,上面的for循环可以以下方式改进:

for (int left = 0, right = arr2.length - 1; left < right; left++, right--) {
    int temp = arr2[right];
    arr2[right] = arr2[left];
    arr2[left] = temp;
}

另一种方法是按升序排序,然后将元素以相反的顺序放入新数组中:
int[] arr2 = ...
Arrays.sort(arr2);

int size = arr2.length;
int[] reversed = new int[size];
Arrays.setAll(reversed, i -> arr2[size - i - 1]);

使用 Arrays.setAll 来完成任务。


2
你可以考虑一个更简单的 for(int left = 0, right = arr2.length-1; left < right; left++, right--) … 在某些情况下可能更有效率。 - Holger

2
public class lambdaTest {

    public static void main(String[] args) {
        int[] arr2 = new int[] {54,432,53,21,43};

        int[] sorted = IntStream.of(arr2)
                .boxed()
                .sorted(Comparator.reverseOrder())
                .mapToInt(i -> i)
                .toArray();
        System.out.println("In decending Order:");
        for(int ss : sorted)
        {
            System.out.print(ss+", ");
        }
        System.out.println();
        int[] reversed = IntStream.range(0, sorted.length)
                .map(i -> sorted[sorted.length-i-1])
                .toArray();
        System.out.println("In Ascending Order: ");

        for(int ss1 : reversed)
        {
            System.out.print(ss1+", ");
        }
    }

}

输出

按降序排列:

432,54,53,43,21,

按升序排列:

21,43,53,54,432,

编辑:更简单的方法是按自然排序 (IntStream.of(arr2) .sorted() .toArray())

感谢 Holger


2
更简单的方法是按自然顺序排序(IntStream.of(arr2) .sorted() .toArray()),然后将其反转,以获得两者,而不是按相反的顺序排序,再将其反转。 - Holger
是的,我明白了,但我提出了另一种做法:?希望这样可以吗? - Vishwa Ratna
2
当然,但是采用反转数组的方法有一个优点,即在进行直接升序排序时,可以避免任何装箱开销。当使用比较器时,进行第一次排序时会产生装箱值(不可避免),这个优势就会丧失。 - Holger

2
如果你需要一个自定义比较器lambda,可以尝试使用以下代码:
    int[] arr2 = new int[] {54,432,53,21,43};
    int[] arr3 = IntStream.of(arr2).boxed().sorted((a, b) -> a - b).mapToInt(Integer::intValue).toArray();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接