83

Suppose the user enter an array, for example:

Array = {France, Spain, France, France, Italy, Spain, Spain, Italy}

which I did know the length of it

the index array would be:

index = {0, 1, 2, 3, 4, 5, 6, 7}

Now, after sorting it using Arrays.sort(Array);

newArray will be like:

newArray = {France, France, France, Italy, Italy, Spain, Spain, Spain}

and the newIndex will be:

newIndex = {0, 2, 3, 4, 7, 1, 5, 6}

The problem is: how can I find the newIndex from the input Array?

starball
  • 20,030
  • 7
  • 43
  • 238
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • Similar to http://stackoverflow.com/questions/4839915/sorting-a-list-based-on-another-lists-values-java/4839994#4839994 but much more clearly defined. – maaartinus Feb 01 '11 at 05:49
  • jfyi, here is how it looks with scala stdlib : input.zipWithIndex.sorted.map( _._2). A bit less efficient(you won't notice the differeentce in most of mundane cases) but painless. – ievgen.garkusha Nov 15 '21 at 22:38

8 Answers8

106

Don't sort the array to start with. Sort the index array, passing in a comparator which compares values by using them as indexes into the array. So you end up with newIndex as the result of the sort, and it's trivial to go from there to the sorted array of actual items.

Admittedly that means sorting an array of integers in a custom way - which either means using an Integer[] and the standard Java library, or a 3rd party library which has an "IntComparator" interface which can be used in conjunction with a sort(int[], IntComparator) type of method.

EDIT: Okay, here's an example comparator. For the sake of simplicity I'll assume you only want to sort an "original" array of strings... and I won't bother with nullity testing.

public class ArrayIndexComparator implements Comparator<Integer>
{
    private final String[] array;

    public ArrayIndexComparator(String[] array)
    {
        this.array = array;
    }

    public Integer[] createIndexArray()
    {
        Integer[] indexes = new Integer[array.length];
        for (int i = 0; i < array.length; i++)
        {
            indexes[i] = i; // Autoboxing
        }
        return indexes;
    }

    @Override
    public int compare(Integer index1, Integer index2)
    {
         // Autounbox from Integer to int to use as array indexes
        return array[index1].compareTo(array[index2]);
    }
}

You'd use it like this:

String[] countries = { "France", "Spain", ... };
ArrayIndexComparator comparator = new ArrayIndexComparator(countries);
Integer[] indexes = comparator.createIndexArray();
Arrays.sort(indexes, comparator);
// Now the indexes are in appropriate order.
Emond
  • 50,210
  • 11
  • 84
  • 115
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
31

Concise way of achieving this with Java 8 Stream API,

final String[] strArr = {"France", "Spain", "France"};
int[] sortedIndices = IntStream.range(0, strArr.length)
                .boxed().sorted((i, j) -> strArr[i].compareTo(strArr[j]) )
                .mapToInt(ele -> ele).toArray();
Pratap Koritala
  • 1,557
  • 14
  • 16
  • How do you sort it in reverse/descending order? If the array is of double datatype. – kingspp Mar 07 '17 at 12:25
  • You would use compareTo method on top of Double – Pratap Koritala May 10 '17 at 23:39
  • 1
    To sort it in reverse order, you just negate both comparing numbers. – Gregor Ažbe Nov 09 '18 at 07:59
  • 3
    If you don't mind using Integer[] then this nice code snipped can be simplified by removing the mapToInt operation... Integer[] sortedIndices = IntStream.range(0, strArr.length) .boxed().sorted((i, j) -> strArr[i].compareTo(strArr[j]) ).toArray(Integer[]::new); – greg Mar 07 '19 at 09:11
8
TreeMap<String,Int> map = new TreeMap<String,Int>();
for( int i : indexes ) {
    map.put( stringarray[i], i );
}

Now iterator over map.values() to retrieve the indexes in sort order, and over map.keySet() to get the strings, or over map.entrySet() to get the String-index-Pairs.

Daniel
  • 27,718
  • 20
  • 89
  • 133
  • 4
    This doesn't work due to duplicates. A SortedMultimap wouldn't help here. – maaartinus Feb 01 '11 at 05:51
  • @maaartinus It could be made to work with a map of TreeMap>. First you'd add all the strings to the map with empty lists, then you'd iterate through the original list and add the indices to the map item lists. – Parthian Shot Oct 10 '15 at 21:26
  • Then you'd simply iterate over values and insert an element for each index in the list... It'd be stable. – Parthian Shot Oct 10 '15 at 21:27
  • 2
    @ParthianShot Actually, [`MultimapBuilder.treeKeys().linkedListValues().build()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/MultimapBuilder.html#treeKeys()) would do exactly that in a single pass. – maaartinus Oct 11 '15 at 03:52
6

If having a scenario of repeatedly sorting primitive float or int arrays with positive values, then a method like below yields much better (x3~x4) speed compared to using any comparators:

long time = System.currentTimeMillis();
for (int i = 0; i < iters; i++) {           
    float[] array = RandomUtils.randomFloatArray(-1,  1, 3000);
    long[] valueKeyPairs = new long[array.length]; 
    for (int j = 0; j < array.length; ++j) {
        valueKeyPairs[j] = (((long) Float.floatToIntBits(array[j])) << 32) | (j & 0xffffffffL);
    }
    Arrays.sort(valueKeyPairs);
    /**Then use this to retrieve the original value and index*/
    //long l = valueKeyPairs[j];
    //float value = Float.intBitsToFloat((int) (l >> 32));
    //int index = (int) (l);
}
long millis = System.currentTimeMillis() - time;
Tom
  • 3,168
  • 5
  • 27
  • 36
3

I made the following based on @Skeet's code. I think it is a little more OOPie. I dunno.

public static <T extends Comparable<T>> List<Integer> sortIndex(List<T> in) {
    ArrayList<Integer> index = new ArrayList<>();
    for (int i = 0; i < in.size(); i++) {
        index.add(i);
    }

    Collections.sort(index, new Comparator<Integer>() {
        @Override
        public int compare(Integer idx1, Integer idx2) {
            return in.get(idx1).compareTo(in.get(idx2));
        }
    });

    return index;
}

Instead of the class that implements the sorting and indexing having the Comparator code for the different objects coming in, the objects in the original array must implement the Comparable interface. It seems many objects of interest have a natural ordering and have the Comparable interface implemented already.

public static void main(String[] args) {

    List<Integer> a1 = new ArrayList<>(Arrays.asList(2, 3, 9, 4, 1));
    // Just pass in the list to have its indexes sorted by the natural ordering
    List<Integer> idx = sortIndex(a1);

    List<Double> a2 = new ArrayList<>(Arrays.asList(1.0, 5.3, 5.2, -3.1, 0.3));
    idx = sortIndex(a2);

    List<numBits> a3 = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        a3.add(new numBits(i));
    }

    // If you need to sort the indexes of your own object, you must implement
    // the Comparable Interface.
    idx = sortIndex(a3);
}

static class numBits implements Comparable<numBits> {
    private int a;

    public numBits(int i) {
        a = i;
    }

    public String toString() {
        return Integer.toString(a);
    }

    // Sort by the total number of bits in the number.
    @Override
    public int compareTo(numBits that) {
        if (Integer.bitCount(this.a) < Integer.bitCount(that.a))
            return -1;
        if (Integer.bitCount(this.a) > Integer.bitCount(that.a))
            return 1;
        return 0;
    }
}
Mark Wistrom
  • 161
  • 1
  • 6
2

One way you could do this is to Wrap the original index and country name into a separate Class. Then sort the Array based on the names. This way, your original indexes will be preserved.

Shamim Hafiz - MSFT
  • 21,454
  • 43
  • 116
  • 176
1

What Comes at first Glance is Map them like that

Map <Integer, String> map = new HashMap<Integer, String>();
map.put(0, "France");
map.put(1, "Spain");
map.put(2, "France");

and then sort them by value like that and then you can know their indexes and values (key, values) just print the map

Iterator mapIterator = map.keySet().iterator();  

while (mapIterator .hasNext()) {  
     String key = mapIterator.next().toString();  
     String value = map.get(key).toString();  

     System.out.println(key + " " + value);  
}
Community
  • 1
  • 1
  • 1
    Why don't you use foreach-loop? Why do you make it slower using keySet()and a lookup instead of entrySet()? Why do you output the values instead of making a method, which can be used further? – maaartinus Feb 01 '11 at 05:54
0

i found solution.

List<String> a = {b, a, d, c};
List<Integer> b = {2, 1, 4, 3};

and if a sort

private void setsortb() {
List<String> beforeA = new ArrayList<>();
List<Integer> beforeB = new ArrayList<>();
beforeA.addAll(a);
beforeB.addAll(b);
a.sort();//change like this {a, b, c, d}

for(int i = 0; i < beforeA.size(); i++) {
int index = beforeA.indexOf(a.get(i));
b.set(i, beforeB.get(i));
}
}

like this

result

a = {a, b, c, d}
b = {1, 2, 3, 4}
StarCue
  • 63
  • 9