Wie sortiere ich ein Array von Ints mit einem benutzerdefinierten Komparator?

75

Ich muss ein Array von Ints mit einem benutzerdefinierten Komparator sortieren, aber die Java-Bibliothek bietet keine Sortierfunktion für Ints mit Komparatoren (Komparatoren können nur mit Objekten verwendet werden). Gibt es eine einfache Möglichkeit, dies zu tun?

Alexandru
quelle
Möchten Sie das Array nur in absteigender Reihenfolge sortieren oder möchten Sie etwas Komplizierteres ausführen?
Roman
Etwas komplizierteres. Ich möchte das int mit dem absoluten Wert als Schlüssel sortieren.
Alexandru

Antworten:

61

Wenn Sie den Typ Ihres Eingabearrays nicht ändern können, funktioniert Folgendes:

final int[] data = new int[] { 5, 4, 2, 1, 3 };
final Integer[] sorted = ArrayUtils.toObject(data);
Arrays.sort(sorted, new Comparator<Integer>() {
    public int compare(Integer o1, Integer o2) {
        // Intentional: Reverse order for this demo
        return o2.compareTo(o1);
    }
});
System.arraycopy(ArrayUtils.toPrimitive(sorted), 0, data, 0, sorted.length);

Dies wird ArrayUtilsaus dem commons-lang-Projekt verwendet, um einfach zwischen int[]und zu konvertieren Integer[], eine Kopie des Arrays zu erstellen, die Sortierung durchzuführen und dann die sortierten Daten über das Original zu kopieren.

Jon Freedman
quelle
3
Warum verwenden Sie nicht Arrays.sort, anstatt Array -> Liste -> Array zu konvertieren?
Nanda
Ein guter Punkt, den ich aktualisiert habe, war, mit Commons-Primitiven herumzuspielen, aber eigentlich nichts Nützliches zu tun
Jon Freedman
Ich wusste nichts über Commons-Lang. Danke für den Tipp.
Alexandru
4
return o2.compareTo(o1);ist das richtig? Ich glaube auf diese Weise wird die Reihenfolge umgekehrt, wie wir erwarten ...
Pedro Dusso
1
Ja, die Reihenfolge ist umgekehrt, ich habe das gewählt, um zu beweisen, dass die Reihenfolge anders war als die natürliche Reihenfolge vonint
Jon Freedman
31

Wie wäre es mit Streams (Java 8)?

int[] ia = {99, 11, 7, 21, 4, 2};
ia = Arrays.stream(ia).
    boxed().
    sorted((a, b) -> b.compareTo(a)). // sort descending
    mapToInt(i -> i).
    toArray();

Oder an Ort und Stelle:

int[] ia = {99, 11, 7, 21, 4, 2};
System.arraycopy(
        Arrays.stream(ia).
            boxed().
            sorted((a, b) -> b.compareTo(a)). // sort descending
            mapToInt(i -> i).
            toArray(),
        0,
        ia,
        0,
        ia.length
    );
user3669782
quelle
6
Es nervt mich, dass wir IntStream nicht sortiert haben können (IntComparator).
Trejkaz
9
Nicht in (a, b) -> b - aumgekehrter Reihenfolge verwenden. Dieser Komparator kann überlaufen. Kümmere dich um die Existenz von Comparator.reverseOrder()
Holger
1
Den möglichen Überlauf komplett verpasst. Die Antwort wurde angepasst. Danke Holger!
user3669782
8

Wenn Sie das Array nicht kopieren möchten (sagen wir, es ist sehr groß), möchten Sie möglicherweise einen Wrapper erstellen List<Integer>, der in einer Sortierung verwendet werden kann:

final int[] elements = {1, 2, 3, 4};
List<Integer> wrapper = new AbstractList<Integer>() {

        @Override
        public Integer get(int index) {
            return elements[index];
        }

        @Override
        public int size() {
            return elements.length;
        }

        @Override
        public Integer set(int index, Integer element) {
            int v = elements[index];
            elements[index] = element;
            return v;
        }

    };

