This question already has answers here:
Sorting an ArrayList of objects using a custom sorting order
(12 answers)
Closed 6 years ago.
If I have a simple list of Strings:
List<String> stringList = new ArrayList<String>();
I can sort it with:
Collections.sort(stringList);
But suppose I have a Person class:
public class Person
{
private String name;
private Integer age;
private String country;
}
And a list of it:
List<Person> personList = new ArrayList<Person>();
And I want to sort it sometimes by name, sometimes by age, sometimes by country.
What is the easiest way to accomplish that?
I know that I can implement the Comparable interface, but that seems to limit me to sort it by one specific property.
Collections.sort can be called with a custom comparator. And that comparator can be implemented to allow sorting in different sort orders. Here's an example (for your Person model - with age as an Integer):
public class FlexiblePersonComparator implements Comparator<Person> {
public enum Order {Name, Age, Country}
private Order sortingBy = Name;
#Override
public int compare(Person person1, Person person2) {
switch(sortingBy) {
case Name: return person1.name.compareTo(person2.name);
case Age: return person1.age.compareTo(person2.age);
case Country: return person1.country.compareTo(person2.country);
}
throw new RuntimeException("Practically unreachable code, can't be thrown");
}
public void setSortingBy(Order sortBy) {
this.sortingBy = sortingBy;
}
}
And you use it like that (assuming persons is a field):
public void sortPersonsBy(FlexiblePersonComparator.Order sortingBy) {
List<Person> persons = this.persons; // useless line, just for clarification
FlexiblePersonComparator comparator = new FlexiblePersonComparator();
comparator.setSortingBy(sortingBy);
Collections.sort(persons, comparator); // now we have a sorted list
}
Implement the Comparator interface (once for each different sort order) and use the Collections.sort() method that takes a Comparator as additional parameter.
Thanks to the responders. For the benefit of others, I'd like to include a complete example.
The solution is the create the following additional classes:
public class NameComparator implements Comparator<Person>
{
public int compare(Person o1, Person o2)
{
return o1.getName().compareTo(o2.getName());
}
}
public class AgeComparator implements Comparator<Person>
{
public int compare(Person o1, Person o2)
{
return o1.getAge().compareTo(o2.getAge());
}
}
public class CountryComparator implements Comparator<Person>
{
public int compare(Person o1, Person o2)
{
return o1.getCountry().compareTo(o2.getCountry());
}
}
The list can then be sorted like this:
Collections.sort(personList, new NameComparator());
Collections.sort(personList, new AgeComparator());
Collections.sort(personList, new CountryComparator());
The Java 8 way of doing this is to use List.sort as follows:
personList.sort(Comparator.comparing(Person::getName));
To quote Stuart Marks in his answer over here.
This is the big advantage of the List.sort(cmp) extension method over Collections.sort(list, cmp). It might seem that this is merely a small syntactic advantage being able to write myList.sort(cmp) instead of Collections.sort(myList, cmp). The difference is that myList.sort(cmp), being an interface extension method, can be overridden by the specific List implementation. For example, ArrayList.sort(cmp) sorts the list in-place using Arrays.sort() whereas the default implementation implements the old copyout-sort-copyback technique.
You could also use the BeanComparator from apache commons beanutils, like this:
Collections.sort(personList, new BeanComparator("name"));
Implement 3 different types of Comparator.
you can add the comparator to the sort command. The comparator you define, will sort the elements by name, age, or what ever.
Collections.sort(list, new Comparator() {
public int compare(Object arg0, Object arg1) {
if (!(arg0 instanceof Person)) {
return -1;
}
if (!(arg1 instanceof Person)) {
return -1;
}
Person pers0 = (Person)arg0;
Person pers1 = (Person)arg1;
// COMPARE NOW WHAT YOU WANT
// Thanks to Steve Kuo for your comment!
return pers0.getAge() - pers1.getAge();
}
});
The Collections.sort method can be invoked with a second argument which is the comparator to use.
Create 3 comparators and use the one you want when appropriate.
Collections.sort(list , new Comparator() {
public int compare(Object o1, Object o2) {
...
}
});
Using lambdaj ( http://code.google.com/p/lambdaj/ ) you can achieve what you're asking in the following way:
sort(personList, on(Person.class).getName());
sort(personList, on(Person.class).getAge());
sort(personList, on(Person.class).getCountry());
I asked a very similar question (about searching rather than sorting), perhaps there is some useful information (I ended up using an enum that implements Comparator so I pass the enum value as a comparator selector).
Related
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?
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);
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();
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.
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.
}
I have an arraylist defined whose elements are say, [man, animal, bird, reptile]. The elements in the arraylist are non-mandatory. The list can even be empty.
I always need to give the output as [animal,man,reptile,bird]. Means, the order of the elements are to be maintained. Is there any way of doing in arraylist?
I thought I can do like
for (String listElement: customList) { //custom list variable holds all elements
if (listElement.equalsIgnoreCase("animal"){
newList.add(0, listElement);
} else if("man") {
newlist.add(1, listElement);
}
But I would want to know the best practice of doing. Can someone please help me on this?
You can define a custom sorting and use it to order your array (save the comparator somewhere, so you don't have to instantiate it many times):
List<String> definedOrder = // define your custom order
Arrays.asList("animal", "man", "reptile", "bird");
Comparator<String> comparator = new Comparator<String>(){
#Override
public int compare(final String o1, final String o2){
// let your comparator look up your car's color in the custom order
return Integer.valueOf(definedOrder.indexOf(o1))
.compareTo(Integer.valueOf(definedOrder.indexOf(o2)));
}
};
Collections.sort(myList, comparator);
Use a custom comparator:
Collections.sort(customList, comparator);
int i = 0;
for (String temp : customList) {
System.out.println("customList " + ++i + " : " + temp);
}
Custom comparator below:
public static Comparator<String> comparator = new Comparator<String>() {
public int compare(String str1, String str2) {
return orderOf(str1) - orderOf(str2);
}
private int orderOf(String name) {
return ((List)Arrays.asList("animal", "man", "reptile", "bird")).indexOf(name);
}
};
You can use Collections.sort(yourArrayList, new CustomComparator());
You need to create your own comparator for this, though, but it is easy.
public class CustomComparator implements Comparator<YourType>{
#Override
public int compare(YoyrType o1, YoyrType o2) {
// logic for ordering the list
}
}
arraylist is ordered.
maybe you want to insert element into the list.
could you create a new list every time when you have to insert and copy each of them?
I have an arraylist containing names, ages, salaries, etc.
I want to sort the list by name.
The following is what I have in my class:
//Sort by Name
public ArrayList<Employee> sortByName()
{
Collections.sort(employees);
return employees;
}
I have a comparator (which I'm not sure is even being used) here:
import java.util.*;
public class EmployeeNameComparator implements Comparator<Employee>
{
ArrayList<Employee> employees = new ArrayList<Employee>();
//METHODS
//Name Compare
public int compare(Employee object1, Employee object2)
{
return object1.getName().compareTo(object2.getName());
}
}
And here are the relevant parts of the tester I've made:
CompanyDataBase database = new CompanyDataBase();
database.addEmployee(new Employee("John James", 34, 45000));
database.addEmployee(new Employee("Josie Gibson", 19, 19000));
database.addEmployee(new Employee("Luke Marsden", 28, 30000));
database.addEmployee(new Manager("Aaron Morgan", 28, 44000, 5500));
System.out.println("\n\nSORT BY NAME");
//Collections.sort(database.getEmployees());
database.sortByName();
for(Employee currEmployee: database.getEmployees())
{
System.out.println(currEmployee.getDescription());
}
I've tried this loads of ways but nothing seems to be working, does anyone know where I'm going wrong or how to make it work? Thanks.
Sort objects by name using a comparator the second parameter required.
Collections.sort(employees, new EmployeeNameComparator());
See the example of using a custom comparator.
use the method Collections.sort(List<T> list, Comparator<? super T> c) to sort a collection with a given comparator
Collections.sort(employees, new EmployeeNameComparator());
I have 2 array lists. One is an array list containing birthdays. The other is an array list of names.
I am sorting the array list of dates in descending order by date using
Collections.sort(birthdayList);
I want to have the array list of names be sorted in the same order that the birthday list was.
i.e.
unsorted
bdaylist namelist
1/20/1980 - Bob
3/15/1970 - Todd
8/25/1990 - Jeff
becomes
sorted
3/15/1970 - Todd
1/20/1980 - Bob
8/25/1990 - Jeff
What would be the most efficient way to do this in Java?
Thank you very much!
Create a Person class with 2 fields: name and birthday.
Put the persons in a list
sort with a custom comparator that compares the birthdays
Create a class like this:
public class Person implements Comparable<Person> {
private String name;
private Date date;
public Person(String name, Date date) {
this.name = name;
this.date = date;
}
public String getName() {
return name;
}
public Date getDate() {
return date;
}
#Override
public int compareTo(Person o) {
return this.date.compareTo(o.getDate());
}
}
Then you can sort the list of Person objects like this:
public static void main(String... args) {
LinkedList<Person> persons = new LinkedList<Person>();
persons.add(new Person("Name1", new Date())); //Specify different dates
persons.add(new Person("Name2", new Date()));
persons.add(new Person("Name3", new Date()));
Collections.sort(persons);
//Collections.sort(persons, Collections.reverseOrder()); //Reverse order
}
That's it.
Or another alternative is to use Comparator:
Collections.sort(persons, new Comparator<Person>() {
#Override
public int compare(Person o1, Person o2) {
return o1.getDate().compareTo(o2.getDate());
}
});
Reverse order:
Collections.sort(persons, Collections.reverseOrder(new Comparator<Person>() {
#Override
public int compare(Person o1, Person o2) {
return o1.getDate().compareTo(o2.getDate());
}
}));
Then you don't need to implement Comparable<Person> in your person class.
Don't sort two array lists; sort one array list filled with combined objects containing both the date and the name. That's really the only way.
The other answers about implementing Comparable and using it as the comparator are all indeed correct. But, if this also helps...
According to the Javadoc for Collections.sort, it is noted that sort() uses Modified Mergesort as the sorting algorithm. Correct me if I am wrong, but it is widely accepted that Merge Sort achieves the best possible running time for the worst-case scenario out of all sorting algorithms out there: O(n log n) (I'm not eliminating the fact that there may be other sort algorithms that also achieve O(n log n) in the worst case).
But, O(n log n) is only the best possible run time for an unbounded domain of values. If you have a bound on your domain, then you can get an even better runtime of O(n) using Bucket Sort.
We have a linkedlist, the elements of this linkedlist are Employee, I want to sort this linkedlist based on the salary of Employee, salary is one member of Employee Class, can we use Collections.sort()? if not, how can I sort it?
Can anyone explain me?
Yes, you can use Collections.sort()
You need to have your Employee class implement the Comparable interface.
http://download.oracle.com/javase/6/docs/api/java/lang/Comparable.html
In your compareTo() method you would compare the salary of the current object to that of the object passed in.
Edit:
The other option you have if you don't want that to be the default comparison is to create a Comparator object and use the second form -> Collections.sort(List, Comparator);
It would look like this:
class SalaryComparator implements Comparator<Employee>
{
public int compare(Employee e1, Employee e2)
{
if (e1.getSalary() > e2.getSalary())
return 1;
else if (e1.getSalary() < e2.getSalary())
return -1;
else
return 0;
}
}
Now you can do: Collections.sort(myEmployeeList, new SalaryComparator());
While a LinkedList<Employee> will work, I'd use an ArrayList<Employee> for this:
List<Employee> employees = new ArrayList<Employee>();
After you populate it (either way) you can sort it by salary like so:
Collections.sort(employees, new Comparator<Employee>() {
public int compare(Employee e1, Employee e2) {
return e1.getSalary() - e2.getSalary();
}
});
You can use Collections.sort()
But in order to do that, your Employee class needs to implement the Comparable interface first.
A rough example would be:
public class Employee implements Comparable<Employee>
{
public int compareTo(Employee e)
{
return this.salary - e.salary;
}
}
You can sort a linked list, but it's not an efficient operation, especially if the list is not trivial in size. Choose appropriate data structures.