Java Array sort: быстрый способ получить отсортированный список индексов массива - PullRequest
38 голосов
/ 04 июня 2009

Проблема: рассмотрим следующие числа с плавающей точкой []:

d[i] =     1.7 -0.3  2.1  0.5

Мне нужен массив int [], который представляет порядок исходного массива с индексами.

s[i] =       1    3    0    2
d[s[i]] = -0.3  0.5  1.7  2.1

Конечно, это можно сделать с помощью специального компаратора, отсортированного набора пользовательских объектов или просто отсортировав массив и затем выполнив поиск индексов в исходном массиве (дрожь).

Что я на самом деле ищу, так это эквивалент второго аргумента возврата функции сортировки Matlab .

Есть ли простой способ сделать это (<5 LOC)? Может ли быть решение, при котором не нужно выделять новый объект для каждого элемента? </p>


Обновление:

Спасибо за ваши ответы. К сожалению, ни одно из того, что было предложено до сих пор, не напоминает простое и эффективное решение, на которое я надеялся. Поэтому я открыл ветку на форуме отзывов JDK, предлагая добавить новую функцию библиотеки классов для решения этой проблемы. Давайте посмотрим, что Sun / Oracle думает об этой проблеме.

http://forums.java.net/jive/thread.jspa?threadID=62657&tstart=0

Ответы [ 15 ]

29 голосов
/ 06 апреля 2010

Простое решение для создания массива индексатора: сортируйте индексатор, сравнивая значения данных:

final Integer[] idx = { 0, 1, 2, 3 };
final float[] data = { 1.7f, -0.3f,  2.1f,  0.5f };

Arrays.sort(idx, new Comparator<Integer>() {
    @Override public int compare(final Integer o1, final Integer o2) {
        return Float.compare(data[o1], data[o2]);
    }
});
23 голосов
/ 04 июня 2009

Создать TreeMap значений для индексов

    float[] array = new float[]{};
    Map<Float, Integer> map = new TreeMap<Float, Integer>();
    for (int i = 0; i < array.length; ++i) {
        map.put(array[i], i);
    }
    Collection<Integer> indices = map.values();

индексы будут отсортированы по поплавкам, на которые они указывают, исходный массив не будет затронут. Преобразование Collection<Integer> в int[] оставляется в качестве упражнения, если это действительно необходимо.

EDIT: Как отмечено в комментариях, этот подход не работает, если в массиве с плавающей точкой есть повторяющиеся значения. Эту проблему можно решить, превратив Map<Float, Integer> в Map<Float, List<Integer>>, хотя это несколько усложнит внутреннюю часть цикла for и генерацию окончательного набора.

15 голосов
/ 24 июня 2009

Я бы настроил алгоритм быстрой сортировки для одновременного выполнения операции обмена над несколькими массивами: массивом индекса и массивом значений. Например (на основе этой быстрой сортировки ):

public static void quicksort(float[] main, int[] index) {
    quicksort(main, index, 0, index.length - 1);
}

// quicksort a[left] to a[right]
public static void quicksort(float[] a, int[] index, int left, int right) {
    if (right <= left) return;
    int i = partition(a, index, left, right);
    quicksort(a, index, left, i-1);
    quicksort(a, index, i+1, right);
}

// partition a[left] to a[right], assumes left < right
private static int partition(float[] a, int[] index, 
int left, int right) {
    int i = left - 1;
    int j = right;
    while (true) {
        while (less(a[++i], a[right]))      // find item on left to swap
            ;                               // a[right] acts as sentinel
        while (less(a[right], a[--j]))      // find item on right to swap
            if (j == left) break;           // don't go out-of-bounds
        if (i >= j) break;                  // check if pointers cross
        exch(a, index, i, j);               // swap two elements into place
    }
    exch(a, index, i, right);               // swap with partition element
    return i;
}

// is x < y ?
private static boolean less(float x, float y) {
    return (x < y);
}

// exchange a[i] and a[j]
private static void exch(float[] a, int[] index, int i, int j) {
    float swap = a[i];
    a[i] = a[j];
    a[j] = swap;
    int b = index[i];
    index[i] = index[j];
    index[j] = b;
}
13 голосов
/ 29 февраля 2016

Использование функций Java 8 (без дополнительной библиотеки), краткий способ достижения этого.

int[] a = {1,6,2,7,8}
int[] sortedIndices = IntStream.range(0, a.length)
                .boxed().sorted((i, j) -> a[i] - a[j])
                .mapToInt(ele -> ele).toArray();
7 голосов
/ 04 июня 2009

С Функциональная Java :

import static fj.data.Array.array;
import static fj.pre.Ord.*;
import fj.P2;

array(d).toStream().zipIndex().sort(p2Ord(doubleOrd, intOrd))
  .map(P2.<Double, Integer>__2()).toArray();
2 голосов
/ 06 мая 2014
public static int[] indexSort(final double[] v, boolean keepUnsorted) {
    final Integer[] II = new Integer[v.length];
    for (int i = 0; i < v.length; i++) II[i] = i;
    Arrays.sort(II, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Double.compare(v[o1],v[o2]);
        }
    });
    int[] ii = new int[v.length];
    for (int i = 0; i < v.length; i++) ii[i] = II[i];
    if (!keepUnsorted) {
        double[] clon = v.clone();
        for (int i = 0; i < v.length; i++) v[i] = clon[II[i]];
    }
    return ii;
}
2 голосов
/ 26 сентября 2011

Лучшее решение было бы по типу qsort C, которое позволяет вам определять функции для сравнения и обмена, поэтому qsort не нужно знать тип или организацию сортируемых данных. Вот тот, который вы можете попробовать. Поскольку в Java нет функций, используйте внутренний класс Array, чтобы обернуть массив или коллекцию для сортировки. Затем оберните это в IndexArray и сортируйте. Результатом getIndex () для IndexArray будет массив индексов, как описано в JavaDoc.