Und jetzt können Sie diese Wrapper-Liste mit einem benutzerdefinierten Komparator sortieren.

user1460736
quelle
Ich mag das viel besser als die akzeptierte Antwort. Sie müssen keine Array-Inhalte kopieren oder konvertieren. Nutzen Sie einfach die benutzerdefinierte Implementierung von Listen.
OB1
2
@ OB1: Es sieht ordentlich aus, aber die Standardimplementierung sortkopiert die gesamte Liste in ein Array, sortiert sie und schreibt sie zurück. Und da diese Liste den RandomAccessMarker nicht implementiert , wird beim Zurückschreiben ein verwendet, ListIteratoranstatt nur aufzurufen set.
Holger
Wow, Holger hat Recht mit der Kopie. Ich dachte nicht einmal daran, dies zu überprüfen, da ich davon ausging, dass niemand so verrückt wäre, eine Kopie zu machen.
user1460736
1
@ user1460736 Die Javadocs sagen, dass dies absichtlich gemacht wird, da Listenimplementierungen für den wahlfreien Zugriff möglicherweise ineffizient sind. ZB LinkedListwäre es super schlecht direkt zu sortieren, also machen sie eine Kopie. Warum sie nicht suchen, RandomAccessist nicht klar, ich denke, nicht viele Leute kennen diese Marker-Oberfläche überhaupt.
Dmitry Avtonomov
Eine Erweiterung RandomAccesswürde nicht schaden, wenn diese Optimierung irgendwann in der Zukunft durchgeführt wird. Derzeit erreicht die Methode jedoch nicht das, wofür sie eingerichtet wurde.
Maarten Bodewes
4

Sie benötigen keine externe Bibliothek:

Integer[] input = Arrays.stream(arr).boxed().toArray(Integer[]::new);
Arrays.sort(input, (a, b) -> b - a); // reverse order
return Arrays.stream(input).mapToInt(Integer::intValue).toArray();
XL Zheng
quelle
0

Hier ist eine Hilfsmethode, um die Arbeit zu erledigen.

Zunächst benötigen Sie eine neue Comparator-Oberfläche, da Comparator keine Grundelemente unterstützt:

public interface IntComparator{
    public int compare(int a, int b);
}

(Sie könnten es natürlich mit Autoboxing / Unboxing machen, aber ich werde nicht dorthin gehen, das ist hässlich)

Hier ist eine Hilfsmethode zum Sortieren eines int-Arrays mit diesem Komparator:

public static void sort(final int[] data, final IntComparator comparator){
    for(int i = 0; i < data.length + 0; i++){
        for(int j = i; j > 0
            && comparator.compare(data[j - 1], data[j]) > 0; j--){
            final int b = j - 1;
            final int t = data[j];
            data[j] = data[b];
            data[b] = t;
        }
    }
}

Und hier ist ein Client-Code. Ein dummer Komparator, der alle Zahlen sortiert, die nur aus der Ziffer '9' nach vorne bestehen (wieder nach Größe sortiert) und dann den Rest (was auch immer gut ist):

final int[] data =
    { 4343, 544, 433, 99, 44934343, 9999, 32, 999, 9, 292, 65 };
sort(data, new IntComparator(){

    @Override
    public int compare(final int a, final int b){
        final boolean onlyNinesA = this.onlyNines(a);
        final boolean onlyNinesB = this.onlyNines(b);
        if(onlyNinesA && !onlyNinesB){
            return -1;
        }
        if(onlyNinesB && !onlyNinesA){
            return 1;
        }

        return Integer.valueOf(a).compareTo(Integer.valueOf(b));
    }

    private boolean onlyNines(final int candidate){
        final String str = String.valueOf(candidate);
        boolean nines = true;
        for(int i = 0; i < str.length(); i++){
            if(!(str.charAt(i) == '9')){
                nines = false;
                break;
            }
        }
        return nines;
    }
});

System.out.println(Arrays.toString(data));

Ausgabe:

[9, 99, 999, 9999, 32, 65, 292, 433, 544, 4343, 44934343]

