I have a custom object like this :
Linkedlist<ClassInfo> classes = new LinkedList<ClassInfo>();
Within that, there are accessors for a teacher's name, the class name, the room number, etc. These are all Strings. I have run into a situation where the data in that LinkedList needs to displayed by different parameters (i.e. teacher name, class name, the room number, etc.).
Can anyone supply a quick implementation of how to do this? If I use the Compartor interface, how would I be able tell it which String field to sort the list by? My research also lead me to the Collator, and I was wondering if this would be of use.
Appreciate any help.
Write a different Comparator implementation for each field:
Comparator<ClassInfo> CLASS_NAME_COMPARATOR = new Comparator<ClassInfo>() {
public int compare(ClassInfo class1, ClassInfo class2) {
return class1.getClassName().compareTo(class2.getClassName());
}
};
... // implementations for other fields
...and then sort by whichever comparator is appropriate:
Collections.sort(classes, CLASS_NAME_COMPARATOR);
You will have to provide a custom comparator for every ordering you need to sort your collection to. Eg:
class TeacherComparator implements Comparator<ClassInfo> {
public int compare(ClassInfo c1, ClassInfo c2) {
String teacher1 = c1.getTeacher();
String teacher2 = c2.getTeacher();
return teacher1.compareTo(teacher2);
}
}
class ClassNameComparator implements Comparator<ClassInfo> {
...
}
Related
So, I'm working on a project right now for school with a few people and one of them has committed some code that I'm really having difficulty wrapping my head around. The basis of the project is creating a music library with songs, albums, and playlists. These playlists, in particular, are arraylists of songs need different ways of sorting and thus he's implemented comparator for sorting. He did so using enums, which I understand from the perspective of just instances to represent items. Like
public enum Suit {
SPADES, CLUBS, HEARTS, DIAMONDS
}
to represent different suits of a card. I also have learned you can declare methods alongside enums, which is what it looks like he did. Here is the attribute declaration area and the constructor:
public class Playlist implements Comparator<Song>{
private TotalTime aTotalTime;
private String aName;
private ArrayList<Song> playList;
private Comparator<Song> aComparator;
private enum Method{
SortByTitle(new Comparator<Song> () {
#Override
public int compare(Song o1, Song o2) {
// TODO Auto-generated method stub
return o2.getTitle().compareTo(o1.getTitle());
}
}),
SortByArtist(new Comparator<Song>() {
#Override
public int compare(Song o1, Song o2) {
// TODO Auto-generated method stub
return o2.getExpectedTags().getArtist().compareTo(o1.getExpectedTags().getArtist());
}
}),
SortByLength(new Comparator<Song>() {
#Override
public int compare(Song o1, Song o2) {
// TODO Auto-generated method stub
return o2.getTotalSeconds()-o1.getTotalSeconds();
}
});
private Comparator<Song> comparator;
Method(Comparator<Song> pComparator){
comparator = pComparator;
}
public Comparator<Song> getComparator(){
return this.comparator;
}
}
// constructor that initializes the the playlist.
public Playlist(String pName,Method aMethod) {
aName = new String(pName);
playList = new ArrayList<Song>();
this.aComparator = aMethod.getComparator();
}
}
I can vaguely follow what's going on here as such: We start with the constructor, which calls aMethod.getComparator(), with aMethod being the enum instance, and then aMethod.getComparator() returns the this.comparator object, which itself is declared three lines above as a private Comparator<Song> comparator. From my perspective, it looks like ithis will return the private comparator object every time and not actually change the sorting method of the Comparable interface. Any help parsing all of this would be greatly appreciated.
Your analysis is correct. This class seems strange. Some points which stand out:
Why is Playlist a Comparator of Songs? It may make more sense to allow the playlist to be sorted using a Method instead of passing on construction.
The Method provided has no impact on the order of Songs in the Playlist.
The Method enum probably should not be private.
It may be worth revisiting the scope of the components in the project.
What is a Playlist? Is it a different Playlist if the Song order has changed?
Should it be up to the Playlist to decide how to play the songs in the playlist?
Look only at the enum definition.
The enum definition defines 3 actual enums: SortByTitle, SortByLength, and SortByArtist - those are your SPADE, HEARTS, DIAMONDS, CLUBS; of this enum. For each value, they are initialized with a non-zero-length constructor, and the object passed is a custom impl of a comparator, but forget all that for now.
The enumeration (heh) of enum values then ends. Why? Because semicolon.
But the enum definition doesn't end yet; then we get private Comparator<Song> comparator;.
Note that each individual enum value gets its own copy of this field. Each value of an enum is itself an instance of the 'class' that the enum represents. And the key point here is that this field holds different comparators for SortByArtist, SortByLength, etc.
Therefore, Method.SortByArtist.getComparator(); returns the value of that field for the instance of the Method 'class' (enums are basically classes, with highly limited construction; only one instance per value, so 3 instances here, ever). Which is different from the value of that field for the SortByLength instance.
The rest is just anonymous inner classes.
This is valid java, I think it should be fairly obvious to tell, right?
class StringByLengthComparator implements Comparator<String> {
public int compare(String a, String b) {
return a.length() - b.length();
}
}
...
Comparator<String> c = new StringByLengthComparator();
but we can write that with less characters in java, using the concept 'anonymous inner classes'. This works when you make a class and then intent to use this definition exactly once, and then never use it again. Let's say this 'Comparator c = ...;' line is the only place in the entire code base that you're ever going to mention StringByLengthComparator by name. Then:
Comparator<String> c = new Conmparator<String>() {
public int compare(String a, String b) {
return a.length() - b.length();
}
};
Looks funky, but it means the exact same thing. The one difference is that this class, which still exists, is not named StringByLengthComparator, but it gets a random name you needn't worry about and cannot ever use. That's okay though - after all, this was the only place we were ever gonna use this thing.
Java lambdas.
Note that you can make this even shorter, using lambda syntax:
Comparator<String> c = (a, b) -> a.length() - b.length();
still means the same thing (well, the object you get no longer has presence, which only matters if you do very silly things, such as relying on its object identity hashcode, or lock on the comparator itself, which are all crazy things to do. So if you don't do anything crazy, it's the same thing).
That's what the code is doing. It's no different than first defining 3 classes that each implement Comparable<Song>, and then passing new SongsByLengthComparer() as expression as first argument.
I need to be able to sort an object by multiple conditions, and these sorts need to be configurable from the application.properties file (as in it should be possible to specify in the file the sorts to apply and the order).
So in my design I created a comparator for each one of the sorts, and then an enum with a supplier such as:
public enum CarSort {
ENGINE(CarEngineComparator::new), BRAND(CarBrandComparator::new);
private final Supplier<Comparator<Car>> constructor;
CarSort(Supplier<Comparator<Car>> constructor){
this.constructor = constructor;
}
Comparator<Car> newComparator() {
return constructor.get();
}
}
With this design, I then would be able to load the sorts from a properties file:
myapp.cars.sorts=BRAND,ENGINE
And chain the sorts with something like:
Stream<Comparator<Car>> comparators = sorts.stream().map(CarSort::newComparator);
return comparators
.reduce(Comparator::thenComparing)
.map(comparator -> filteredCars.sorted((comparator.reversed())))
.orElse(filteredCars)
.collect(Collectors.toList());
The problem I have at the moment is that one of the comparators requires two parameters, so I don't think this solution holds anymore, but I can't think of any clean alternatives at the moment.
Do you know if it would be possible to adapt my current design to fit this requirement, or have any alternative viable solution?
I'm going to add more details. Let's say the specific comparator I need to add sorts cars based for example on how far they are from the client. So it could be something like this (details are not important really, only the fact that it needs an additional parameter):
public class CarDistanceComparator implements Comparator<Car> {
private Location origin;
CarDistanceComparator(Location origin) {
this.origin = origin;
}
#Override
public int compare(Car o1, Car o2) {
double distanceToFirstCar = DistanceService.calculateDistance(origin, o1.getLocation());
double distanceToSecondCar = DistanceService.calculateDistance(origin, o2.getLocation());
return Double.compare(distanceToFirstCar, distanceToSecondCar);
}
}
So then what I would like is to be able to sort by BRAND, ENGINE, DISTANCE (as specified in the config file) for multiple customers. Meaning that I would need to pass a different argument each time to the DistanceComparator.
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).
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.
My problem is this; I have to order a table of data. Each row of the table is an object (lets call it TableObject) stored in a List. Each column of data is a property of the class (usually a String).
I have to do the typical ordering of data when the user clicks on any column. So I thought about changing the List to a TreeSet and implementing Comparator in my TableObject.
The problem comes when I try to reorder the TreeSet. The compare is fairly easy at first (cheeking for exceptions in parseInt have been omitted):
public int compare(TableObject to1, TableObject to2){
TableObject t1 = to1;
TableObject t2 = to2;
int result = 1;
if(Integer.parseInt(t1.getId()) == Integer.parseInt(t2.getId())){result=0;}
if(Integer.parseInt(t1.getId()) < Integer.parseInt(t2.getId())){result=-1;}
return result;
}
But when I have to reorder by the text of the data or by other dozens of data that the TableObject has I have a problem.
I do not want to create dozens of compare functions, each for one. I prefer not to use a switch (or a chain of ifs) to decide how to compare the object.
Is there any way to do this in some way (like Reflexive), that doesn't imply that I will write like hundreds of lines of nearly the same code?
Thanks for all!
Bean Comparator should work.
Using reflection the BeanComparator that will allow you to sort on any property that has a zero parameter method that returns the value of the property.
So basically you can sort on any property that has a "getter" method.
What you could do is make the comparator take a String representing the name of the parameter to sort by in its constructor.
Then you could use reflection to sort by the given parameter.
The following code is very dirty. But I think it illustrates the gist of what you would need to do.
public class FieldComparator<T> implements Comparator<T> {
String fieldName;
public FieldComparator(String fieldName){
this.fieldName = fieldName;
}
#Override
public int compare(T o1, T o2) {
Field toCompare = o1.getClass().getField(fieldName);
Object v1 = toCompare.get(o1);
Object v2 = toCompare.get(o2);
if (v1 instanceof Comparable<?> && v2 instanceof Comparable<?>){
Comparable c1 = (Comparable)v1;
Comparable c2 = (Comparable)v2;
return c1.compareTo(c2);
}else{
throw new Exception("Counld not compare by field");
}
}
}
Yes, you could use the reflection API, to get the content of a field based on it's name.
See Field class and especially the Field.get method.
(I wouldn't recommend it though, as reflection is not designed for this type of task.)