Reputation: 501
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?
Upvotes: 3
Views: 5345
Reputation: 9049
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);
Upvotes: 6
Reputation: 1
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. }
Upvotes: 0
Reputation: 61128
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();
Upvotes: 2
Reputation: 534
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.
Upvotes: 0