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 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 have a series of objects that I want to sort in a specific way.
Example:
I have the class Employee that has an attribute departmentId.
So many instances of Employee objects have the same value for departmentId.
An employee also has a salary attribute.
So I have a List<Employee> employees objects that I want to sort so that the sorting order is as follows:
The employee with the lowest salary is first in the list followed by all the other employees sorted by salary of the same department.
Then after the last employee of that department I want the next department who has an employee with less salary than all other departments and the rest of the employees sorted etc.
E.g.
(John, 10000, A)
(Jane, 30000, A)
(Bill, 32000, A)
(Jim, 12000, B)
(Jake, 50000, B)
(James, 14000, C)
etc
What is the most efficient way to accomplish this? I want to make the code as compact and efficient as possible rather than creating temporary anonymous classes to add them to hash maps (unless that is the only efficient way).
Note:
I know about comparators and comparable etc.
My question is not about how to actually do the sorting (I know implement comparator) but how can code like this be efficiently done preferably avoiding a bunch of temporary anonymous objects
Also: I am not using Java 8 and would like a plain Java approach
Update:
In response to comments. I want the department with the lowest salary first then the next highest etc
Personally I'd use an Employee class and Department class, both would extend the Comparable<T> interface to make use of library functionality.
Here is an example of a Department class which has a name and a list of employees, which are sorted upon construction. You may want to shallow-copy the list on getEmployees() to prevent others from altering it's order:
class Department implements Comparable<Department> {
private List<Employee> employees;
private char name;
public Department(char name, List<Employee> employees) {
// avoid mutating original list
this.employees = new ArrayList<Employee>(employees);
this.name = name;
Collections.sort(this.employees);
}
#Override
public int compareTo(Department other) {
if (other == null) {
return 1;
}
// employees are sorted by salary within their department.
// all we need is to compare the lowest-salary employees
// from both departments
return this.employees.get(0).getSalary()
- other.employees.get(0).getSalary();
}
public List<Employee> getEmployees() {
return this.employees;
}
public char getName() {
return this.name;
}
}
Now Employee class only has to implement compareTo(Employee other) with the salary comparison:
class Employee implements Comparable<Employee> {
private String name;
private int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
#Override
public int compareTo(Employee other) {
if (other == null) {
return 1;
}
return this.salary - other.salary;
}
#Override
public String toString() {
return "Employee [name=" + name + ", salary=" + salary + "]";
}
public String getName() {
return name;
}
public int getSalary() {
return salary;
}
}
This should allow you to use Collections.sort on lists of departments and get the right order. Here is a full blown example:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Fiddles {
static class Department implements Comparable<Department> {
private List<Employee> employees;
private char name;
public Department(char name, List<Employee> employees) {
// avoid mutating original list
this.employees = new ArrayList<Employee>(employees);
this.name = name;
Collections.sort(this.employees);
}
#Override
public int compareTo(Department other) {
if (other == null) {
return 1;
}
return this.employees.get(0).getSalary()
- other.employees.get(0).getSalary();
}
public List<Employee> getEmployees() {
return this.employees;
}
public char getName() {
return this.name;
}
}
static class Employee implements Comparable<Employee> {
private String name;
private int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
#Override
public int compareTo(Employee other) {
if (other == null) {
return 1;
}
return this.salary - other.salary;
}
#Override
public String toString() {
return "Employee [name=" + name + ", salary=" + salary + "]";
}
public String getName() {
return name;
}
public int getSalary() {
return salary;
}
}
public static void main(String args[]) {
final Department A = new Department('A', new ArrayList<Employee>() {
{
add(new Employee("John", 10000));
add(new Employee("Jane", 30000));
add(new Employee("Bill", 32000));
}
});
final Department B = new Department('B', new ArrayList<Employee>() {
{
add(new Employee("Jim", 12000));
add(new Employee("Jake", 50000));
}
});
final Department C = new Department('C', new ArrayList<Employee>() {
{
add(new Employee("James", 14000));
}
});
List<Department> departments = new ArrayList<Department>() {
{
add(A);
add(B);
add(C);
}
};
Collections.shuffle(departments);
Collections.sort(departments);
for (Department department : departments) {
for (Employee e : department.getEmployees()) {
System.out.println(String.format(
"Employee: %s, Salary: %d, department: %s",
e.getName(), e.getSalary(), department.getName()));
}
}
}
}
I believe the simplest way is to:
Prepare a Map from each department ID to the minimum salary in that department. This requires scanning once through the list (O(n)).
Make a comparator that uses that map. It's just a single object. The comparator sorts such that:
If two employees are in the same department, compare their salaries.
If the two departments are different, compare their minimum salaries based on the above map.
If they have the same rank (lowest salary), sort by their IDs (otherwise you might get two departments with the same lowest salary mixed).
Otherwise compare their ranks.
So here is a demonstration (Edit: the operations are now encapsulated in the Employee class. The work it does is the same, though):
public class Employee {
private String name;
private int salary;
private String department;
/**
* Comparator - Note that it has a constructor that takes a department ranking
* list, which should be prepared in advance
*/
private static class DepartmentAndSalaryComparator implements Comparator<Employee>{
Map<String,Integer> departmentRanking;
public DepartmentAndSalaryComparator(Map<String,Integer> departmentRanking) {
this.departmentRanking = departmentRanking;
}
#Override
public int compare(Employee o1, Employee o2) {
// If employees belong to the same department, rank them by salary
if ( o1.department.equals(o2.department )) {
return o1.salary - o2.salary;
}
// Get the lowest salaries for the departments of the respective employees
int o1Rank = departmentRanking.get(o1.department);
int o2Rank = departmentRanking.get(o2.department);
if ( o1Rank == o2Rank ) {
return o1.department.compareTo(o2.department);
}
return o1Rank - o2Rank;
}
}
public Employee(String name, int salary, String department) {
this.name = name;
this.salary = salary;
this.department = department;
}
/**
* Creates a map of department id to minimum salary in that department
* from a given list of employees.
* This operation is O(n)
* #param employees List of employees for which to calculate map
* #return Map of department rankings
*/
private static Map<String,Integer> calculateDepartmentRanking( List<Employee> employees ) {
Map<String,Integer> rankings = new HashMap<>();
for ( Employee emp : employees ) {
Integer currMin = rankings.get(emp.department);
if ( currMin == null || currMin > emp.salary ) {
rankings.put(emp.department, emp.salary);
}
}
return rankings;
}
/**
* Static method to sort a list of employees by Department, then by Salary, where
* the order of department is based on the minimum salary in that department.
* This operation is O(n log n)
*
* #param employees The list of employees to sort
*/
public static void sortListBySalaryBasedDepartment( List<Employee> employees ) {
Collections.sort(employees, new DepartmentAndSalaryComparator(calculateDepartmentRanking(employees)));
}
#Override
public String toString() {
return String.format("Employee: name=%s, salary=%d, dept.=%s",
name,
salary,
department);
}
public static void main(String[] args) {
// Create example list and shuffle it to make sure it's not ordered
List<Employee> employees = Arrays.asList(
new Employee("Millie", 12000, "Accounts"),
new Employee("Morris", 21200, "Accounts"),
new Employee("Jerry", 22000, "Accounts"),
new Employee("Ellen", 17000, "Sales"),
new Employee("Sandy", 12500, "Technology"),
new Employee("Jack", 40000, "Technology")
);
Collections.shuffle(employees);
// Sort using the special comparator
Employee.sortListBySalaryBasedDepartment(employees);
for (Employee e : employees) {
System.out.println(e);
}
}
}
Output:
Employee: name=Millie, salary=12000, dept.=Accounts
Employee: name=Morris, salary=21200, dept.=Accounts
Employee: name=Jerry, salary=22000, dept.=Accounts
Employee: name=Sandy, salary=12500, dept.=Technology
Employee: name=Jack, salary=40000, dept.=Technology
Employee: name=Ellen, salary=17000, dept.=Sales
Here is my code in make sure to add student information with name ,age and their address. In order to make sure the student is unique. I use the hashCode() and equals() to make sure data integrity. The same name of student will be considered as override.
Problem is: The same information is never be cleaned out, Anybody know why? It seems the hashCode() and equals() never work.
class Student implements Comparable<Student>{
private String name;
private int age;
Student(String name, int age){
this.name = name;
this.age = age;
}
public int hashcode(){
return name.hashCode() + age *34;
}
//override equals method
public boolean equals(Object obj){
if(!(obj instanceof Student))
throw new ClassCastException("The data type is not match!");
Student s = (Student)obj;
return this.name.equals(s.name) && this.age==s.age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
#Override
public int compareTo(Student s) {
int num = new Integer(this.age).compareTo(new Integer(s.age));
if (num == 0)
return this.name.compareTo(s.name);
return num;
}
}
public class HashMapDemo1 {
public static void main (String[] agrs){
HashMap<Student,String> hs = new HashMap<Student,String>();
hs.put(new Student("James",27),"Texas");
hs.put(new Student("James",27), "California");
hs.put(new Student("James",27), "New mexico");
hs.put(new Student("Jack",22),"New York");
hs.put(new Student("John",25),"Chicago");
hs.put(new Student("Francis",26),"Florida");
Set<Student> Keyset = hs.keySet();
Iterator<Student> it = Keyset.iterator();
while(it.hasNext()){
Student stu = it.next();
String addr = hs.get(stu);
System.out.print(stu.getName()+stu.getAge()+"..." +addr+"\n");
}
}
hashcode != hashCode
Be sure to use the #Override annotation whenever you think that you are overriding a super-class's method, as this will allow the compiler to notify you if/when you are wrong. As you're finding out, it's much easier to fix errors at the compilation stage rather than the run-time stage.
Myself, I'd not use the age field as part of equals or hashCode since age can change for a student with time. I'd use Date birthDate or some other invariant instead.
And also I agree with Radiodef: the equals(...) method should not throw an exception. If the parameter object is not Student type, simply return false.
The method you have implemented is public int hashcode().
It should be public int hashCode().
I have a collection of the following:
Name Age Salary
John 30 $1000
Mary 25 $2000
Eric 22 $5000
How can I find the minimum age, salary and maximum age, salary?
Assuming that you are using a Collection of custom Objects (let's say Person objects), you can simply use the Collections API with different Comparator.
Let's say your Person class has at least these three methods:
getName()
getAge()
getSalary()
Now you can implement an age comparator:
public class AgeComparator implements Comparator<Person> {
public int compare(Person p1, Person p2) {
if(p1.getAge() < p2.getAge()) return -1;
else if(p1.getAge() > p2.getAge()) return 1;
else return 0;
}
public boolean equals(Person p1, Person p2) {
return this.compare(p1, p2) == 0;
}
}
To get the max or min aged person, use Collections:
Collections.min(myList, new AgeComparator());
Collections.max(myList, new AgeComparator());
You can use another comparator (say SalaryComparator) that works like AgeComparator but considers salary.
Collections.min(myList, new SalaryComparator());
Naturally, you might use a single class for this as well and set a variable instead of using two different comparators.
Since you have no information in your post I am going to make some assumptions.
You have an Employee class that looks like:
public class Employee {
private String name;
private int age;
private BigDecimal salary;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public BigDecimal getSalary() {
return salary;
}
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
}
And you have some Collection<Employee> such a List:
final List<Employee> employees = ...
Now, I will also assume you have Java 8. To find the oldest Employee you would do:
Optional<Employee> oldest = employees.stream().
max(Comparator.comparing(Employee::getAge));
You will see that this returns an Optional - this is because the behaviour when employees is empty is undefined. You will need to define your own behaviour.
Similarly for the other properties:
Optional<Employee> youngest = employees.stream().
min(Comparator.comparing(Employee::getAge));
Optional<Employee> highestEarner = employees.stream().
max(Comparator.comparing(Employee::getSalary));
Optional<Employee> lowestEarner = employees.stream().
min(Comparator.comparing(Employee::getSalary));
You can set a Comparator for each field
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class NameAgeSalary {
private String name;
private Integer age;
private Float salary;
private NameAgeSalary(String name, Integer age, Float salary) {
super();
this.name = name;
this.age = age;
this.salary = salary;
}
#Override
public String toString() {
return "NameAgeSalary [name=" + this.name + ", age=" + this.age
+ ", salary=" + this.salary + "]";
}
public static void main(String[] args) {
NameAgeSalary n1 = new NameAgeSalary("John",30,1000f);
NameAgeSalary n2 = new NameAgeSalary("Mary",25,2000f);
NameAgeSalary n3 = new NameAgeSalary("Eric",22,5000f);
List<NameAgeSalary> list = Arrays.asList(n1,n2,n3);
Collections.sort(list, new Comparator<NameAgeSalary>() {
#Override
public int compare(NameAgeSalary o1, NameAgeSalary o2) {
/*
* uncomment the field you want to compare
*/
return o1.age.compareTo(o2.age);
// return o1.name.compareTo(o2.name);
// return o1.salary.compareTo(o2.salary);
}
});
System.out.println("MIN "+list.get(0)); //min
System.out.println("MAX "+list.get(list.size()-1)); //max
}
}