3

I want to sort a List of objects by a specified attribute of those objects and I want to choose which attribute should be used for sorting. Example:

class Car{
  private String name;
  private String colour;
  public enum sortBy {NAME, COLOUR};

  public String name(){
    return name;
  }

  public String colour(){
    return colour;
  }

  public static Car[] getSortedArray(Car[] carArray, sortBy sortType){
    HashMap<Object, Car> carMap = new HashMap<Object, Car>();
    Object[] sortArray = new Object[carArray.length];
    Object value = null;
    for(int i = 0; i < carArray.length; i++){
      if(sortType == sortBy.NAME){
        value = carArray[i].name();
      }else if(sortType == sortBy.COLOUR){
        value = carArray[i].colour();
      }
      carMap.put(value, carArray[i]);
      sortArray[i] = value;
    }  
    Arrays.sort(sortArray);
    Car[] sortedArray = new Car[sortArray.length];
    for(int i = 0; i < sortArray.length; i++){
      sortedArray[i] = carMap.get(sortArray[i]);
    }
    return sortedArray;
  }
}

//external:
Car[] cars = getSomeCars();
Car[] nameSortedCars = Car.getSortedArray(cars, Car.sortBy.NAME);
Car[] colourSortedCars = Car.getSortedArray(cars, Car.sortBy.COLOUR);

The idea is simple:
I put all values that i want to sort by into an array, and i create a map that maps these values back to their objects. After I sorted this array I take the objects mapped to these values and put them in the same order into a new array which is then sorted by these values. The values are just created with type Object so I can sort by multiple types (not just Strings as in the example).

This works fine unless you have two objects with the same attribute value, then only one object will be in the returned array, but two times.
Is there a better way to achieve this sorting?

1
  • 1
    the problem is, HashMap doesn't accept duplicated keys, then if you have 2 objects with the same key, the second will overdrive the first. Commented Apr 16, 2015 at 14:48

4 Answers 4

6

It would be much simpler to use custom comparators:

To sort by name:

Arrays.sort(carArray, Comparator.comparing(Car::name));

To sort by colour:

Arrays.sort(carArray, Comparator.comparing(Car::colour));

So you could modify getSortedArray():

public static Car[] getSortedArray(Car[] carArray, Comparator<Car> comparator) {
    Car[] sorted = carArray.clone()
    Arrays.sort(sorted, comparator);
    return sorted;
}

And call it like this:

Car[] sorted = getSortedArray(carArray, Comparator.comparing(Car::name));

Edit:

If you use a language version that does not support these features, you can create the comparators by explicitly creating a nested class that implements the Comparator interface.

This, for example, is a singleton Comparator that compares Car instances by name:

static enum ByName implements Comparator<Car> {
    INSTANCE;

    @Override
    public int compare(Car c1, Car c2) {
        return c1.name().compareTo(c2.name());
    }
}

Then call:

Car[] sorted = getSortedArray(carArray, ByName.INSTANCE);
Sign up to request clarification or add additional context in comments.

3 Comments

That looks like it's going to solve my problem, but it seems not to work for Android. I applied Java 8 as JDK for AndroidStudio but it seems not to know Comparator.comparing and Car::name gives me an error that method references are not supported at this language level.
@jklmnn I updated the answer. This should work on older Java versions.
Ok, it seems to be an Android problem, I created a small test with the car class and that worked. Thanks!
2

TL;DR: There's already a wheel for that.

I would say the easiest way to do this is to create a comparator:

final Comparator<Car> byName = Comparator.comparing(Car::name);
final Comparator<Car> byColour = Comparator.comparing(Car::colour);

Then just use the appropriate method on Arrays to sort by a comparator:

Arrays.sort(carArray, byName);

Now you want to do it with an enum? Just have the enum implements Comparator<Car>:

enum SortBy implements Comparator<Car> {
    NAME(Comparator.comparing(Car::name)),
    COLOUR(Comparator.comparing(Car::colour));

    private final Comparator<Car> delegate;

    private SortBy(Comparator<Car> delegate) {
        this.delegate = delegate;
    }

    @Override
    public int compare(final Car o1, final Car o2) {
        return delegate.compare(o1, o2);
    }               
}

Want to sort by name then by colour? Easy:

final Comparator<Car> byName = SortBy.NAME.thenComparing(SortBy.COLOUR);

Want to sort by name in reverse order? Easy:

final Comparator<Car> byName = SortBy.NAME.reversed();

Comments

0

You're reinventing the wheel! Life will be much easier for you if you use the templated Collections API. To do this, you would work with List instead of arrays, define a Comparator to do your sorting, and then let the API do the work for you.

 Comparator<Car> carComparator = new Comparator<Car>(){
    public int sort(Car car1, Car car2){
      //Sorting logic goes here.
    }
 }
 List<Car> cars = getCars();
 cars = Collections.sort(cars, carComparator); //the cars collection is now sorted.

If you wanted to sometimes sort by one attribute or another, you could make my variable carComparator into its own class and define which attributes to sort by in the constructor.

Hope that helps :)

Edit: As others have pointed out, this approach also works with arrays. But unless you have a good reason to be working with Arrays, working with Collections will generally be easier.

Comments

0

I think the solution would be more efficient if you passed a Comparator implementation to the Arrays.sort. Right now, you are looping n*2 from the looks of it, the hash map (O(1)) plus the Arrays.sort (which is another 0(n log n) or such). If you do the below, you could skip the 2 loops, and the map, you are using currently.

You can simply create a Comparator like (rough code):

class CarComparator implements Comparator<Car> {
    enum compareType; //plus setter

    public int compareTo(Car a, Car b) {
        if(compareType == COLOUR) return a.colour.compareTo(b.colour);
        if(compareType == NAME.....
    }
}

, and then simply send the array of Cars to

Arrays.sort(cars, new CarComparator(COLOUR))

, or use more specialised comparator classes, one for each attribute, and a factory to render them, and of course don't create a new Comparator() for each sort if this is happening often. :-)

Overall, this approach should make your code more efficient. }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.