public class QuickSortArray {

public interface Array {
    int cmp(int aindex, int bindex);
    void swap(int aindex, int bindex);
    int length();
}

public static void quicksort(Array a) {
    quicksort(a, 0, a.length() - 1);
}

public static void quicksort(Array a, int left, int right) {
    if (right <= left) return;
    int i = partition(a, left, right);
    quicksort(a, left, i-1);
    quicksort(a, i+1, right);
}

public static boolean isSorted(Array a) {
    for (int i = 1, n = a.length(); i < n; i++) {
        if (a.cmp(i-1, i) > 0)
            return false;
    }
    return true;
}

private static int mid(Array a, int left, int right) {
    // "sort" three elements and take the middle one
    int i = left;
    int j = (left + right) / 2;
    int k = right;
    // order the first two
    int cmp = a.cmp(i, j);
    if (cmp > 0) {
        int tmp = j;
        j = i;
        i = tmp;
    }
    // bubble the third down
    cmp = a.cmp(j, k);
    if (cmp > 0) {
        cmp = a.cmp(i, k);
        if (cmp > 0)
            return i;
        return k;
    }
    return j;
}

private static int partition(Array a, int left, int right) {
    int mid = mid(a, left, right);
    a.swap(right, mid);
    int i = left - 1;
    int j = right;

    while (true) {
        while (a.cmp(++i, right) < 0)
            ;
        while (a.cmp(right, --j) < 0)
            if (j == left) break;
        if (i >= j) break;
        a.swap(i, j);
    }
    a.swap(i, right);
    return i;
}

public static class IndexArray implements Array {
    int[] index;
    Array a;

    public IndexArray(Array a) {
        this.a = a;
        index = new int[a.length()];
        for (int i = 0; i < a.length(); i++)
            index[i] = i;
    }

    /**
     * Return the index after the IndexArray is sorted.
     * The nested Array is unsorted. Assume the name of
     * its underlying array is a. The returned index array
     * is such that a[index[i-1]] <= a[index[i]] for all i
     * in 1..a.length-1.
     */
    public int[] index() {
        int i = 0;
        int j = index.length - 1;
        while (i < j) {
            int tmp = index[i];
            index[i++] = index[j];
            index[j--] = tmp;
        }
        int[] tmp = index;
        index = null;
        return tmp;
    }

    @Override
    public int cmp(int aindex, int bindex) {
        return a.cmp(index[aindex], index[bindex]);
    }

    @Override
    public void swap(int aindex, int bindex) {
        int tmp = index[aindex];
        index[aindex] = index[bindex];
        index[bindex] = tmp;
    }

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

}
2 голосов
/ 15 ноября 2009

Более общий случай ответа Джерико , который допускает дублирование значений, будет следующим:

// Assuming you've got: float[] array; defined already

TreeMap<Float, List<Integer>> map = new TreeMap<Float, List<Integer>>();
for(int i = 0; i < array.length; i++) {
    List<Integer> ind = map.get(array[i]);
    if(ind == null){
        ind = new ArrayList<Integer>();
        map.put(array[i], ind);
    }
    ind.add(i);
}

// Now flatten the list
List<Integer> indices = new ArrayList<Integer>();
for(List<Integer> arr : map.values()) {
    indices.addAll(arr);
}
0 голосов
/ 26 мая 2016

Я бы хотел использовать это, потому что это очень быстро. Но я использую его для int, вы можете изменить его на float.

private static void mergeSort(int[]array,int[] indexes,int start,int end){
    if(start>=end)return;
    int middle = (end-start)/2+start;
    mergeSort(array,indexes,start,middle);
    mergeSort(array,indexes,middle+1,end);
    merge(array,indexes,start,middle,end);
}
private static void merge(int[]array,int[] indexes,int start,int middle,int end){
    int len1 = middle-start+1;
    int len2 = end - middle;
    int leftArray[] = new int[len1];
    int leftIndex[] = new int[len1];
    int rightArray[] = new int[len2];
    int rightIndex[] = new int[len2];
    for(int i=0;i<len1;++i)leftArray[i] = array[i+start];
    for(int i=0;i<len1;++i)leftIndex[i] = indexes[i+start];
    for(int i=0;i<len2;++i)rightArray[i] = array[i+middle+1];
    for(int i=0;i<len2;++i)rightIndex[i] = indexes[i+middle+1];
    //merge
    int i=0,j=0,k=start;
    while(i<len1&&j<len2){
        if(leftArray[i]<rightArray[j]){
            array[k] = leftArray[i];
            indexes[k] = leftIndex[i];
            ++i;
        }
        else{
            array[k] = rightArray[j];
            indexes[k] = rightIndex[j];
            ++j;
        }
        ++k;
    }
    while(i<len1){
        array[k] = leftArray[i];
        indexes[k] = leftIndex[i];
        ++i;++k;
    }
    while(j<len2){
        array[k] = rightArray[j];
        indexes[k] = rightIndex[j];
        ++j;++k;
    }
}
0 голосов
/ 19 ноября 2014

Ниже приведен метод, основанный на сортировке вставкой

public static int[] insertionSort(float[] arr){
    int[] indices = new int[arr.length];
        indices[0] = 0;
        for(int i=1;i<arr.length;i++){
            int j=i;
            for(;j>=1 && arr[j]<arr[j-1];j--){
                    float temp = arr[j];
                    arr[j] = arr[j-1];
                    indices[j]=indices[j-1];
                    arr[j-1] = temp;
            }
            indices[j]=i;
        }
        return indices;//indices of sorted elements
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...