Deciding to use Comparable or Comparator - java

My program implements a Product class whose objects contain the following instance variables: name, priority, price, and amount.
I have a LinkedList of Product objects that I need to sort before doing any other operations on the LinkedList.
I want to sort the list first by priority (from lowest to highest). If priority is the same, then look at the price (lowest to highest), then the name (alphabetical order).
I have done a lot of reading about Collections.sort, Comparable, and Comparator. I believe I need to use the Comparable interface and implement a compareTo method. My thinking is that because both priority, price, and name have a "natural" ordering it makes more sense to use Comparable.
public class Product extends ProductBase implements PrintInterface, Comparable<Product>{
private String name;
private int priority;
private int cents;
private int quantity;
// setters and getters
/**
* Compare current Product object with compareToThis
* return 0 if priority, price and name are the same for both
* return -1 if current Product is less than compareToThis
* return 1 if current Product is greater than compareToThis
*/
#override
public int compareTo(Product compareToThis)
}
Then when I want to sort my LinkedList I just call Collections.sort(LinkedList). Before I start writing the code, can you tell me if I am I missing or forgetting anything?
*************UPDATE*******************************
I just created a separate class called ProductComparator with a compare method.
This is part of the LinkedList class..
import java.util.Collections;
public class LinkedList {
private ListNode head;
public LinkedList() {
head = null;
}
// this method will sort the LinkedList using a ProductComparator
public void sortList() {
ListNode position = head;
if (position != null) {
Collections.sort(this, new ProductComparator());
}
}
// ListNode inner class
private class ListNode {
private Product item;
private ListNode link;
// constructor
public ListNode(Product newItem, ListNode newLink) {
item= newItem;
link = newLink;
}
}
}
I am getting the following error from the IDE when I compile.
The method sort(List, Comparator) in the type Collections is not applicable for the arguments (LinkedList, ProductComparator).
Does anyone know why I am getting this error and can point me in the right direction to resolve it?

If there is a "natural" ordering, use Comparable. Rule of thumb for figuring out if the ordering is "natural" is, whether the order of the objects will always be that.
Having said that, the decision whether to use Comparable or Camparator is not the kind of decision you need to think too much about. Most IDEs have refactoring tools which makes the conversion between a Comparable and a Comparator very easy. So if you choose to walk the wrong path now, changing that will not require too much effort.

The order you define here on your Product is very specific and
will probably change in future versions of your program
might be enriched with contextual parameterization
won't cover new features
So it can hardly been said "natural".
I'd suggest to define a constant, for example
public static Comparator<Product> STANDARD_COMPARATOR = new Comparator<Product>() {
public int compare(Product p1, Product p1) {
return ...
}
};
then you'll be able to easily sort anywhere with
Collections.sort(myProductList, Product.STANDARD_COMPARATOR);
Your code will evolve in a better manner as you'll add other comparators.
Just like you should generally prefer composition over inheritance, you should try to avoid defining the behavior of your objects in immutable manner.

If your order was based only on numbers, Comparable would be fine.
However, since your order (sometimes) involves lexical order of text,
a Comparator class is better, since use of Comparable would mean using
String.compareTo which would prevent you from having internationalization.
A separate class which implements Comparator can make use of a
localized Collator for comparing Strings. For instance:
public class ProductComparator
implements Comparator<Product> {
private final Collator collator;
public ProductComparator() {
this(Locale.getDefault());
}
public ProductComparator(Locale locale) {
this.collator = Collator.getInstance(locale);
}
public int compare(Product product1,
Product product2) {
int c = product1.getPriority() - product2.getPriority();
if (c == 0) {
c = product1.getPrice() - product2.getPrice();
}
if (c == 0) {
c = collator.compare(product1.getName(), product2.getName());
}
return c;
}
}
Regardless of whether you go with Comparable or Comparator, it is wise
to make sure Product has an equals method which checks the same
attributes as the comparison code.

Related

The default (natural) sorting order is: Country and then Week