Der Sortiercode wurde aus Arrays.sort (int []) übernommen , und ich habe nur die Version verwendet, die für kleine Arrays optimiert ist. Für eine echte Implementierung sollten Sie sich wahrscheinlich den Quellcode der internen Methode sort1(int[], offset, length)in der Arrays- Klasse ansehen .

Sean Patrick Floyd
quelle
5
Arrays.sort () scheint Quicksort zu verwenden, wenn der Code betrachtet wird, während die vorgeschlagene Sortierung die Einfügesortierung zu verwenden scheint. Wäre es nicht asymptotisch langsamer?
Sudarshan S
Ja, es ist inakzeptabel langsam, es sei denn, das Array ist sehr kurz
Stefan Reich
0

Ich habe maximal versucht, den Komparator mit primitivem Typ selbst zu verwenden. Endlich kam ich zu dem Schluss, dass es keine Möglichkeit gibt, den Komparator zu betrügen. Dies ist meine Implementierung.

public class ArrSortComptr {
    public static void main(String[] args) {

         int[] array = { 3, 2, 1, 5, 8, 6 };
         int[] sortedArr=SortPrimitiveInt(new intComp(),array);
         System.out.println("InPut "+ Arrays.toString(array));
         System.out.println("OutPut "+ Arrays.toString(sortedArr));

    }
 static int[] SortPrimitiveInt(Comparator<Integer> com,int ... arr)
 {
    Integer[] objInt=intToObject(arr);
    Arrays.sort(objInt,com);
    return intObjToPrimitive(objInt);

 }
 static Integer[] intToObject(int ... arr)
 {
    Integer[] a=new Integer[arr.length];
    int cnt=0;
    for(int val:arr)
      a[cnt++]=new Integer(val);
    return a;
 }
 static int[] intObjToPrimitive(Integer ... arr)
 {
     int[] a=new int[arr.length];
     int cnt=0;
     for(Integer val:arr)
         if(val!=null)
             a[cnt++]=val.intValue();
     return a;

 }

}
class intComp implements Comparator<Integer>
{

    @Override //your comparator implementation.
    public int compare(Integer o1, Integer o2) {
        // TODO Auto-generated method stub
        return o1.compareTo(o2);
    }

}

@Roman: Ich kann nicht sagen, dass dies ein gutes Beispiel ist, aber seit du gefragt hast, ist mir das in den Sinn gekommen. Angenommen, Sie möchten in einem Array Zahlen nur nach ihrem absoluten Wert sortieren.

Integer d1=Math.abs(o1);
Integer d2=Math.abs(o2);
return d1.compareTo(d2);

Ein anderes Beispiel kann sein, dass Sie nur Zahlen größer als 100 sortieren möchten. Es hängt tatsächlich von der Situation ab. Ich kann mir keine weiteren Situationen vorstellen. Vielleicht kann Alexandru weitere Beispiele nennen, da er sagt, er möchte einen Komparator für das int-Array verwenden .

Emil
quelle
@Emil: Entschuldigung für ein wenig offtop, aber ich bin nur neugierig, können Sie mir bitte ein Beispiel für einen Komparator zeigen, mit dem Sie eine Reihe von ganzen Zahlen sortiert haben? Ich kann mir einfach keine Implementierung vorstellen, außer return sign * (i1 - i2);wo sign-1 oder +1 ist, abhängig von der gewünschten Reihenfolge.
Roman
@Emil: Eigentlich ist die Implementierung, die ich gerade gezeigt habe, wahrscheinlich kaputt (Ints sollten zuerst zu lang gegossen werden), aber das spielt im Kontext keine Rolle.
Roman
Wollen Sie damit sagen, dass ein Komparator für Ganzzahlen nur in aufsteigender und absteigender Reihenfolge erforderlich ist?
Emil
@Emil: fast ja, aber ich sagte, dass nur ich mir keinen anderen Fall vorstellen kann.
Roman
@Roman: Ich habe der Antwort ein Beispiel angehängt. Ich weiß nicht, ob Sie das erwartet haben.
Emil
0

