Related
I have array of objects person (int age; String name;).
How can I sort this array alphabetically by name and then by age?
Which algorithm would you use for this ?
You can use Collections.sort as follows:
private static void order(List<Person> persons) {
Collections.sort(persons, new Comparator() {
public int compare(Object o1, Object o2) {
String x1 = ((Person) o1).getName();
String x2 = ((Person) o2).getName();
int sComp = x1.compareTo(x2);
if (sComp != 0) {
return sComp;
}
Integer x1 = ((Person) o1).getAge();
Integer x2 = ((Person) o2).getAge();
return x1.compareTo(x2);
}});
}
List<Persons> is now sorted by name, then by age.
String.compareTo "Compares two strings lexicographically" - from the docs.
Collections.sort is a static method in the native Collections library. It does the actual sorting, you just need to provide a Comparator which defines how two elements in your list should be compared: this is achieved by providing your own implementation of the compare method.
For those able to use the Java 8 streaming API, there is a neater approach that is well documented here:
Lambdas and sorting
I was looking for the equivalent of the C# LINQ:
.ThenBy(...)
I found the mechanism in Java 8 on the Comparator:
.thenComparing(...)
So here is the snippet that demonstrates the algorithm.
Comparator<Person> comparator = Comparator.comparing(person -> person.name);
comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));
Check out the link above for a neater way and an explanation about how Java's type inference makes it a bit more clunky to define compared to LINQ.
Here is the full unit test for reference:
#Test
public void testChainedSorting()
{
// Create the collection of people:
ArrayList<Person> people = new ArrayList<>();
people.add(new Person("Dan", 4));
people.add(new Person("Andi", 2));
people.add(new Person("Bob", 42));
people.add(new Person("Debby", 3));
people.add(new Person("Bob", 72));
people.add(new Person("Barry", 20));
people.add(new Person("Cathy", 40));
people.add(new Person("Bob", 40));
people.add(new Person("Barry", 50));
// Define chained comparators:
// Great article explaining this and how to make it even neater:
// http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
Comparator<Person> comparator = Comparator.comparing(person -> person.name);
comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));
// Sort the stream:
Stream<Person> personStream = people.stream().sorted(comparator);
// Make sure that the output is as expected:
List<Person> sortedPeople = personStream.collect(Collectors.toList());
Assert.assertEquals("Andi", sortedPeople.get(0).name); Assert.assertEquals(2, sortedPeople.get(0).age);
Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
Assert.assertEquals("Bob", sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
Assert.assertEquals("Bob", sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
Assert.assertEquals("Bob", sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
Assert.assertEquals("Dan", sortedPeople.get(7).name); Assert.assertEquals(4, sortedPeople.get(7).age);
Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3, sortedPeople.get(8).age);
// Andi : 2
// Barry : 20
// Barry : 50
// Bob : 40
// Bob : 42
// Bob : 72
// Cathy : 40
// Dan : 4
// Debby : 3
}
/**
* A person in our system.
*/
public static class Person
{
/**
* Creates a new person.
* #param name The name of the person.
* #param age The age of the person.
*/
public Person(String name, int age)
{
this.age = age;
this.name = name;
}
/**
* The name of the person.
*/
public String name;
/**
* The age of the person.
*/
public int age;
#Override
public String toString()
{
if (name == null) return super.toString();
else return String.format("%s : %d", this.name, this.age);
}
}
Using the Java 8 Streams approach, with method references on the getters...
// Create a stream...
var sortedList = persons.stream()
// sort it (does not sort the original list)...
.sorted(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge));
// and collect to a new list
.collect(Collectors.toList());
Collection to an array ist also possible:
persons.stream()
.sorted(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge));
.toArray(String[]::new);
And the Java 8 Lambda approach...
//Sorts the original list Lambda style
persons.sort((p1, p2) -> {
if (p1.getName().compareTo(p2.getName()) == 0) {
return p1.getAge().compareTo(p2.getAge());
} else {
return p1.getName().compareTo(p2.getName());
}
});
Lastly...
// This syntax is similar to the Streams example above, but sorts the original list!!!
persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
You need to implement your own Comparator, and then use it: for example
Arrays.sort(persons, new PersonComparator());
Your Comparator could look a bit like this:
public class PersonComparator implements Comparator<? extends Person> {
public int compare(Person p1, Person p2) {
int nameCompare = p1.name.compareToIgnoreCase(p2.name);
if (nameCompare != 0) {
return nameCompare;
} else {
return Integer.valueOf(p1.age).compareTo(Integer.valueOf(p2.age));
}
}
}
The comparator first compares the names, if they are not equals it returns the result from comparing them, else it returns the compare result when comparing the ages of both persons.
This code is only a draft: because the class is immutable you could think of building an singleton of it, instead creating a new instance for each sorting.
Have your person class implement Comparable<Person> and then implement the compareTo method, for instance:
public int compareTo(Person o) {
int result = name.compareToIgnoreCase(o.name);
if(result==0) {
return Integer.valueOf(age).compareTo(o.age);
}
else {
return result;
}
}
That will sort first by name (case insensitively) and then by age. You can then run Arrays.sort() or Collections.sort() on the collection or array of Person objects.
Guava's ComparisonChain provides a clean way of doing it. Refer to this link.
A utility for performing a chained comparison statement. For example:
public int compareTo(Foo that) {
return ComparisonChain.start()
.compare(this.aString, that.aString)
.compare(this.anInt, that.anInt)
.compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
.result();
}
You can do like this:
List<User> users = Lists.newArrayList(
new User("Pedro", 12),
new User("Maria", 10),
new User("Rafael",12)
);
users.sort(
Comparator.comparing(User::getName).thenComparing(User::getAge)
);
I would be careful when using Guava's ComparisonChain because it creates an instance of it per element been compared so you would be looking at a creation of N x Log N comparison chains just to compare if you are sorting, or N instances if you are iterating and checking for equality.
I would instead create a static Comparator using the newest Java 8 API if possible or Guava's Ordering API which allows you to do that, here is an example with Java 8:
import java.util.Comparator;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsLast;
private static final Comparator<Person> COMPARATOR = Comparator
.comparing(Person::getName, nullsLast(naturalOrder()))
.thenComparingInt(Person::getAge);
#Override
public int compareTo(#NotNull Person other) {
return COMPARATOR.compare(this, other);
}
Here is how to use the Guava's Ordering API: https://github.com/google/guava/wiki/OrderingExplained
Create as many comparators as necessary. After, call the method "thenComparing" for each order category. It's a way of doing by Streams. See:
//Sort by first and last name
System.out.println("\n2.Sort list of person objects by firstName then "
+ "by lastName then by age");
Comparator<Person> sortByFirstName
= (p, o) -> p.firstName.compareToIgnoreCase(o.firstName);
Comparator<Person> sortByLastName
= (p, o) -> p.lastName.compareToIgnoreCase(o.lastName);
Comparator<Person> sortByAge
= (p, o) -> Integer.compare(p.age,o.age);
//Sort by first Name then Sort by last name then sort by age
personList.stream().sorted(
sortByFirstName
.thenComparing(sortByLastName)
.thenComparing(sortByAge)
).forEach(person->
System.out.println(person));
Look: Sort user defined object on multiple fields – Comparator (lambda stream)
Use Comparator and then put objects into Collection, then Collections.sort();
class Person {
String fname;
String lname;
int age;
public Person() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public String getLname() {
return lname;
}
public void setLname(String lname) {
this.lname = lname;
}
public Person(String fname, String lname, int age) {
this.fname = fname;
this.lname = lname;
this.age = age;
}
#Override
public String toString() {
return fname + "," + lname + "," + age;
}
}
public class Main{
public static void main(String[] args) {
List<Person> persons = new java.util.ArrayList<Person>();
persons.add(new Person("abc3", "def3", 10));
persons.add(new Person("abc2", "def2", 32));
persons.add(new Person("abc1", "def1", 65));
persons.add(new Person("abc4", "def4", 10));
System.out.println(persons);
Collections.sort(persons, new Comparator<Person>() {
#Override
public int compare(Person t, Person t1) {
return t.getAge() - t1.getAge();
}
});
System.out.println(persons);
}
}
Or you can exploit the fact that Collections.sort() (or Arrays.sort()) is stable (it doesn't reorder elements that are equal) and use a Comparator to sort by age first and then another one to sort by name.
In this specific case this isn't a very good idea but if you have to be able to change the sort order in runtime, it might be useful.
You can use generic serial Comparator to sort collections by multiple fields.
import org.apache.commons.lang3.reflect.FieldUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* #author MaheshRPM
*/
public class SerialComparator<T> implements Comparator<T> {
List<String> sortingFields;
public SerialComparator(List<String> sortingFields) {
this.sortingFields = sortingFields;
}
public SerialComparator(String... sortingFields) {
this.sortingFields = Arrays.asList(sortingFields);
}
#Override
public int compare(T o1, T o2) {
int result = 0;
try {
for (String sortingField : sortingFields) {
if (result == 0) {
Object value1 = FieldUtils.readField(o1, sortingField, true);
Object value2 = FieldUtils.readField(o2, sortingField, true);
if (value1 instanceof Comparable && value2 instanceof Comparable) {
Comparable comparable1 = (Comparable) value1;
Comparable comparable2 = (Comparable) value2;
result = comparable1.compareTo(comparable2);
} else {
throw new RuntimeException("Cannot compare non Comparable fields. " + value1.getClass()
.getName() + " must implement Comparable<" + value1.getClass().getName() + ">");
}
} else {
break;
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
}
Arrays.sort(persons, new PersonComparator());
import java.util.Comparator;
public class PersonComparator implements Comparator<? extends Person> {
#Override
public int compare(Person o1, Person o2) {
if(null == o1 || null == o2 || null == o1.getName() || null== o2.getName() ){
throw new NullPointerException();
}else{
int nameComparisonResult = o1.getName().compareTo(o2.getName());
if(0 == nameComparisonResult){
return o1.getAge()-o2.getAge();
}else{
return nameComparisonResult;
}
}
}
}
class Person{
int age; String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Updated version:
public class PersonComparator implements Comparator<? extends Person> {
#Override
public int compare(Person o1, Person o2) {
int nameComparisonResult = o1.getName().compareToIgnoreCase(o2.getName());
return 0 == nameComparisonResult?o1.getAge()-o2.getAge():nameComparisonResult;
}
}
For a class Book like this:
package books;
public class Book {
private Integer id;
private Integer number;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "book{" +
"id=" + id +
", number=" + number +
", name='" + name + '\'' + '\n' +
'}';
}
}
sorting main class with mock objects
package books;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
Book b = new Book();
Book c = new Book();
Book d = new Book();
Book e = new Book();
Book f = new Book();
Book g = new Book();
Book g1 = new Book();
Book g2 = new Book();
Book g3 = new Book();
Book g4 = new Book();
b.setId(1);
b.setNumber(12);
b.setName("gk");
c.setId(2);
c.setNumber(12);
c.setName("gk");
d.setId(2);
d.setNumber(13);
d.setName("maths");
e.setId(3);
e.setNumber(3);
e.setName("geometry");
f.setId(3);
f.setNumber(34);
b.setName("gk");
g.setId(3);
g.setNumber(11);
g.setName("gk");
g1.setId(3);
g1.setNumber(88);
g1.setName("gk");
g2.setId(3);
g2.setNumber(91);
g2.setName("gk");
g3.setId(3);
g3.setNumber(101);
g3.setName("gk");
g4.setId(3);
g4.setNumber(4);
g4.setName("gk");
List<Book> allBooks = new ArrayList<Book>();
allBooks.add(b);
allBooks.add(c);
allBooks.add(d);
allBooks.add(e);
allBooks.add(f);
allBooks.add(g);
allBooks.add(g1);
allBooks.add(g2);
allBooks.add(g3);
allBooks.add(g4);
System.out.println(allBooks.size());
Collections.sort(allBooks, new Comparator<Book>() {
#Override
public int compare(Book t, Book t1) {
int a = t.getId()- t1.getId();
if(a == 0){
int a1 = t.getNumber() - t1.getNumber();
return a1;
}
else
return a;
}
});
System.out.println(allBooks);
}
}
I'm not sure if it's ugly to write the compartor inside the Person class in this case. Did it like this:
public class Person implements Comparable <Person> {
private String lastName;
private String firstName;
private int age;
public Person(String firstName, String lastName, int BirthDay) {
this.firstName = firstName;
this.lastName = lastName;
this.age = BirthDay;
}
public int getAge() {
return age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
#Override
public int compareTo(Person o) {
// default compareTo
}
#Override
public String toString() {
return firstName + " " + lastName + " " + age + "";
}
public static class firstNameComperator implements Comparator<Person> {
#Override
public int compare(Person o1, Person o2) {
return o1.firstName.compareTo(o2.firstName);
}
}
public static class lastNameComperator implements Comparator<Person> {
#Override
public int compare(Person o1, Person o2) {
return o1.lastName.compareTo(o2.lastName);
}
}
public static class ageComperator implements Comparator<Person> {
#Override
public int compare(Person o1, Person o2) {
return o1.age - o2.age;
}
}
}
public class Test {
private static void print() {
ArrayList<Person> list = new ArrayList();
list.add(new Person("Diana", "Agron", 31));
list.add(new Person("Kay", "Panabaker", 27));
list.add(new Person("Lucy", "Hale", 28));
list.add(new Person("Ashley", "Benson", 28));
list.add(new Person("Megan", "Park", 31));
list.add(new Person("Lucas", "Till", 27));
list.add(new Person("Nicholas", "Hoult", 28));
list.add(new Person("Aly", "Michalka", 28));
list.add(new Person("Adam", "Brody", 38));
list.add(new Person("Chris", "Pine", 37));
Collections.sort(list, new Person.lastNameComperator());
Iterator<Person> it = list.iterator();
while(it.hasNext())
System.out.println(it.next().toString());
}
}
I need to use a PriorityQueue to sort Person objects.
The Person class:
public class Person implements Comparable<Person>{
private String firstName;
private String lastName;
public Person(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {return firstName;}
public String getLastName() {return lastName;}
public String toString(){
return "Name: " + firstName + " " + lastName + "\n";
}
public int compareTo(Person p){
String pname1 = firstName + lastName;
String pname2 = p.firstName + p.lastName;
return pname1.compareTo(pname2);
}
The main code:
import java.util.PriorityQueue;
public class priorityQ{
public static void main(String[] args){
PriorityQueue<Person> pers = new PriorityQueue<Person>();
pers.add(new Person("Sam", "Smith"));
pers.add(new Person("Charlie", "Black"));
pers.add(new Person("Betty", "Brown"));
pers.add(new Person("Jessica", "Stewart"));
pers.add(new Person("John", "Friday"));
pers.add(new Person("Frank", "Folcy"));
System.out.print(pers);
}
}
I need to:
Print without sort (already done in main)
Sort by last name
Sort by first name, then last name
I must not change any code in the Person class.
I created new class:
import java.util.Comparator;
public class LastNameComparator implements Comparator<Person>{
public int compareTo(Person a, Person b){
Person p1 = (Person)a;
Person p2 = (Person)b;
if(p1.getLastName().equals(p2.getLastName()))
{
return p1.getFirstName().compareTo(p2.getFirstName());
}
return p1.getLastName().compareTo(p2.getLastName());
}
}
It has a compile error:
LastNameComparator.java:2: error: LastNameComparator is not abstract and does not override abstract method compare(Person,Person) in Comparator
first you have to remove comparable interface from the person class and its all implementations:
try to do like this:
import java.util.Comparator;
public class LastNameComparator implements Comparator<Person>{
#Override
public int compare(Person o1, Person o2) {
return o1.lastname.compareTo(o2.lastname);
}
}
And in main method do the following for sort by Last Name:
Collections.sort(pers,new LastNameComparator());
Iterator<Person> it = pers.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
First of all:
Please remove
implements Comparable<Person>
and
public int compareTo(Person p){
String pname1 = firstName + lastName;
String pname2 = p.firstName + p.lastName;
return pname1.compareTo(pname2);
}
from Person class, since you are not aloud to do any changes in it.
Second:
Comparator Class have only compare(T ob1, T2 ob2) method, there are no public int compareTo(Person a, Person b).
So please create compare(T ob1, T2 ob2) and put #Override annotation on top of it to be sure you did everything right.
So after all in you Comparator class have to be
#Override
public int compare(Person a, Person b){
//..your code
}
Rest of code seems fine, from quick look...
class Person
{
private String name;
private String profession;
}
profession has values:
engineer
Doctor
Teacher
student
I have list of person and want to sort it on the basis of Profession.
engineer comes first then Doctor and then Teacher and then student.
Is it possible to sort it with comparable interface.
You can sort your custom object using Collection.sort method like this,
Collections.sort(list, new Comparator(){
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
return p1.getProfession().compareToIgnoreCase(p2.getProfession());
}
});
To Sort in reverse order just make your return statement line like this,
p2.getProfession().compareToIgnoreCase(p1.getProfession());
This will directly make your list sorted.
Add enum to your variables:
class Person {
private String name;
private String profession;
private enum enumProffesion
{
Doctor, Teacher, student;
}
}
After that could add function to the Person class which would give you value of the profession:
public int professionValue()
{
enumProffesion enumValue= enumProffesion.valueOf(proffesion);
switch (enumValue) {
case Doctor: return 1; break;
case Teacher: return 2; break;
case student: return 3; break;
default: return null;
}
}
After that you just implement logic that will sort all Persons. For that you can help your self with this answer: sorting integers in order lowest to highest java
Implement Comparable
Yes you can implement Comparable. Create a method compareTo(Person obj) and then write your custom logic. You can compare alphabetically or whatever other algorithm you want - for example engineer before doctor because he makes more money :) For alphabetic comparing you can do it like that:
class Person implements Comparable<Person> {
#Override
public int compareTo(Person o) {
return this.profession.compareTo(o.getProfession());
}
private String name;
private String profession;
}
After that you just use the Collections.sort
Enum
You can do it easily if replace profession field by enum:
class Person implements Comparable<Person> {
private String name;
private Profession profession;
// by this we'll define natural ordering based on ordering
// in which `Profession` enum values are declared
#Override
public int compareTo(Person p) {
return this.profession.compareTo(p.profession);
}
}
And here's Profession enum:
public enum Profession {
ENGINEER("engineer"), DOCTOR("Doctor"), TEACHER("Teacher"), STUDENT("student");
private String displayName;
Profession(String dn) {
this.displayName = dn;
}
#Override
public String toString() {
return this.displayName;
}
}
If you are new to the enum facility in Java, see Oracle tutorial.
Add getter and setter for profession in Person class and simply use below code
class Person {
private String name;
private String profession;
public String getProfession() {
return profession;
}
public void setProfession(String profession) {
this.profession = profession;
}
}
List<Person> personList = new ArrayList<Person>();
Person p1 = new Person();
p1.setProfession("Engineer");
personList.add(p1);
Person p2 = new Person();
p2.setProfession("Doctor");
personList.add(p2);
Person p3 = new Person();
p3.setProfession("Teacher");
personList.add(p3);
Person p4 = new Person();
p4.setProfession("student");
personList.add(p4);
Collections.sort(personList, new Comparator() {
#Override
public int compare(Object obj1, Object obj2) {
Person p1 = (Person)obj1;
Person p2 = (Person)obj2;
return p1.getProfession().compareToIgnoreCase(p2.getProfession());
}
});
Two ways:
Implement the Comparable interface
Create a Comparator
With the first way you can sort the collection only by the one method compareTo you will define
Your Person.java
class Person implements Comparable<Person> {
private String name;
private String profession;
#Override
public int compareTo(Person o) {
return this.profession.compareTo(o.getProfession());
}
public String getName() {
return name;
}
public String getProfession() {
return profession;
}
}
Then you need to call:
Collections.sort(yourCollection);
With the second way you can sort by one or more Comparator, giving you the ability to compare the same collection with different criteria.
Example of two Comparator
public class PersonSortByNameComparator implements Comparator<Person>{
#Override
public int compare(Person p1, Person p2) {
return p1.getName().compareTo(p2.getName());
}
}
public class PersonSortByAlphabeticalProfessionComparator implements Comparator<Person>{
#Override
public int compare(Person p1, Person p2) {
return p1.getProfession().compareTo(p2.getProfession());
}
}
Or this one you need:
public class PersonSortByProfessionComparator implements Comparator<Person> {
#Override
public int compare(Person p1, Person p2) {
if(p1.getProfession().equalsIgnoreCase(p2.getProfession())
return 0;
if(p1.getProfession().equalsIgnoreCase("student")
return -1;
if(p1.getProfession().equalsIgnoreCase("engineer")
return 1;
if(p1.getProfession().equalsIgnoreCase("doctor")
return 1;
else
return -1;
}
}
And then call one of them:
Collections.sort(yourCollection, new PersonSortByNameComparator());
This blog article is really good written and you can some examples
My suggestion -
1. Create a new class Profession -
class Profession{
public Profession(Integer id, String prefessionName){
this.id=id;
this.prefessionName=prefessionName;
}
Integer id;
String professionName;
}
2. Now give Id to each Profession object/instance maintaining the order. For example -
Profession engineer = new Profession(1, "Engineer"); //since Engineer is in first place
Profession doctor = new Profession(2, "Doctor"); //since Doctor is in second place
Profession teacher = new Profession(3, "Teacher");
Profession student = new Profession(4, "Student");
3. Now sort the Profession for id using compareable interface.
I have a list of java object that have 4 members.
int id;
String name;
String age;
int order;
I am using his code to sort object fields in ascending order according to if condition.
Collections.sort(list, new Comparator<com.MyObject>() {
public int compare(com.MyObject object1, com.MyObject object2) {
if(myString.equalsIgnoreCase("name")){
return object1.getName().compareTo(object2.getName());
}else if(myString.equalsIgnoreCase("age")){
return object1.getAge().compareTo(object2.getAge());
}else{
return object1.getOrder() - object2.getOrder();
}
}
});
I want to add another condition if(myString.equalsIgnoreCase("nameReverse")) If this will be the case the list should be sorted in descending order.
The Collections class has a reverseOrder method which returns a reverse Comparator for a given comparator. IMO, it would be more cleaner to not modify the Comparator if all you want to do is get a reverse Comparator.
A bit of sample code:
public class Person {
public static void testComparator() {
Person p1 = new Person("c");
Person p2 = new Person("a");
Person p3 = new Person("b");
List<Person> persons = Arrays.asList(p1, p2, p3);
Collections.sort(persons, PersonComparator.INSTANCE);
System.out.println("After normal sorting: " + persons);
Collections.sort(persons, Collections.reverseOrder(PersonComparator.INSTANCE));
System.out.println("After reverse sorting: " + persons);
}
public Person(String name) { this.name = name; }
public String name;
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Person [name=").append(name).append("]");
return builder.toString();
}
}
class PersonComparator implements Comparator<Person> {
public static final PersonComparator INSTANCE = new PersonComparator();
#Override
public int compare(Person o1, Person o2) {
return o1.name.compareTo(o2.name);
}
}
I have array of objects person (int age; String name;).
How can I sort this array alphabetically by name and then by age?
Which algorithm would you use for this ?
You can use Collections.sort as follows:
private static void order(List<Person> persons) {
Collections.sort(persons, new Comparator() {
public int compare(Object o1, Object o2) {
String x1 = ((Person) o1).getName();
String x2 = ((Person) o2).getName();
int sComp = x1.compareTo(x2);
if (sComp != 0) {
return sComp;
}
Integer x1 = ((Person) o1).getAge();
Integer x2 = ((Person) o2).getAge();
return x1.compareTo(x2);
}});
}
List<Persons> is now sorted by name, then by age.
String.compareTo "Compares two strings lexicographically" - from the docs.
Collections.sort is a static method in the native Collections library. It does the actual sorting, you just need to provide a Comparator which defines how two elements in your list should be compared: this is achieved by providing your own implementation of the compare method.
For those able to use the Java 8 streaming API, there is a neater approach that is well documented here:
Lambdas and sorting
I was looking for the equivalent of the C# LINQ:
.ThenBy(...)
I found the mechanism in Java 8 on the Comparator:
.thenComparing(...)
So here is the snippet that demonstrates the algorithm.
Comparator<Person> comparator = Comparator.comparing(person -> person.name);
comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));
Check out the link above for a neater way and an explanation about how Java's type inference makes it a bit more clunky to define compared to LINQ.
Here is the full unit test for reference:
#Test
public void testChainedSorting()
{
// Create the collection of people:
ArrayList<Person> people = new ArrayList<>();
people.add(new Person("Dan", 4));
people.add(new Person("Andi", 2));
people.add(new Person("Bob", 42));
people.add(new Person("Debby", 3));
people.add(new Person("Bob", 72));
people.add(new Person("Barry", 20));
people.add(new Person("Cathy", 40));
people.add(new Person("Bob", 40));
people.add(new Person("Barry", 50));
// Define chained comparators:
// Great article explaining this and how to make it even neater:
// http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
Comparator<Person> comparator = Comparator.comparing(person -> person.name);
comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));
// Sort the stream:
Stream<Person> personStream = people.stream().sorted(comparator);
// Make sure that the output is as expected:
List<Person> sortedPeople = personStream.collect(Collectors.toList());
Assert.assertEquals("Andi", sortedPeople.get(0).name); Assert.assertEquals(2, sortedPeople.get(0).age);
Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
Assert.assertEquals("Bob", sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
Assert.assertEquals("Bob", sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
Assert.assertEquals("Bob", sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
Assert.assertEquals("Dan", sortedPeople.get(7).name); Assert.assertEquals(4, sortedPeople.get(7).age);
Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3, sortedPeople.get(8).age);
// Andi : 2
// Barry : 20
// Barry : 50
// Bob : 40
// Bob : 42
// Bob : 72
// Cathy : 40
// Dan : 4
// Debby : 3
}
/**
* A person in our system.
*/
public static class Person
{
/**
* Creates a new person.
* #param name The name of the person.
* #param age The age of the person.
*/
public Person(String name, int age)
{
this.age = age;
this.name = name;
}
/**
* The name of the person.
*/
public String name;
/**
* The age of the person.
*/
public int age;
#Override
public String toString()
{
if (name == null) return super.toString();
else return String.format("%s : %d", this.name, this.age);
}
}
Using the Java 8 Streams approach, with method references on the getters...
// Create a stream...
var sortedList = persons.stream()
// sort it (does not sort the original list)...
.sorted(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge));
// and collect to a new list
.collect(Collectors.toList());
Collection to an array ist also possible:
persons.stream()
.sorted(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge));
.toArray(String[]::new);
And the Java 8 Lambda approach...
//Sorts the original list Lambda style
persons.sort((p1, p2) -> {
if (p1.getName().compareTo(p2.getName()) == 0) {
return p1.getAge().compareTo(p2.getAge());
} else {
return p1.getName().compareTo(p2.getName());
}
});
Lastly...
// This syntax is similar to the Streams example above, but sorts the original list!!!
persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
You need to implement your own Comparator, and then use it: for example
Arrays.sort(persons, new PersonComparator());
Your Comparator could look a bit like this:
public class PersonComparator implements Comparator<? extends Person> {
public int compare(Person p1, Person p2) {
int nameCompare = p1.name.compareToIgnoreCase(p2.name);
if (nameCompare != 0) {
return nameCompare;
} else {
return Integer.valueOf(p1.age).compareTo(Integer.valueOf(p2.age));
}
}
}
The comparator first compares the names, if they are not equals it returns the result from comparing them, else it returns the compare result when comparing the ages of both persons.
This code is only a draft: because the class is immutable you could think of building an singleton of it, instead creating a new instance for each sorting.
Have your person class implement Comparable<Person> and then implement the compareTo method, for instance:
public int compareTo(Person o) {
int result = name.compareToIgnoreCase(o.name);
if(result==0) {
return Integer.valueOf(age).compareTo(o.age);
}
else {
return result;
}
}
That will sort first by name (case insensitively) and then by age. You can then run Arrays.sort() or Collections.sort() on the collection or array of Person objects.
Guava's ComparisonChain provides a clean way of doing it. Refer to this link.
A utility for performing a chained comparison statement. For example:
public int compareTo(Foo that) {
return ComparisonChain.start()
.compare(this.aString, that.aString)
.compare(this.anInt, that.anInt)
.compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
.result();
}
You can do like this:
List<User> users = Lists.newArrayList(
new User("Pedro", 12),
new User("Maria", 10),
new User("Rafael",12)
);
users.sort(
Comparator.comparing(User::getName).thenComparing(User::getAge)
);
I would be careful when using Guava's ComparisonChain because it creates an instance of it per element been compared so you would be looking at a creation of N x Log N comparison chains just to compare if you are sorting, or N instances if you are iterating and checking for equality.
I would instead create a static Comparator using the newest Java 8 API if possible or Guava's Ordering API which allows you to do that, here is an example with Java 8:
import java.util.Comparator;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsLast;
private static final Comparator<Person> COMPARATOR = Comparator
.comparing(Person::getName, nullsLast(naturalOrder()))
.thenComparingInt(Person::getAge);
#Override
public int compareTo(#NotNull Person other) {
return COMPARATOR.compare(this, other);
}
Here is how to use the Guava's Ordering API: https://github.com/google/guava/wiki/OrderingExplained
Create as many comparators as necessary. After, call the method "thenComparing" for each order category. It's a way of doing by Streams. See:
//Sort by first and last name
System.out.println("\n2.Sort list of person objects by firstName then "
+ "by lastName then by age");
Comparator<Person> sortByFirstName
= (p, o) -> p.firstName.compareToIgnoreCase(o.firstName);
Comparator<Person> sortByLastName
= (p, o) -> p.lastName.compareToIgnoreCase(o.lastName);
Comparator<Person> sortByAge
= (p, o) -> Integer.compare(p.age,o.age);
//Sort by first Name then Sort by last name then sort by age
personList.stream().sorted(
sortByFirstName
.thenComparing(sortByLastName)
.thenComparing(sortByAge)
).forEach(person->
System.out.println(person));
Look: Sort user defined object on multiple fields – Comparator (lambda stream)
Use Comparator and then put objects into Collection, then Collections.sort();
class Person {
String fname;
String lname;
int age;
public Person() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public String getLname() {
return lname;
}
public void setLname(String lname) {
this.lname = lname;
}
public Person(String fname, String lname, int age) {
this.fname = fname;
this.lname = lname;
this.age = age;
}
#Override
public String toString() {
return fname + "," + lname + "," + age;
}
}
public class Main{
public static void main(String[] args) {
List<Person> persons = new java.util.ArrayList<Person>();
persons.add(new Person("abc3", "def3", 10));
persons.add(new Person("abc2", "def2", 32));
persons.add(new Person("abc1", "def1", 65));
persons.add(new Person("abc4", "def4", 10));
System.out.println(persons);
Collections.sort(persons, new Comparator<Person>() {
#Override
public int compare(Person t, Person t1) {
return t.getAge() - t1.getAge();
}
});
System.out.println(persons);
}
}
Or you can exploit the fact that Collections.sort() (or Arrays.sort()) is stable (it doesn't reorder elements that are equal) and use a Comparator to sort by age first and then another one to sort by name.
In this specific case this isn't a very good idea but if you have to be able to change the sort order in runtime, it might be useful.
You can use generic serial Comparator to sort collections by multiple fields.
import org.apache.commons.lang3.reflect.FieldUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* #author MaheshRPM
*/
public class SerialComparator<T> implements Comparator<T> {
List<String> sortingFields;
public SerialComparator(List<String> sortingFields) {
this.sortingFields = sortingFields;
}
public SerialComparator(String... sortingFields) {
this.sortingFields = Arrays.asList(sortingFields);
}
#Override
public int compare(T o1, T o2) {
int result = 0;
try {
for (String sortingField : sortingFields) {
if (result == 0) {
Object value1 = FieldUtils.readField(o1, sortingField, true);
Object value2 = FieldUtils.readField(o2, sortingField, true);
if (value1 instanceof Comparable && value2 instanceof Comparable) {
Comparable comparable1 = (Comparable) value1;
Comparable comparable2 = (Comparable) value2;
result = comparable1.compareTo(comparable2);
} else {
throw new RuntimeException("Cannot compare non Comparable fields. " + value1.getClass()
.getName() + " must implement Comparable<" + value1.getClass().getName() + ">");
}
} else {
break;
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
}
Arrays.sort(persons, new PersonComparator());
import java.util.Comparator;
public class PersonComparator implements Comparator<? extends Person> {
#Override
public int compare(Person o1, Person o2) {
if(null == o1 || null == o2 || null == o1.getName() || null== o2.getName() ){
throw new NullPointerException();
}else{
int nameComparisonResult = o1.getName().compareTo(o2.getName());
if(0 == nameComparisonResult){
return o1.getAge()-o2.getAge();
}else{
return nameComparisonResult;
}
}
}
}
class Person{
int age; String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Updated version:
public class PersonComparator implements Comparator<? extends Person> {
#Override
public int compare(Person o1, Person o2) {
int nameComparisonResult = o1.getName().compareToIgnoreCase(o2.getName());
return 0 == nameComparisonResult?o1.getAge()-o2.getAge():nameComparisonResult;
}
}
For a class Book like this:
package books;
public class Book {
private Integer id;
private Integer number;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "book{" +
"id=" + id +
", number=" + number +
", name='" + name + '\'' + '\n' +
'}';
}
}
sorting main class with mock objects
package books;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
Book b = new Book();
Book c = new Book();
Book d = new Book();
Book e = new Book();
Book f = new Book();
Book g = new Book();
Book g1 = new Book();
Book g2 = new Book();
Book g3 = new Book();
Book g4 = new Book();
b.setId(1);
b.setNumber(12);
b.setName("gk");
c.setId(2);
c.setNumber(12);
c.setName("gk");
d.setId(2);
d.setNumber(13);
d.setName("maths");
e.setId(3);
e.setNumber(3);
e.setName("geometry");
f.setId(3);
f.setNumber(34);
b.setName("gk");
g.setId(3);
g.setNumber(11);
g.setName("gk");
g1.setId(3);
g1.setNumber(88);
g1.setName("gk");
g2.setId(3);
g2.setNumber(91);
g2.setName("gk");
g3.setId(3);
g3.setNumber(101);
g3.setName("gk");
g4.setId(3);
g4.setNumber(4);
g4.setName("gk");
List<Book> allBooks = new ArrayList<Book>();
allBooks.add(b);
allBooks.add(c);
allBooks.add(d);
allBooks.add(e);
allBooks.add(f);
allBooks.add(g);
allBooks.add(g1);
allBooks.add(g2);
allBooks.add(g3);
allBooks.add(g4);
System.out.println(allBooks.size());
Collections.sort(allBooks, new Comparator<Book>() {
#Override
public int compare(Book t, Book t1) {
int a = t.getId()- t1.getId();
if(a == 0){
int a1 = t.getNumber() - t1.getNumber();
return a1;
}
else
return a;
}
});
System.out.println(allBooks);
}
}
I'm not sure if it's ugly to write the compartor inside the Person class in this case. Did it like this:
public class Person implements Comparable <Person> {
private String lastName;
private String firstName;
private int age;
public Person(String firstName, String lastName, int BirthDay) {
this.firstName = firstName;
this.lastName = lastName;
this.age = BirthDay;
}
public int getAge() {
return age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
#Override
public int compareTo(Person o) {
// default compareTo
}
#Override
public String toString() {
return firstName + " " + lastName + " " + age + "";
}
public static class firstNameComperator implements Comparator<Person> {
#Override
public int compare(Person o1, Person o2) {
return o1.firstName.compareTo(o2.firstName);
}
}
public static class lastNameComperator implements Comparator<Person> {
#Override
public int compare(Person o1, Person o2) {
return o1.lastName.compareTo(o2.lastName);
}
}
public static class ageComperator implements Comparator<Person> {
#Override
public int compare(Person o1, Person o2) {
return o1.age - o2.age;
}
}
}
public class Test {
private static void print() {
ArrayList<Person> list = new ArrayList();
list.add(new Person("Diana", "Agron", 31));
list.add(new Person("Kay", "Panabaker", 27));
list.add(new Person("Lucy", "Hale", 28));
list.add(new Person("Ashley", "Benson", 28));
list.add(new Person("Megan", "Park", 31));
list.add(new Person("Lucas", "Till", 27));
list.add(new Person("Nicholas", "Hoult", 28));
list.add(new Person("Aly", "Michalka", 28));
list.add(new Person("Adam", "Brody", 38));
list.add(new Person("Chris", "Pine", 37));
Collections.sort(list, new Person.lastNameComperator());
Iterator<Person> it = list.iterator();
while(it.hasNext())
System.out.println(it.next().toString());
}
}