Implement the class: WeeklyDataProper that extends the class WeeklyData.
Requirements:
The default (natural) sorting order is: Country and then Week
The class could be properly used in a HashSet collection. Two objects are considered equal if they have the same Country and Week attributes.
How can I do the first part of the question? by compareTo method?
Right now the result is:
int week, String country
I need :
String country, int week
First, HashSet does NOT maintain any order:
It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time
The implementation of a SortedSet is TreeSet which may use a natural order for objects implementing Comparable interface (thus implementing compareTo method), or a custom comparator via constructor public TreeSet(Comparator<? super E> comparator)
So, class WeeklyDataProper may implement Comparable interface as follows (null checks omitted here):
public class WeeklyDataProper extends WeeklyData implements Comparable<WeeklyDataProper> {
// getters getCountry / getWeek implemented in the parent
// ...
#Override
public void int compareTo(WeeklyDataProper that) {
int result = this.getCountry().compareTo(that.getCountry());
if (result == 0) {
result = Integer.compare(this.getWeek(), that.getWeek());
}
return result;
}
}
However, it may be a bit redundant to implement a separate subclass just to sort the set of WeeklyData, therefore the sorted set of WeeklyData may be retrieved with the custom comparator:
SortedSet<WeeklyData> sorted = new TreeSet<>(
Comparator.<WeeklyData>comparing(WeeklyData::getCountry)
.thenComparingInt(WeeklyData::getWeek)
);

Require input regarding JAVA ENUMS

Consider the following
public enum tc implements {
NORESULTS(0), GOOD_RESULTS(1), EXCELLENT_RESULTS(2), NO_DATA_AVAILABLE(5), SOME_OTHER_VALUE(4);
private final Integer value;
// Code for the constructor, getters and setters for the value****
The enum tc values correspond to the testValue in the below class.
public class TestData {
private int testID;
private String testName;
private int testValue;
....
...
}
In the Results class, the TestDataList has to be sorted by a different order of ranking rather than testValue.For example Excellent followed by Good Results followed by NoResults etc..
public class Results {
List<TestData> TestDataList = getTestData();
I can code for the comparator etc..the question is since I require a different ordering for the enums which of the following two options is better
a) add private int rankTestValue in the enum tc. This option may require that I have to write a method getRank(int value) that would return the corresponding rankTestValue based on the value.
OR
b) add in Results class a map Map tcRankMap = new HashMap();. Populate this map with key values like (2,1) (1,2) corresponding to (enum values, ranking).For example (2,1) would be Excellent_Results has first ranking etc.
Which of these two options would be better. If there are better solutions then please let me know.
Option (a) looks better and according to Object Oriented Analysis and Design.
The good news is that the is a question of implementation detail which can be encapsulated into your Comparator anyway, so it doesn't matter so much.
As for style and readability, I would prefer (a), but it's down to personal preference.
There is also a solution (c) - use the ordinal(), and then sort them according to rank. Just add a comment to make it clear
public enum tc implements {
// NB: enum values are sorted according to rank
EXCELLENT_RESULTS(2),
GOOD_RESULTS(1),
NORESULTS(0),
NO_DATA_AVAILABLE(5),
SOME_OTHER_VALUE(4);
private final Integer value;
// Code for the constructor, getters and setters for the value****
}
Your first option would look like this:
enum TestScore {
EXCELLENT(5),
NO_RESULT(2),
POOR(1);
private final int order;
private TestScore(int order) {
this.order = order;
}
public int compareOrderTo(TestScore other) {
return this.order - other.order;
}
}
You could then add a comparison method to TestData
public int compareTestScore(TestData other) {
return this.testScore.compareOrderTo(other.testScore);
}
And sort your list with:
Collections.sort(testData, TestData::compareTestScore);
The problem with this is that the order field is really completely arbitrary and needs to be updated each time you add a new entry. However that's definitely better and more explicit than using the natural ordering of the enum (i.e. it's ordinal value which should be entirely incidental to avoid fragility).

Using a custom comparator

