Am currently trying to sort my object by lastname first then first name then date of birth then ssn. But logically I can only come up with is the lastname then the firstname
any tips?
public int compareTo(Doctor o)
{
int result = this.lastName.compareTo(o.lastName());
return result == 0 ?this.firstName.compareTo(((Doctor) o).firstName()):result;
}
Nested if would be better choice to implement this.
public int compareTo(Doctor o){
int result = this.lastName.compareTo(o.lastName());
if(result==0){
result = this.firstName.compareTo(o.firstName());
if(result==0){
result = this.dob.compareTo(o.dob());
if(result==0){
....
}
}
}
return result;
}
You can use the following:
public int compareTo(Doctor o)
{
int result = this.lastName.compareTo(o.lastName());
if (result != 0)
return result;
result = this.firstName.compareTo(o.firstName());
if (result != 0)
return result;
result = this.birthDate.compareTo(o.birthDate());
if (result != 0)
return result;
return this.ssn.compareTo(o.ssn());
}
First sort on lastname. If the sort value is 0, sort on firstname. If that result is 0, sort for date of birth, and so on. You'll have multiple return statements, sure, but it's a lot more readable.
As you probably know, a result value of 0 means the two values are equal. In your use case, this should result in an additional sort instead of simply returning the values.
Edit: other answers below have provided exact implementation for this.
As you may have realized, this is a kind of the "chain of responsibility". So i would suggest you the pattern for this case. Chain-of-responsibility Pattern
It will save you from writing too many if()...s
Related
This question already has answers here:
How to compare objects by multiple fields
(23 answers)
Closed 3 years ago.
I don't think that's the best way to word that title but I can't think of a better way to word it. Here's my problem: I have to write a method that compares in several different ways. If the last names are the same, I then need to compare by first name. If the first names are the same, then I need to sort by section. What would be the most effective way to sort a data structure in this hierarchy? Here's what I've currently got and I think I understand why it doesn't work but I can't come up with a different way to write this function:
//Student class structure, each field has a public get/set method
public class Student implements Comparable<Student>
{
private String fname;
private String lname;
private int section;
}
//My current compareTo method
#Override
public int compareTo(Student s)
{
/*
-compare by last name
-if the same, compare by first name
-if the same, compare by section
*/
String slast = s.getLastName();
if(lname.compareTo(slast) == 0)
{
String sfirst = s.getFirstName();
if(fname.compareTo(sfirst) == 0)
{
int sclass = s.getSection();
return Integer.compare(section, sclass);
}
else
{
return fname.compareTo(sfirst);
}
}
else
{
return lname.compareTo(slast);
}
}
You can create a Comparator for your Student class this way:
Comparator<Student> comparator = Comparator
.comparing(Student::getLastName)
.thenComparing(Student::getFirstName)
.thenComparing(Student::getSection);
And then use this comparator (instead of implementing Comparable interface) to sort a list with Student objects, or to create a TreeMap with these objects:
Collections.sort(listOfStudents, comparator);
TreeMap<Student> mapOfStudents = new TreeMap<>(comparator);
You don't have to use getters or setters if you're overriding compareTo. You can also forgo the else/return statements since they're terminal return statements, and just use return.
#Override
public int compareTo(Student s) {
if (lname.compareTo(s.lname) == 0) {
if (fname.compareTo(s.fname) == 0) {
return section.compareTo(s.section);
}
return fname.compareTo(s.fname);
}
return lname.compareTo(s.lname);
}
Your code looks correct to me.
What would be the most effective way to sort a data structure in this
hierarchy?
Well, it's worth mentioning that you are potentially doing the first two comparisons (first name and last name) multiple times
if(lname.compareTo(slast) == 0)
{
//...
}
else
{
return lname.compareTo(slast);
}
It should be fairly obvious that you are doing lname.compareTo(slast) twice. You can store the result in a variable instead.
int lastNameComparison = lname.compareTo(slast);
if(lastNameComparison == 0)
{
//...
}
else
{
return lastNameComparison;
}
It is a matter of style, but I would not bother to store the result of getters into variables. Just call them when you need them.
Combining both of the above points, you get:
int lastNameComparison = lname.compareTo(s.getLastName();
if (lastNameComparison == 0)
{
int firstNameComparison = fname.compareTo(s.getFirstName());
if (firstNameComparison == 0)
{
return Integer.compare(section, s.getSection());
}
else
{
return firstNameComparison;
}
}
else
{
return lastNameComparison;
}
The nesting is quite ugly and if we need to add another criteria, it would get even worse.
We can solve that by inverting the conditions and using multiple return statements.
int lastNameComparison = lname.compareTo(s.getLastName());
if (lastNameComparison != 0) return lastNameComparison;
// Last names must be equal
int firstNameComparison = fname.compareTo(s.getFirstName());
if (firstNameComparison != 0) return firstNameComparison;
// First names must be equal
return Integer.compare(section, s.getSection());
I would personally use the declarative style of writing this, but if this code is for an assignment, that is likely not what they are expecting.
Would appreciate comment on whether this is the best/recommended way to parse a pipe-delimited string for particular keys.
In a low-latency system which runs this operation for every request - inefficiency is expensive.
public String extractFields(String key,String comment){
if(comment!=null){
for(String test:comment.split("\\|")){
if(test.contains(key)){
return test.substring(test.indexOf(key)+key.length()).trim();
}
}
}
return null;
}
Hmm, the split and search seems a bit pointless to me.
Why not:
indexOf() of the key to get its location, test if this is valid
If valid, from that location, do indexOf() to the next |
Return a substring of the above region.
Based on Nim's answer, here is an implementation of the faster way to do it:
public String extractField(String key, String comment) {
if (key == null || comment == null)
return null;
int i = comment.indexOf(key);
if (i < 0) // Does not contain the key
return null;
String result = comment.substring(i + 1);
i = result.indexOf('|');
if (i >= 0)
result = result.substring(0, i);
return result;
}
I am a little confused about how to implement comparators/comparable. I am trying to sort a list (currently an ArrayList, but this is can change...) whereby when Collections.Sort is called, it's objects are sorted by a specific integer field in descending order, and if that integer field is equal then by lexicographic order of the name field.
This is my object:
class Movie implements Comparator<Movie>, Comparable<Movie> {
public String _title;
public int _time;
public Movie(String title, int time) {
this._title = title;
this._time = time;
}
public Movie() {
}
#Override
public int compareTo(Movie o) {
return (this._title.compareTo(o._title));
}
#Override
public int compare(Movie arg0, Movie arg1) {
return (arg0._time > arg1._time) ? -1 : (arg0._time == arg1._time) ? 0 : 1;
}
}
At the moment it only sorts via the number fields. How do I go about making it sort via lexicographic order if the number fields are equal?
I am a little confused by the compareTo method. This defines the natural order right? So what does
(this._title.compareTo(o._title))
actually do?
Thank you so much for your help!
EDIT:
I got my desired output by adding an if statement in the compare method and returning "arg0.compareTo(arg1)". However I am still unsure as to the compareTo method (even after reading about on the net) and a simple explanation would be great.
I am a little confused by the compareTo method. This defines the
natural order right? So what does
(this._title.compareTo(o._title))
actually do?
The natural order of Strings is their lexicographical order.
try:
if (arg0._time > arg1._time){
return -1
} else if (arg0._time < arg1._time){
return 1;
} else {
return arg0._title.compareTo(arg1._title);
}
You can use the following pattern to define compareTo with more and more specific conditions:
#Override
public int compareTo(Movie other) {
int result = Integer.compare( this._time, other._time );
if (result == 0) {
result = this._title.compareTo( other._title );
}
if (result == 0) {
// result = compare even more specific field
}
return result;
}
I've been using ArrayList for my project to store a cricket team players and order them.
I started thinking about using a TreeSet because of its advantage of removing duplicates.
However the problem I'm having is that if for example I create the following two players:
P p1 = new P("Jack","Daniel",33(age),180(height),78(weight),41(games played),2300
(runs scored),41(dismisses))
P p2 = new P("Jack","Daniel",37(age),185(height),79(weight),45(games played),2560
(runs scored),45(dismisses))
Notice that the two players have the same first and last name, but everything else is different. When I try to add these two players to the TreeSet, it considers them duplicates because of the names similarities and removes the second one. Obviously I don't want this to happen and I want the Set to remove a player only if everything he has is the same as another player, and not just the first and last names.
Is there a way of achieving this?
Also my TreeSet takes a Player object.
Originally, this answer neglected the fact that a TreeSet does its comparisons based on compareTo(), rather than equals(). Edits have been made to address this.
You need to define equals(), hashCode() and compareTo() for your Player object correctly. (Since it's a TreeSet and not a HashSet, implementing hashCode() isn't so important - but it's good practice.)
Equals and hashCode need to take into account all of the fields. Eclipse can auto-generate one for you that will look similar to this (Source > Generate hashcode and equals).
If you already have a natural sort order that doesn't use all of the fields, then you could supply a custom comparator to your TreeSet. However, even if you really only want to sort by a subset of the fields, there's nothing stopping you sorting by all fields (with the uninteresting fields only playing a part of the interesting parts are identical). The important thing to note here is that a TreeSet determines equality not by the equals() method, but by compareTo() == 0.
Here's an example equals():
#Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Player that = (Player) obj;
return this.age == that.age &&
this.height == that.height &&
this.weight == that.weight &&
this.games == that.games &&
this.runs == that.runs &&
this.dismisses == that.dismisses &&
this.given.equals(that.given) &&
this.family.equals(that.family);
}
And here's hashcode:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.age;
result = prime * result + this.dismisses;
result = prime * result + this.family.hashCode());
result = prime * result + this.games;
result = prime * result + this.given.hashCode());
result = prime * result + this.height;
result = prime * result + this.runs;
result = prime * result + this.weight;
return result;
}
Finally, here's a compareTo:
public int compareTo(Player that)
{
int result;
result = this.family.compareTo(that.family);
if (result != 0) // is the family name different?
{
return result; // yes ... use it to discriminate
}
result = this.given.compareTo(that.given);
if (result != 0) // is the given name different?
{
return result; // yes ... use it to discriminate
}
result = this.age - that.age; // is the age different?
if (result != 0)
{
return result; // yes ... use it to discriminate
}
... (and so on) ...
... with the final one ...
return this.dismisses - that.dismisses; // only thing left to discriminate by
}
a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
From Java Platform Standard Edition 8 Documentation TreeSet part.
class Student implements Comparable<Student> {
String name;
public Student(String name) {
this.name=name;
}
public String toString(){
return name;
}
public int compareTo(Student gStudent) {
if(!this.name.equals(gStudent.getName()))
return 1;
return 0;
}
private String getName() {
return name;
}
}
I want to navigate into a list by identifier.
1- I manage/create a list.
2- I create function to get next item of a identifier element from my list
Can you help me to fix this code?
Prepare the list
List<String> myList = new ArrayList<String>();
myList.add("1");
myList.add("2");
myList.add("3");
myList.add("4");
myList.add("5");
public String function getNext(String uid) {
if (myList.indexOf(uid).hasNext()) {
return myList.indexOf(uid).nextElement();
}
return "";
}
public String function getPrevious(String uid) {
return myList.indexOf(uid).hasPrevious() ? myList.indexOf(uid).previousElement() : "";
}
You could use an index to lookup your String which is faster and simpler however to implement the functions as you have them.
public String getNext(String uid) {
int idx = myList.indexOf(uid);
if (idx < 0 || idx+1 == myList.size()) return "";
return myList.get(idx + 1);
}
public String getPrevious(String uid) {
int idx = myList.indexOf(uid);
if (idx <= 0) return "";
return myList.get(idx - 1);
}
Using a List.get(i) is O(1) which makes keeping the index the fastest option. List.indexOf(String) is O(n). Using a NavigatbleSet might appear attractive as it is O(log n), however the cost of creating an object is so high that the collection has to be fairly large before you would see a benefit. (In which case you would use the first option)
If your elements are not repeated, what you need is a NavigableSet:
http://download.oracle.com/javase/6/docs/api/java/util/NavigableSet.html
The methods higher and lower are what you are looking for.
Lists don't have a nextElement() method. indexOf returns the integer index of the item. You could simply add (or subtract) one to get the next (or previous) item:
public String function getNext(String uid) {
var index = myList.indexOf(uid);
if (index > -1) {
try {
return myList.get(i+1);
} catch ( IndexOutOfBoundsException e) {
// Ignore
}
}
return ""; // consider returning `null`. It's usually a better choice.
}
However looking up an object with indexOf on ArrayList is a very slow process, because it has to check every single entry. There are better ways to this, but that depends on what you are actually trying to achieve.