I am attempting to sort an ArrayList based on the value of a long present within each object. After following various examples around the internet, I have come up with the following code but it is not sorting as desired (it seems to truncate parts of the object).
public static Comparator<Customer> compareSIN =
new Comparator<Customer>() {
public int compare(Customer cust1, Customer other) {
String sin1 = "" + cust1.sin;
String sin2 = "" + other.sin;
return sin1.compareTo(sin2);
}
};
Please advise me on what I am doing missing in the first snippet of code that is preventing me from sorting the objects properly.
Thanks!
From the title I assume Customer.sin is a long - and the problem is you are trying to compare them as Strings rather then by their numeric value.
(Example: 10000 is lexicographically smaller then 2 - so using Strings here is the fault)
You should use Long.compare() (Assuming java 7):
public static Comparator<Customer> compareSIN =
new Comparator<Customer>() {
public int compare(Customer cust1, Customer other) {
return Long.compare(cust1.sin,other.sin);
}
};
You do not actually need to use a compareTo() method inside your own compareTo() method.
The compare to states that it must return 0 if they are equal and negative or positive numbers for non equality.
For this reason you can compare two longs by returning the one subtracted from the other.
public int compare(Customer cust1, Customer other) {
return cust1.sin - other.sin;
}
This will as you can see, return 0 if they are equal, negative if other.sin is greater than cust1.sin and positive if cust1.sin is greater than other.sin.
You compare Strings instead of longs.
So, imagine you want to compare : "10" and "5", outcome would be "10" < "5" whereas thinking that you're working with long, you expect to get 10 > 5 ...
That can explain your issue.
Related
I am working with Java and I am looking for a data structure that stores objects and sorts them based on one of the properties(Integer type) of object. For example, if objects have different properties and one of these properties is called number of type Integer. I would like to sort all the objects based on the property number. I am also looking for the time complexities to be as follows:
Insertion in log(n) time.
Deletion in log(n) time.
Get all the objects that have the number property in a certain range in log(n) time. The range has a min value and a max value. I would like to get all the objects that have the number property falling in this range and would like the time complexity to be log(n) in worst case scenario.
I believe TreeSet data structure in Java could be a good candidate to store these objects. I can use to custom comparator to sort the objects based on the number property. But the problem is this number property could be the same for multiple objects and I would like to store all of them. Initially I had a custom comparator like the following. But with this comparator, object with the same number property were getting overwritten.
class Helper implements Comparator<NumberWrapper> {
public int compare(NumberWrapper num1, NumberWrapper num2)
{
if(num1.number == num2.number){
return 0;
}
else if (num1.number > num2.number)
return 1;
else
return -1;
}
}
So, I removed the following code in the comparator so that objects with same number property cannot be overwritten.
if(num1.number == num2.number){
return 0;
}
Now, the overall code looks like the following
import java.util.*;
public class Test {
public static void main(String[] args) {
TreeSet<NumberWrapper> set = new TreeSet<NumberWrapper>(new Helper());
set.add(new NumberWrapper(2));
set.add(new NumberWrapper(3));
set.add(new NumberWrapper(2));
set.add(new NumberWrapper(1));
// Subset to hold all objects which have the "number" property greater than or equal
// to 2 and less than 3. (Basically all 2s)
TreeSet<NumberWrapper> subSet = (TreeSet<NumberWrapper>) set.subSet(new
NumberWrapper(2),new NumberWrapper(3));
// Output here will 2 , 2,
for (NumberWrapper value : subSet){
System.out.print(value.number + " ,");
}
}
}
class NumberWrapper {
Integer number;
NumberWrapper(Integer number){
this.number = number;
}
}
class Helper implements Comparator<NumberWrapper> {
public int compare(NumberWrapper num1, NumberWrapper num2)
{
if (num1.number > num2.number)
return 1;
else
return -1;
}
}
This code seems to be working as expected for me. All the objects with same number property are not getting overwritten. But I have a couple of questions
I am not sure if the compare method I wrote for comparator is not buggy. Will there be a chance that some of the objects with same number property will not be returned when I try to get all the objects that have the number property in a certain range?
Is there any other data structure I should be considering for my needs?
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);
}
}
I read that the rule for the return value of these methods is that for obj1.compareTo(obj2) for example, if obj2 is under obj1 in the hierarchy, the return value is negative and if it's on top of obj1, then it's positive (and if it's equal then it's 0). However, in my class I saw examples where Math.signum was used in order to get -1 (for negative) and 1 (for positive) in the compareTo method.
Is there any reason for that?
EDIT:
Here is the code I meant:
Comparator comp = new Comparator() {
public int compare(Object obj1, Object obj2) {
Book book1 = (Book) obj1;
Book book2 = (Book) obj2;
int order = book1.getAuthor().compareTo(book2.getAuthor());
if (order == 0) {
order = (int) Math.signum(book1.getPrice() - book2.getPrice());
}
return order;
};
Is there any reason for using Math.signum
Yes there is.
order = (int) Math.signum(book1.getPrice() - book2.getPrice());
Suppose you have replace the above line with this
order = (int)(book1.getPrice() - book2.getPrice());
Now let us assume
book1.getPrice() returns 10.50
book2.getPrice() returns 10.40
If you do not use signum you will never have any compile time or run time error but value of order will be 0. This implies that book1 is equals to book2 which is logically false.
But if you use signum value of order will be 1 which implies book1 > book2.
But it must be mentioned that you should never make any assumption about compare function returning value between 1 and -1.
You can read official document for comparator http://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html.
Any negative number will do to show that a < b. And any positive number will show that a > b. -1 and 1 serve that purpose just fine. There's no sense of being "more less than" or "more greater than"; they are binary attributes. The reason that any negative (or positive) value is permitted is probably historical; for integers it's common to implement the comparator by simple subtraction.
No.
PS: Frequent error in implementation is to use subtraction
public int compareTo(Object o) {
OurClass other = (OurClass)o; //Skip type check
return this.intField - other.intField;
}
It is wrong because if you call new OurClass(Integer.MIN_VALUE).compareTo(new OurClass(Integer.MAX_VALUE)) you get overflow. Probably Math.abs is attempt (failed) to deal with this problem.
The only reason I can see is that if you want to compare two ints for example (a and b), and you write
return a - b;
it might overflow. If you convert them to doubles and use (int)Math.signum( (double)a - (double)b ), you will definitely avoid that. But there are simpler ways of achieving the same effect, Integer.compare( a, b) for example.
Am somewhat confused with Java's compareTo() and Collections.sort() behavior.
I am supposed to sort a column in ascending order using compareTo() & Collections.sort().
My criteria is (if the same number occurs than please sort the next available column).
(1) Document Number
(2) Posting Date
(3) Transaction Date
(4) Transaction Reference Number Comparison
Here's the code (which is executed in a calling method) that implements the Collection.sort() method:
public int compareTo(CreditCardTransactionDetail t) {
int comparison = 0;
int documentNumberComparison = this.getDocumentNumber().compareTo(t.getDocumentNumber());
if (documentNumberComparison != 0) {
comparison = documentNumberComparison;
}
else {
int postingDateComparison = this.getTransactionPostingDate().compareTo(t.getTransactionPostingDate());
if (postingDateComparison != 0) {
comparison = postingDateComparison;
}
else {
int transactionDateComparison = this.getTransactionDate().compareTo(t.getTransactionDate());
if (transactionDateComparison != 0) {
comparison = transactionDateComparison;
}
else {
int transactionRefNumberComparison = this.getTransactionReferenceNumber().compareTo(t.getTransactionReferenceNumber());
LOG.info("\n\n\t\ttransactionRefNumberComparison = " + transactionRefNumberComparison + "\n\n");
if (transactionRefNumberComparison != 0) {
comparison = transactionRefNumberComparison;
}
}
}
return comparison;
}
Question(s):
(1) Am I doing the right thing? When a comparison = 0, it returns as -2. Is this correct behavior because I always thought it to be between -1,0,1.
(2) Should I be using the comparator?
Happy programming...
To address your specific questions:
Yes, that looks fine. The result does not have to be -1, 0 or 1. Your code could be slightly less verbose, though, and just return as soon as it finds a result without using the comparison variable at all.
If you're implementing Comparable, no need to deal with a Comparator. It's for when you need to compare something that isn't Comparable or need to compare in a different way.
Guava's ComparisonChain class makes a compareTo method like this incredibly easy:
public int compareTo(CreditCardTransactionDetail o) {
return ComparisonChain.start()
.compare(getDocumentNumber(), o.getDocumentNumber())
.compare(getTransactionPostingDate(), o.getTransactionPostingDate())
.compare(getTransactionDate(), o.getTransactionDate())
.compare(getTransactionReferenceNumber(), o.getTransactionReferenceNumber())
.result();
}
Answer for (1): It's correct. see javadoc of Comparator.compare(T, T): "a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second."
Or use Google Guava which encapsulates Comparator for easier and powerful usage:
//Write 4 Comparators for each comparable field
Ordering ordering = Ordering.from(comparatorDocumentNumber)
.compound(comparatorTransactionPostingDate)
.compound(comparatorTransactionDate)
.compound(comparatorTransactionReferenceNumber);
Collections.sort(list, ordering);
It decouples each Comparator, it's easy to change/ add/ remove fields order.
EDIT: see ColinD's lighter solution.
Your compareTo is reasonable enough. compareTo can return values other than -1,0,1. Just negative, 0 and positive.
You should be using a comparator.
According to the Comparable Documentation, compareTo():
Returns a negative integer, zero, or a positive integer as this object
is less than, equal to, or greater than the specified object.
So -2 is a valid result.
That's a matter of preference, really. Personally I prefer using a Comparator, but compareTo() works just as well. In either case, your code would look pretty much the same.
How do I change my code so that it lists the elements in alphabetical order from a to z. Right now it's ordering from z to a. I can't figure it out and am stuck :-/
String sName1 = ((Address)o).getSurname().toLowerCase();
String sName2 = (this.surname).toLowerCase();
int result = (sName1).compareTo(sName2);
return result;
Thanks :)
Just swap the objects you're comparing.
int result = (sName2).compareTo(sName1);
Usually, for ascending sorting you'd like the lefthand side be part of this object and the righthand side be part of the other object. For descending sorting you just swap the both sides (which you thus initially did).
To make your code more intuitive, I'd however rather swap the assignments of sName1 and sName2:
String sName1 = (this.surname).toLowerCase();
String sName2 = ((Address)o).getSurname().toLowerCase();
int result = (sName1).compareTo(sName2);
And get rid of the hungarian notation and unnecessary parentheses as well. The following sums it:
public class Address implements Comparable<Address> {
private String surname;
// ...
public int compareTo(Address other) {
return this.surname.toLowerCase().compareTo(other.surname.toLowerCase());
}
}
You may want to add nullchecks if you can't guarantee that surname is never null.
See also:
Object Ordering tutorial at Sun.com
Different ways to sort a list of Javabeans