I have several arrays in the form:
private static String[] patientNames = { "John Lennon", "Paul McCartney", "George Harrison", "Ringo Starr" };
Then I make a TreeSet like this:
TreeSet<Patient> patTreeSet = new TreeSet<Patient>();
Where Patient is a different class that makes "Patient" objects.
Then I loop through each element in my arrays to create several patients and add them to my patTreeSet like this:
for(int i = 0; i< patientNames.length; i++){
Date dob = date.getDate("MM/dd/yyyy", patientBirthDates[i]);
Patient p = new PatientImpl(patientNames[i], patientSSN[i], dob);
patTreeSet.add(p);
}
But when I go to check my patTreeSet.size() it only returns "1" - why is this?
I know my objects are working well because when I try to do the same thing but with ArrayList instead, everything works fine. So I'm guessing I'm using the TreeSet wrong.
If it helps, Patient has a method called getFirstName(), and when I try to do the following:
Iterator<Patient> patItr = patTreeSet.iterator();
while(patItr.hasNext()){
System.out.println(patItr.next().getFirstName());
}
Then only "John" prints, which obviously shouldn't be the case... So, am I totally misusing the TreeSet?
Thanks in advance for any help!
EDIT below
================PatientImpl Class====================
public class PatientImpl implements Patient, Comparable{
Calendar cal = new GregorianCalendar();
private String firstName;
private String lastName;
private String SSN;
private Date dob;
private int age;
private int thisID;
public static int ID = 0;
public PatientImpl(String fullName, String SSN, Date dob){
String[] name = fullName.split(" ");
firstName = name[0];
lastName = name[1];
this.SSN = SSN;
this.dob = dob;
thisID = ID += 1;
}
#Override
public boolean equals(Object p) {
//for some reason casting here and reassigning the value of p doesn't take care of the need to cast in the if statement...
p = (PatientImpl) p;
Boolean equal = false;
//make sure p is a patient before we even compare anything
if (p instanceof Patient) {
Patient temp = (Patient) p;
if (this.firstName.equalsIgnoreCase(temp.getFirstName())) {
if (this.lastName.equalsIgnoreCase(temp.getLastName())) {
if (this.SSN.equalsIgnoreCase(temp.getSSN())) {
if(this.dob.toString().equalsIgnoreCase(((PatientImpl) p).getDOB().toString())){
if(this.getID() == temp.getID()){
equal = true;
}
}
}
}
}
}
return equal;
}
and then all the getters are below, as well as the compareTo() method from the Comparable interface
If you put your objects in a TreeSet, you need to either provide an implementation of the Comparator interface in the constructor, or you need your objects to be of a class that implements Comparable.
You said you implement compareTo from the Comparable interface, but in your comment you say that you didn't, so am I correct in assuming that you just return 0; in the compareTo method? That would explain your problem, because TreeSet would then think that all your objects are 'the same' based on the compareTo method result.
Basically, in a TreeSet, your objects are maintained in a sorted order, and the sorting is determined by the outcome of the Comparable/Comparator method. This is used to quickly find duplicates in a TreeSet and has the added benefit that when you iterate over the TreeSet, you get the results in sorted order.
The Javadoc of TreeSet says:
Note that the ordering maintained by a set (whether or not an explicit
comparator is provided) must be consistent with equals if it is
to correctly implement the Set interface.
The easiest way to achieve that is to let your equals method call the compareTo method and check if the result is 0.
Given your PatientImpl class, I assume that you would want to sort patients first by their last name, then by their first name, and then by the rest of the fields in the class.
You could implement a compareTo method like this:
#Override
public int compareTo(Object o) {
if (!(o instanceof Patient))
return -1;
Patient temp = (Patient) o;
int r = this.lastName.compareToIgnoreCase(temp.getLastName());
if (r == 0)
r = this.firstName.compareToIgnoreCase(temp.getFirstName());
if (r == 0)
r = this.SSN.compareToIgnoreCase(temp.getSSN());
if (r == 0)
r = this.dob.toString().compareToIgnoreCase(temp.getDOB().toString());
if (r == 0)
r = Integer.compare(this.getID(), temp.getID());
return r;
}
I believe that would solve the problem you described.
I would advise you to read up (Javadoc or books) on TreeSet and HashSet and the importance of the equals, compareTo and hashCode methods.
If you want to put your objects in a Set or a Map, you need to know about these to implement that correctly.
Note
I based this compareTo method on your equals method.
You were comparing the date-of-birth by first calling toString. That's not a very good way of doing that - you can use the equals method in java.util.Date directly. In a compareTo method the problem gets worse because dates do not sort correctly when you sort them alphabetically.
java.util.Date also implements Comparable so you can replace that comparison in the method with:
if (r == 0)
r = this.dob.compareTo(temp.getDOB());
In addition, if any of the fields could be null, you need to check for that as well.
Related
Pls tell what is wrong is happening here. I have a Person class which I'm using as a key in TreeMap. I have implemented Comparable also so that TreeMap can do sorting.
public class Person implements Comparable{
private String name;
private int age;
// getters and setters were omitted
#Override
public int compareTo(Object o) {
return 0;
}
}
Now I created TreeMap and added values in it as:
Map treeMap=new TreeMap<Person,Object>();
treeMap.put(new Person(), "String1");
treeMap.put(new Person(), "String2");
treeMap.put(new Person(), "String3");
treeMap.put(new Person(), "String4");
System.out.println(treeMap);
After printing directly using System.out.println(treeMap); Iam only getting the last inserted value ie
Output:{Person#4aa36c=String4}
I know keys should be different but new operator always create a new object so I think its fine. But Iam helpless to figure out what is wrong going on here.
You are likely placing items in the Map backwards and would more than likely want the Person object as the value and the String as the key. But first you need to enhance your Person object with a Constructor to allow you to set the name at least, and maybe the age. Add a constructor to `Person':
public Person(String n, int a){
this.name = n;
this.age = a;
}
Then you can reorder how you add elements to the Map:
Person p1 = new Person("Bob Jones", 32);
treeMap.put(p1.getName(), person);
Additionally, TreeMap uses the compareTo method to determine where to place the entry in the Map for the keys. So, if you intend to use the Person object as the key, you need to implement the compareTo method. But your compareTo method is not implemented correctly and simply returns 0 for every element, causing your elements to overwrite each others location in the Map:
#Override
public int compareTo(Object o) {
/ TODO Auto-generated method stub
return 0;
}
You need to properly implement the method contents (Hence the TODO statement).
Maybe add a social security number (SSN) to the Person class:
Long ssn;
public void setSsn(Long value){
ssn = value;
}
public Long getSsn(){
return ssn;
}
Then you could compare on ssn:
#Override
public int compareTo(Object o) {
if(o == null) return 1;
Person op = (Person)o;
return ssn.compareTo(op.getSsn());
}
But if you just want to create some type of combination with what you have, maybe concatenate the name and age to try to have uniqueness:
#Override
public int compareTo(Object o) {
if(o == null) return 1;
Person op = (Person)o;
return (name + age).compareTo(op.getName() + op.getAge());
}
TreeMap uses compareTo method of Comparable (not equals method from Object) when it tries to put an element into Map. Since your compareTo method implementation returns 0 for all Person objects, there can only be one element in TreeMap.
So when you try to put multiple Person keys, TreeMap only updates value, since for it all Person objects are same (as per your implementation for compareTo).
Here is code snippet for TreeMap.put from Java-8
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
#SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
So first it checks, whether Comparator is provided, if yes, TreeMap uses compare method to compare keys otherwise it uses compareTo method of Comparable for comparison.
You overrode toCompare method incorrectly. It always returns 0. So, all objects in the tree will be interpreted as the same and your treeMap always contains only the value that had been added the last.
I suggest you consider the compact (but not general as #4castle noted) solution.
#Override
public int compareTo(Person o) {
if (age != o.age) return age > o.age ? 1 : -1;
return name.compareTo(o.name);
}
If you changed the first line of the class declaration to
public class Person implements Comparable<Pesron>
Keys aren't unique based on their memory address, they are unique based on having a unique value. All of your Person objects are practically identical, but what's more, all of your Person objects claim to be "equal" to any other Person object given your current compareTo implementation (0 means equal).
The parameter order for the put method is key first, value second. You need to rearrange the put statements so that something unique is in front (the string), and therefore make all the entries unique.
treeMap.put("String1", new Person());
treeMap.put("String2", new Person());
treeMap.put("String3", new Person());
treeMap.put("String4", new Person());
Now there's no need to implement Comparable or have the compareTo method in Person.
If you require that the Person object be a key, you will have to make each one unique by giving it a unique variable with which to use the compareTo method properly. The best solution is to add a required ID number to each Person object, or otherwise use the name and age as the unique value for the object. Add on this code to Person:
private int id;
private static int numPeople = 0;
public Person() {
numPeople++;
id = numPeople;
name = "";
}
#Override
public int compareTo(Person o) {
if (age != o.age) return age > o.age ? 1 : -1;
if (!name.equals(o.name) return name.compareTo(o.name);
return id > o.id ? 1 : -1;
}
with
public class Person implements Comparable<Person>
public class Drink implements Comparable {
public String name;
#Override
public int compareTo(Object o) {
return 0;
}
#Override
public String toString() {
return name;
}
public static void main(String[] args) {
Drink one = new Drink();
Drink two = new Drink();
one.name = "Coffee";
two.name = "Tea";
TreeSet set = new TreeSet();
set.add(one);
set.add(two);
Iterator itr = set.iterator();
while(itr.hasNext()) {
System.out.println(itr.next()); //prints Tea
}
}
}
Usually, compareTo() method prints in lexicographical order, but when compareTo() method is overridden as in the above code then how it is comparing the two strings?
According to your compareTo method, all objects are equal to each other, since you always return 0, so when you try to add two Drink objects to your TreeSet, only the first one will be added, since a Set doesn't allow duplicates.
It would make more sense to have an implementation like this, that actually compares the names :
public class Drink implements Comparable<Drink> {
public String name;
#Override
public int compareTo(Drink o) {
return name.compareTo(o.name);
}
...
}
It is not comparing the string in this as thecomapareTo() method is returning 0 (meaning objects are equal) so set.add(two) will be considered as duplicated and only the first value added will be printed.
Try reversing the order of addition of values to the set and you will get your answer
The overridden compareTo method is used for customize comparison. In this function, you compare two objects based on your business logic and on the basis of your logic, you return -1,0 or 1, where -1 represents invoking object is smaller than the invoked object while _1 represents the other way. 0 represents that both the objects are equal.
In your code, right now it is not putting any logic. Its just returning a prototype value. You might put something like that in your code
return name.compareTo((String)o);
which will be the default functionality if you don't put your custom override method.
I am trying to write a custom hashCode fn, but I am not able to figure out the correct way to do that.
public class Person {
String name;
List<String> attributes;
#Override
public boolean equals(Object o) {
// Persons are equal if name is equal & if >= 2 of attributes are equal
// This I have implemented
}
#Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + (this.name == null ? 0 : this.name.hashCode());
//Not sure what to do here to account for attributes
return result;
}
}
I want the hashCode fn to be such that:
"If object1 and object2 are equal according to their equals() method, they must also have the same hash code"
Not sure how to do that?
As Oli points out in the comments, you cannot solve this by implementing equals() and relying on a Set to de-duplicate for you. Weird things could happen.
Thus you must resort to coding this yourself. Add the first item from your list into your new de-duplicated list. Then for each remaining item in your original list, compare it with those already present in your de-duplicated list and only add it if it passes your non-duplicate test.
Easiest way to fulfill the contract of the equals/hashcCode methods is to return a constant:
#Override
public int hashCode() {return 13;}
Otherwise your solution with a hash code based only on name will work.
I am learning about arrays, and basically I have an array that collects a last name, first name, and score.
I need to write a compareTo method that will compare the last name and then the first name so the list could be sorted alphabetically starting with the last names, and then if two people have the same last name then it will sort the first name.
I'm confused, because all of the information in my book is comparing numbers, not objects and Strings.
Here is what I have coded so far. I know it's wrong but it at least explains what I think I'm doing:
public int compare(Object obj) // creating a method to compare
{
Student s = (Student) obj; // creating a student object
// I guess here I'm telling it to compare the last names?
int studentCompare = this.lastName.compareTo(s.getLastName());
if (studentCompare != 0)
return studentCompare;
else
{
if (this.getLastName() < s.getLastName())
return - 1;
if (this.getLastName() > s.getLastName())
return 1;
}
return 0;
}
I know the < and > symbols are wrong, but like I said my book only shows you how to use the compareTo.
This is the right way to compare strings:
int studentCompare = this.lastName.compareTo(s.getLastName());
This won't even compile:
if (this.getLastName() < s.getLastName())
Use
if (this.getLastName().compareTo(s.getLastName()) < 0) instead.
So to compare fist/last name order you need:
int d = getFirstName().compareTo(s.getFirstName());
if (d == 0)
d = getLastName().compareTo(s.getLastName());
return d;
The compareTo method is described as follows:
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.
Let's say we would like to compare Jedis by their age:
class Jedi implements Comparable<Jedi> {
private final String name;
private final int age;
//...
}
Then if our Jedi is older than the provided one, you must return a positive, if they are the same age, you return 0, and if our Jedi is younger you return a negative.
public int compareTo(Jedi jedi){
return this.age > jedi.age ? 1 : this.age < jedi.age ? -1 : 0;
}
By implementing the compareTo method (coming from the Comparable interface) your are defining what is called a natural order. All sorting methods in JDK will use this ordering by default.
There are ocassions in which you may want to base your comparision in other objects, and not on a primitive type. For instance, copare Jedis based on their names. In this case, if the objects being compared already implement Comparable then you can do the comparison using its compareTo method.
public int compareTo(Jedi jedi){
return this.name.compareTo(jedi.getName());
}
It would be simpler in this case.
Now, if you inted to use both name and age as the comparison criteria then you have to decide your oder of comparison, what has precedence. For instance, if two Jedis are named the same, then you can use their age to decide which goes first and which goes second.
public int compareTo(Jedi jedi){
int result = this.name.compareTo(jedi.getName());
if(result == 0){
result = this.age > jedi.age ? 1 : this.age < jedi.age ? -1 : 0;
}
return result;
}
If you had an array of Jedis
Jedi[] jediAcademy = {new Jedi("Obiwan",80), new Jedi("Anakin", 30), ..}
All you have to do is to ask to the class java.util.Arrays to use its sort method.
Arrays.sort(jediAcademy);
This Arrays.sort method will use your compareTo method to sort the objects one by one.
Listen to #milkplusvellocet, I'd recommend you to implement the Comparable interface to your class as well.
Just contributing to the answers of others:
String.compareTo() will tell you how different a string is from another.
e.g. System.out.println( "Test".compareTo("Tesu") ); will print -1
and System.out.println( "Test".compareTo("Tesa") ); will print 19
and nerdy and geeky one-line solution to this task would be:
return this.lastName.equals(s.getLastName()) ? this.lastName.compareTo(s.getLastName()) : this.firstName.compareTo(s.getFirstName());
Explanation:
this.lastName.equals(s.getLastName()) checks whether lastnames are the same or not
this.lastName.compareTo(s.getLastName()) if yes, then returns comparison of last name.
this.firstName.compareTo(s.getFirstName()) if not, returns the comparison of first name.
You're almost all the way there.
Your first few lines, comparing the last name, are right on track. The compareTo() method on string will return a negative number for a string in alphabetical order before, and a positive number for one in alphabetical order after.
Now, you just need to do the same thing for your first name and score.
In other words, if Last Name 1 == Last Name 2, go on a check your first name next. If the first name is the same, check your score next. (Think about nesting your if/then blocks.)
Consider using the Comparator interface described here which uses generics so you can avoid casting Object to Student.
As Eugene Retunsky said, your first part is the correct way to compare Strings. Also if the lastNames are equal I think you meant to compare firstNames, in which case just use compareTo in the same way.
if (s.compareTo(t) > 0) will compare string s to string t and return the int value you want.
public int Compare(Object obj) // creating a method to compare {
Student s = (Student) obj; //creating a student object
// compare last names
return this.lastName.compareTo(s.getLastName());
}
Now just test for a positive negative return from the method as you would have normally.
Cheers
A String is an object in Java.
you could compare like so,
if(this.lastName.compareTo(s.getLastName() == 0)//last names are the same
I wouldn't have an Object type parameter, no point in casting it to Student if we know it will always be type Student.
As for an explanation, "result == 0" will only occur when the last names are identical, at which point we compare the first names and return that value instead.
public int Compare(Object obj)
{
Student student = (Student) obj;
int result = this.getLastName().compareTo( student.getLastName() );
if ( result == 0 )
{
result = this.getFirstName().compareTo( student.getFirstName() );
}
return result;
}
If you using compare To method of the Comparable interface in any class.
This can be used to arrange the string in Lexicographically.
public class Student() implements Comparable<Student>{
public int compareTo(Object obj){
if(this==obj){
return 0;
}
if(obj!=null){
String objName = ((Student)obj).getName();
return this.name.comapreTo.(objName);
}
}
Why is Java Comparable used? Why would someone implement Comparable in a class? What is a real life example where you need to implement comparable?
Here is a real life sample. Note that String also implements Comparable.
class Author implements Comparable<Author>{
String firstName;
String lastName;
#Override
public int compareTo(Author other){
// compareTo should return < 0 if this is supposed to be
// less than other, > 0 if this is supposed to be greater than
// other and 0 if they are supposed to be equal
int last = this.lastName.compareTo(other.lastName);
return last == 0 ? this.firstName.compareTo(other.firstName) : last;
}
}
later..
/**
* List the authors. Sort them by name so it will look good.
*/
public List<Author> listAuthors(){
List<Author> authors = readAuthorsFromFileOrSomething();
Collections.sort(authors);
return authors;
}
/**
* List unique authors. Sort them by name so it will look good.
*/
public SortedSet<Author> listUniqueAuthors(){
List<Author> authors = readAuthorsFromFileOrSomething();
return new TreeSet<Author>(authors);
}
Comparable defines a natural ordering. What this means is that you're defining it when one object should be considered "less than" or "greater than".
Suppose you have a bunch of integers and you want to sort them. That's pretty easy, just put them in a sorted collection, right?
TreeSet<Integer> m = new TreeSet<Integer>();
m.add(1);
m.add(3);
m.add(2);
for (Integer i : m)
... // values will be sorted
But now suppose I have some custom object, where sorting makes sense to me, but is undefined. Let's say, I have data representing districts by zipcode with population density, and I want to sort them by density:
public class District {
String zipcode;
Double populationDensity;
}
Now the easiest way to sort them is to define them with a natural ordering by implementing Comparable, which means there's a standard way these objects are defined to be ordered.:
public class District implements Comparable<District>{
String zipcode;
Double populationDensity;
public int compareTo(District other)
{
return populationDensity.compareTo(other.populationDensity);
}
}
Note that you can do the equivalent thing by defining a comparator. The difference is that the comparator defines the ordering logic outside the object. Maybe in a separate process I need to order the same objects by zipcode - in that case the ordering isn't necessarily a property of the object, or differs from the objects natural ordering. You could use an external comparator to define a custom ordering on integers, for example by sorting them by their alphabetical value.
Basically the ordering logic has to exist somewhere. That can be -
in the object itself, if it's naturally comparable (extends Comparable -e.g. integers)
supplied in an external comparator, as in the example above.
Quoted from the javadoc;
This interface imposes a total
ordering on the objects of each class
that implements it. This ordering is
referred to as the class's natural
ordering, and the class's compareTo
method is referred to as its natural
comparison method.
Lists (and arrays) of objects that
implement this interface can be sorted
automatically by Collections.sort (and
Arrays.sort). Objects that implement
this interface can be used as keys in
a sorted map or as elements in a
sorted set, without the need to
specify a comparator.
Edit: ..and made the important bit bold.
The fact that a class implements Comparable means that you can take two objects from that class and compare them. Some classes, like certain collections (sort function in a collection) that keep objects in order rely on them being comparable (in order to sort you need to know which object is the "biggest" and so forth).
Most of the examples above show how to reuse an existing comparable object in the compareTo function. If you would like to implement your own compareTo when you want to compare two objects of the same class, say an AirlineTicket object that you would like to sort by price(less is ranked first), followed by number of stopover (again, less is ranked first), you would do the following:
class AirlineTicket implements Comparable<Cost>
{
public double cost;
public int stopovers;
public AirlineTicket(double cost, int stopovers)
{
this.cost = cost; this.stopovers = stopovers ;
}
public int compareTo(Cost o)
{
if(this.cost != o.cost)
return Double.compare(this.cost, o.cost); //sorting in ascending order.
if(this.stopovers != o.stopovers)
return this.stopovers - o.stopovers; //again, ascending but swap the two if you want descending
return 0;
}
}
An easy way to implement multiple field comparisons is with Guava's ComparisonChain - then you can say
public int compareTo(Foo that) {
return ComparisonChain.start()
.compare(lastName, that.lastName)
.compare(firstName, that.firstName)
.compare(zipCode, that.zipCode)
.result();
}
instead of
public int compareTo(Person other) {
int cmp = lastName.compareTo(other.lastName);
if (cmp != 0) {
return cmp;
}
cmp = firstName.compareTo(other.firstName);
if (cmp != 0) {
return cmp;
}
return Integer.compare(zipCode, other.zipCode);
}
}
Comparable is used to compare instances of your class. We can compare instances from many ways that is why we need to implement a method compareTo in order to know how (attributes) we want to compare instances.
Dog class:
package test;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Dog d1 = new Dog("brutus");
Dog d2 = new Dog("medor");
Dog d3 = new Dog("ara");
Dog[] dogs = new Dog[3];
dogs[0] = d1;
dogs[1] = d2;
dogs[2] = d3;
for (int i = 0; i < 3; i++) {
System.out.println(dogs[i].getName());
}
/**
* Output:
* brutus
* medor
* ara
*/
Arrays.sort(dogs, Dog.NameComparator);
for (int i = 0; i < 3; i++) {
System.out.println(dogs[i].getName());
}
/**
* Output:
* ara
* medor
* brutus
*/
}
}
Main class:
package test;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Dog d1 = new Dog("brutus");
Dog d2 = new Dog("medor");
Dog d3 = new Dog("ara");
Dog[] dogs = new Dog[3];
dogs[0] = d1;
dogs[1] = d2;
dogs[2] = d3;
for (int i = 0; i < 3; i++) {
System.out.println(dogs[i].getName());
}
/**
* Output:
* brutus
* medor
* ara
*/
Arrays.sort(dogs, Dog.NameComparator);
for (int i = 0; i < 3; i++) {
System.out.println(dogs[i].getName());
}
/**
* Output:
* ara
* medor
* brutus
*/
}
}
Here is a good example how to use comparable in Java:
http://www.onjava.com/pub/a/onjava/2003/03/12/java_comp.html?page=2
For example when you want to have a sorted collection or map
OK, but why not just define a compareTo() method without implementing comparable interface.
For example a class City defined by its name and temperature and
public int compareTo(City theOther)
{
if (this.temperature < theOther.temperature)
return -1;
else if (this.temperature > theOther.temperature)
return 1;
else
return 0;
}
When you implement Comparable interface, you need to implement method compareTo(). You need it to compare objects, in order to use, for example, sorting method of ArrayList class. You need a way to compare your objects to be able to sort them. So you need a custom compareTo() method in your class so you can use it with the ArrayList sort method. The compareTo() method returns -1,0,1.
I have just read an according chapter in Java Head 2.0, I'm still learning.