Hier ist ein Code (es ist eigentlich nicht Timsort, wie ich ursprünglich dachte, aber es funktioniert gut), der den Trick ohne Boxen / Unboxing macht. In meinen Tests funktioniert es 3-4 mal schneller als die Verwendung von Collections.sort mit einem List-Wrapper um das Array.

// This code has been contributed by 29AjayKumar 
// from: https://www.geeksforgeeks.org/sort/

static final int sortIntArrayWithComparator_RUN = 32; 

// this function sorts array from left index to  
// to right index which is of size atmost RUN  
static void sortIntArrayWithComparator_insertionSort(int[] arr, IntComparator comparator, int left, int right) { 
    for (int i = left + 1; i <= right; i++)  
    { 
        int temp = arr[i]; 
        int j = i - 1; 
        while (j >= left && comparator.compare(arr[j], temp) > 0)
        { 
            arr[j + 1] = arr[j]; 
            j--; 
        } 
        arr[j + 1] = temp; 
    } 
} 

// merge function merges the sorted runs  
static void sortIntArrayWithComparator_merge(int[] arr, IntComparator comparator, int l, int m, int r) { 
    // original array is broken in two parts  
    // left and right array  
    int len1 = m - l + 1, len2 = r - m; 
    int[] left = new int[len1]; 
    int[] right = new int[len2]; 
    for (int x = 0; x < len1; x++)  
    { 
        left[x] = arr[l + x]; 
    } 
    for (int x = 0; x < len2; x++)  
    { 
        right[x] = arr[m + 1 + x]; 
    } 

    int i = 0; 
    int j = 0; 
    int k = l; 

    // after comparing, we merge those two array  
    // in larger sub array  
    while (i < len1 && j < len2)  
    { 
        if (comparator.compare(left[i], right[j]) <= 0)
        { 
            arr[k] = left[i]; 
            i++; 
        } 
        else 
        { 
            arr[k] = right[j]; 
            j++; 
        } 
        k++; 
    } 

    // copy remaining elements of left, if any  
    while (i < len1) 
    { 
        arr[k] = left[i]; 
        k++; 
        i++; 
    } 

    // copy remaining element of right, if any  
    while (j < len2)  
    { 
        arr[k] = right[j]; 
        k++; 
        j++; 
    } 
} 

// iterative sort function to sort the  
// array[0...n-1] (similar to merge sort)  
static void sortIntArrayWithComparator(int[] arr, IntComparator comparator) { sortIntArrayWithComparator(arr, lIntArray(arr), comparator); }
static void sortIntArrayWithComparator(int[] arr, int n, IntComparator comparator) { 
    // Sort individual subarrays of size RUN  
    for (int i = 0; i < n; i += sortIntArrayWithComparator_RUN)  
    { 
        sortIntArrayWithComparator_insertionSort(arr, comparator, i, Math.min((i + 31), (n - 1))); 
    } 

    // start merging from size RUN (or 32). It will merge  
    // to form size 64, then 128, 256 and so on ....  
    for (int size = sortIntArrayWithComparator_RUN; size < n; size = 2 * size)  
    { 
          
        // pick starting point of left sub array. We  
        // are going to merge arr[left..left+size-1]  
        // and arr[left+size, left+2*size-1]  
        // After every merge, we increase left by 2*size  
        for (int left = 0; left < n; left += 2 * size)  
        { 
              
            // find ending point of left sub array  
            // mid+1 is starting point of right sub array  
            int mid = Math.min(left + size - 1, n - 1);
            int right = Math.min(left + 2 * size - 1, n - 1); 

            // merge sub array arr[left.....mid] &  
            // arr[mid+1....right]  
            sortIntArrayWithComparator_merge(arr, comparator, left, mid, right); 
        } 
    } 
}

static int lIntArray(int[] a) {
  return a == null ? 0 : a.length;
}

static interface IntComparator {
  int compare(int a, int b);
}
Stefan Reich
quelle