I've checked a bunch of similar questions but am still very confused. Anyway this was for an assignment that's been and gone.
I have a Present class:
public class Present implements PresentInterface{
private String name;
private String type;
private double cost;
public Present() {
}
public Present(String name, String type, double cost) {
this.name = name;
this.type = type;
this.cost = cost;
}
and then a bunch of code for getting and setting the values.
I have a Child class which contains a bunch of info about the child.
I have a GiftList class which is an arraylist of presents. Each giftlist is associated with at most one child.
I then have a GiftSelector class which is an arraylist of GiftLists.
I would like to have a method in the giftSelector class that creates a hashmap with the keys being children, and the values being a list of presents sorted by cost.
So far I have:
public HashMap<Child, ArrayList<Present>> sortList(){
HashMap<Child, ArrayList<Present>> presentMap = new HashMap<Child, ArrayList<Present>>();
ArrayList<Present> presentList = new ArrayList<Present>();
for (GiftList giftList : giftSelector){
presentList.clear();//clears the present list with each iteration otherwise
//each child would be paired with a list of presents containing those
//of the child before.
Child mapChild = giftList.getChild();
for (Present present : giftList.getAllPresents()){
presentList.add(present);//goes through each present in the giftlist and adds it to presentlist
}
Collections.sort(presentList, new Comparator<Present>());
presentMap.put(mapChild, presentList);
}
return presentMap;
}
}
The comparator isn't defined so of course it doesn't work. Do I define the comparator in the Present class or in the giftSelector class or do I give it an entirely new class of its own?
I think I need something like this somewhere:
public int compare(Present p1, Present p2){
if (p1==null || p2 == null){
return 0;
}
return p1.getCost().compareTo(p2.getCost());
}
and then some stuff which involves overriding and setting the compareTo values and other bits of magic. Any advice would be greatly appreciated :)
Incidentally, when I take out the Collections.sort(presentList, comparator) bit of the sortList() method, it compiles and runs fine, except that each child in the presentMap has the same value. They all have an arraylist containing the presents of the last giftlist that was iterated through. I've probably missed something obvious though.
I would define it as a static member class:
public class Present implements PresentInterface {
public static class CostComparator implements Comparator<Present >
{
public int compare(Present p1, Present p2)
{
// use either this line for null
if (p1 == null || p2 == null) throw new NullPointerException();
// or these 2 lines for null:
if (p1 == null) return p2 == null ? 0 : -1;
if (p2 == null) return 1;
// and now do a reference check for speed
if (p1 == p2) return 0;
// and finally the value checks
return Double.compare(p1.cost, p2.cost);
}
}
private String name;
private String type;
private double cost;
}
There are two alternatives for null ordering (as shown) as per the docs:
Unlike Comparable, a comparator may optionally permit comparison of null arguments, while maintaining the requirements for an equivalence relation
Putting it inside the Present class just makes it easy to find, and because it is only relevant to the Present class it makes sense to nest it. I would however document its inconsistency with equals as, according to the docs:
Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map).
i.e. as it now has a different "natural order" definition to equals, you may run into unforeseen problems in various circumstances.
You should also think about how you order two different Present objects that have the same cost - is ordering by cost all you need to do?
Late edit as far as your "second question" goes, you need to instantiate a new ArrayList each time you start the iterator, otherwise every map value refers to the same arraylist (see this answer for more explanation):
for (GiftList giftList : giftSelector){
presentList = new ArrayList<Present>; // create a new instance of a present list with each iteration otherwise ...

Can I define my own subclass of java.util.List?

Can I define my own list in Java?
I have my own list-type class that is very similar to a LinkedList, called PersonList.
In another program, I'm using a Comparator, so I need to have a List() object as the parameter.
Is it possible to make the following statement, if I make changes in my code?
List list1= new PersonList();
PersonList doesn't extend or import anything.
You'd need to implement the built in interface java.util.List. It would need to define all the methods listed in the interface java.util.List.
You just have to overload the equals function which is implemented by every
class of Type Object (Every class). The list implementation will use your equals implementation due to the polymorphic concept of OOP.
I strongly recommend to use the given List implemenmtations because they meet all
performance issues you don't even think about. When you have concurrency issues refer to the documentation.
In order to achieve customized comparison you have to implement the Comparable interface
and implement its method toCompare(..);
In this way you can use all given Collection API classes and extend them using your own
comparison or equals algorithm which meets your application needs.
Update to to Comments
class Person implements Compareable {
#override
public int compareTo(Person p) {
return p.age > this.age; //Or whatever
}
#Override
equals(Object person) {
if (person instanceof Person) {
Person p = (Person)person;
if (p.x == this.x &&
p.y == this.y &&
p.address.equals(this.address) {
...
return true;
}
}
return false;
}
}
And now just intialize you list.
List<Person> personList = new ArrayList<Person>();
or
List<Persin> personList = new Vector<Person>();
or
LinkedList<Person> personList = new Queue<Person>();
and and and.
Collections.sort(personList);
To answer the question in the comment, "How would I go about writing my own Comparator for a Linked List?":
public class PersonListComparator implements Comparator<LinkedList> {
#Override
public int compare(LinkedList list1, LinkedList list2) {
// something that returns a negative value if list1<list2, 0 if list1 and
// list2 are equal, a positive value if list1>list2.
}
}
See the javadoc for Comparator, especially the text at the top. This explains what could happen if the compare function could return 0 when list1.Equals(list2) is false. It's not necessarily a problem, depending on how you use it.
Note that I'm still assuming you want to compare entire lists (rather than just individual Persons). Based on later comments, it looks like you want to compare Person objects, but provide different ways to compare ("depending on the different parameter being compared"). You could define more than one class that implements Comparator<Person>. Or you could define a class that takes a parameter when you construct the object:
public enum ComparisonType { NAME, AGE, WEIGHT } // whatever
public class ComparePerson implements Comparator<Person> {
private ComparisonType type;
public ComparePerson(ComparisonType type) {
this.type = type;
}
#Override
public int compare(Person p1, Person p2) {
switch(type) {
case NAME:
// return comparison based on the names
case AGE:
// and so on
...
}
}
}
I haven't tested this, so I could have made a mistake, but you get the idea. Hope this helps, but it's still possible I've misunderstood what you're trying to do.

In Java, How do you quicksort an ArrayList of objects in which the sorting field is multiple layers deep?

Basically, I have a Container class called "Employees" which has in it an ArrayList. This ArrayList contains "Employee" objects, which in turn contain "EmployeeData" objects which in turn contain String objects such as "first" or "last" (which are employee names).
Here's a diagram of the ArrayList structure:
ArrayList[Employee] emps ==> 1:Many ==> Employee emp
Employee emp ==> 1:1 ==> EmployeeData data
EmployeeData data ==> 1:2 ==> String last // A string that contains employee's last name.
How in the world would I perform a quicksort on the ArrayList so that the "Employee" objects in it are in alphabetical order based on the String object "last"? It seems kinda complicated!
Here's a basic design of my classes:
class Employees{
//data:
private ArrayList<Employee> emps = new ArrayList<Employee>();
//Some constructors go here
//Methods to add, remove, toString, etc, go here
public /*output a sorted ArrayList?*/ sort(){
// Some kind of "quicksort" in here to modify or create a new ArrayList sorted by employee's las name...
}
}
class Employee{
//data:
EmployeeData data;
// Some methods to construct and modify EmployeeData data.
}
class EmployeeData{
//data:
String first, last; // I wish to sort with "last". How do you do it?
double payrate, hours;
//...methods...
}
As you can see, those are the classes. I have no idea how to implement "sort" in the "Employees" class so that it sorts the ArrayList by the "last" variable of the "EmployeeData" class.
You can make a comparator, something like:
public class MyComparator implements Comparator<Employee>
{
public int compare(Employee e1, Employee e2)
{
return e1.getData().getLast().compareTo(e2.getData().getLast());
}
}
Then use it to sort the list.
Collections.sort(myList, new MyComparator());
Alternatively, you can use a TreeSet to sort on insertion using this comparator or make the Employee a comparable object to sort using Collections or a SortedSet.
public class Employee implements Comperable<Employee>
{
...
public int compareTo(Employee e)
{
return this.getData().getLast().compareTo(e.getData().getLast());
}
...
}
Define Employee implements Comparable<Employee>.
In the compareTo method, dig into the layers and compare the strings you need. Then you can use Collections.sort(), or you can store the data in a SortedSet, which is naturally ordered.
The best practice is to encapsulate the sorting logic in the class stored in the ArrayList, Employee in this case. Implement Comparable by creating a compareTo(Employee) method.
import java.util.*;
public class Employee implements Comparable<Employee> {
public EmployeeData Data;
public Employee(String first, String last)
{
Data = new EmployeeData(first, last);
}
public int compareTo(Employee other)
{
return Data.Last.compareTo(other.Data.Last);
}
public String toString() {
return Data.First + " " + Data.Last;
}
public static void main(String[] args) throws java.io.IOException {
ArrayList list = new ArrayList();
list.add(new Employee("Andy", "Smith"));
list.add(new Employee("John", "Williams"));
list.add(new Employee("Bob", "Jones"));
list.add(new Employee("Abraham", "Abrams"));
Collections.sort(list);
for (int i = 0; i < list.size(); i++)
{
System.out.println(list.get(i));
}
System.in.read();
}
}
public class EmployeeData {
public String First;
public String Last;
public EmployeeData(String first, String last)
{
First = first;
Last = last;
}
}
Output:
Abraham Abrams
Bob Jones
Andy Smith
John Williams
Peter DeWeese and others have given you very good answers. You can use
Collections.sort(myList, new MyComparator());
to sort myList using a Comparator you have defined. <=== What the heck does that mean?
In Java, if something implements Comparable (java.lang.comparable) then you can define an order for your elements. It seems like you know what Java Generics are, as you used them to declare your ArrayList as being of type < Employee >. This is awesome, because you can store an Employee object into each entry in the ArrayList. So far so good?
However, if you want to sort objects, first you have to define an order. Since objects can have various properties, maybe I want to sort my employees by ear-size. In this case, I simply tell Java that my class implements Comparable. With generics, I have to specify that it implements Comparable< Employee > because I am defining an order for my Employee objects (peons, minions, whatever).
Peter DeWeese mentioned:
public int compareTo(Employee e)
{
return this.getData().getLast().compareTo(e.getData().getLast());
}
and Jason Goemaat mentioned:
public int compareTo(Employee other)
{
return Data.Last.compareTo(other.Data.Last);
}
What the heck does this mean? If I say that my class implements Comparable then I need to define a compareTo function. (An interface is a collection of methods that need to be implemented) The function compareTo defines the order of my elements.
From the Comparable< T> spec:
int compareTo(T o)
Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
If I am comparing ear sizes, and let's say I want big ears to come first in my list, then I could (re)define compareTo as:
public int compareTo(Employee e)
{
if (this.earSize > e.earSize) //big ears come first
return -1;
if (this.earSize == e.earSize) //equality
return 0;
else
return 1; // if e.earSize > this.earSize then return 1
}
To answer Steve Kuo's question, we put the keyword this in our comparator because when we call the compareTo method
x.compareTo(y);
the keyword this will refer to x.
You can think of compareTo as being a method of the object x, so when you call x.compareTo(y) you are really saying this.compareTo(y) from within the scope of object x.
We can also look at a String example:
This means that if I want "Medvedev" to come before "Putin" (as 'M' comes before 'P' in the English alphabet) I would have to state that I want compareTo to return -1 when comparing Medvedev to Putin.
String TheMString = "Medvedev";
String ThePString = "Putin";
then the line
TheMString.compareTo(ThePString);
will evaluate to -1.
Now a standard routine such as Collections.sort(list, comparator) will be able to use these values that compareTo returns to figure out the [absolute] order of list. As you may know, sorting is a comparison based operation and we need to know what value is "less than" or "greater than" another value in order to have a meaningful sort.
One big caveat is that if you call compareTo on Strings, it defaults to alphabetical order, so you may simply tell compareTo to return A.compareto(B) and it will make sure the strings are in order.
Normally (well, I should say, in other cases) when redefining the compareTo method, you must explicitly state a neg/zero/pos return value.
I hope that helps.

Categories