Implement configurable sort criterias - java

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.

Related

Hierarchical data structure for configuration parameters

I'm writing a setup routine with several configuration parameters (for now, could be int, double, or String). In order to avoid long method signatures, I'd like to create a data structure to store these parameters. The main setup routine calls several methods/factories, each with their own set of required parameters, and I'd like to only pass the relevant piece of the data structure to each routine.
My initial thought was to use some kind of recursive/tree dictionary-like structure, where String keys could be associated with either a value, or a subtree that behaves exactly like the top-level tree.
A rough example of what I'm trying to do follows:
public class A {
// some common methods here
}
public class A1 extends A {
public A1(int x, double y) {
// do stuff
}
}
public class A2 extends A {
public A2(double z) {
// do stuff
}
}
public A SetupA(ConfigOptions opts) {
if (opts.get("typeA").equals("A1")) {
return SetupA1(opts.get("A1opts"));
} else {
return SetupA2(opts.get("A2opts"));
}
}
public A1 SetupA1(ConfigOptions A1opts) {
return new A1(A1opts.get("x"), A1opts.get("y"));
}
public A2 SetupA2(ConfigOptions A2opts) {
return new A2(A2opts.get("z"));
}
When I fill the ConfigOptions structure, I'd like to have a character, e.g. :, that separates levels of the tree. I.e. for the example above, I could specify that I want an A2 object with z = 1.0 something like this:
opts.set("typeA", "A2");
opts.set("A2opts:z", 1.0);
I know that what I've just laid out won't work as written since it's not type-safe. What modifications to the interface, and/or what implementation techniques could I consider that would allow me to accomplish my objectives?
Notes:
The setup routine has a relatively small one-time cost. Therefore readability and a convenient interface is a much higher priority for me than speed.
This is a learning project for me, so I'm looking for a solution that doesn't depend on external libraries or built-in collection classes.
Error checking does not need to be considered by the data structure, since it will be handled by the routines that receive the parameters.

Ordering objects in java *without* using values

I want to create a class of objects to compare to each other, without using values to compare them with. Is there a library in Java which is able to provide this functionality for me? In terms of ordering, the most frequently mentioned library is Comparator, but all the examples I have seen so far use some kinds of value from the objects in order to perform this ordering with.
For example, I want to be able to say that within a class of objects that:
Object A is more important than Object B.
Object B is more important than Object C.
Therefore, I want the library to be able to perform some kind of analysis, and to be able to order the items according to these values, and say to me, that the order of the values above are A, B, C, in that order.
Is there a library which is able to do this in Java?
Are you thinking of something like this?
enum Importance {
High,
Medium,
Low;
}
class Thing implements Comparable<Thing> {
private Importance importance = Importance.Medium;
public Importance getImportance() {
return importance;
}
public void setImportance(Importance importance) {
this.importance = importance;
}
#Override
public int compareTo(Thing o) {
return importance.compareTo(o.importance);
}
}
Alternatively - if you want to control the relativity of each object then record that in a Map. You will need to be careful to tightly control the map to ensure there are no cycles - if there is then your sorting will become unstable.
static Map<Thing, Set<Thing>> moreImportant = new HashMap<>();
class Thing implements Comparable<Thing> {
#Override
public int compareTo(Thing o) {
Set<Thing> more = moreImportant.get(this);
return more == null ? 0 : more.contains(o) ? 1 : -1;
}
}

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).

Compare LinkedList by multiple strings in Java

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> {
...
}

OO pattern for dividing a collection into groups

I have a list of MyObjects which I need to divide into three groups:
Known good (keep)
Known bad (reject)
Unrecognized (raise alert)
MyObject contains various properties which must be examined to determine which of the 3 groups to put the object in.
My initial implementation (Java) just takes a List in its constructor and does the triage there. Pseudocode:
class MyObjectFilterer {
public MyObjectFilterer(List<MyObject> list) {
// triage items here
}
public List<MyObject> getGood() {
// return sub-list of good items
}
public List<MyObject> getBad() {
// return sub-list of bad items
}
public List<MyObject> getUnrecognized() {
// return sub-list of unrecognized items
}
}
Any issues with this implementation? Is there a better OO choice?
I would probably prefer a static factory method to do the filtering, that then calls a private constructor that takes the three filtered lists, following the good code practice of never doing any serious work in a constructor. Other than that, this looks fine.
There may be multiple approachs. If the problem is generic / repetitive enough, you could define an interface with a method to classify the objects.
interface Selector {
public boolean isGood(MyObject myObject);
public boolean isBad(MyObject myObject);
public boolean isUnknown(MyObject myObject);
}
That way you could change the logic implementation easily.
An other idea would be using the Chain of responsibility.
Your MyObjectFilterer contains a reference to three Objects GoodFilterer, BadFilterer and UnrecognizedFilterer. Each of them contains the following methods: addMethod(MyObject object), getObjects() and addFilter(). Of course they have to implement an interface Filterer.
With the addFilter method you can build the chain. so that the GoodFilterer contains a reference to the BadFilterer and this one contains a reference to the UnrecognizedFilterer
Now you go through your list of MyObjects and call the add method on the GoodFilterer (first one in this chain). Inside the add method you decide if this is good, than you keep it and finish the work, if not pass it on to the BadFilterer.
You keep your three methods for getting the good/bad and unrecognized, but you will pass this to the getObjects() method of the corresponding Filterer
The Benefit is that the logic if this is a good/bad or Unrecognized one is now seperated.
The Downside you would need 3 new classes and 1 Interface.
But like i said, this is just an other idea what you could do.
You should simplify as it's possible. Just make static method in MyObjectFilter with following signature:
public static List filterMyObjects(List data, Group group).
Group is enumeration with three values and it can be used as attribute of MyObject class
I might try something like:
enum MyObjectStatus {
GOOD, BAD, UNRECOGNIZED;
}
class MyObjectFilterer {
private MyObjectStatus getStatus(MyObject obj) {
// classify logic here, returns appropriate enum value
}
// ListMultimap return type below is from Google Guava
public ListMultimap<MyObjectStatus, MyObject> classify(List<MyObject> objects) {
ListMultimap<MyObjectStatus, MyObject> map = ArrayListMultimap.create();
for(MyObject obj: objects) {
map.put(getStatus(obj), obj);
}
}
}
Call classify() to get a Multimap, and extract each category as needed with something like:
List<MyObject> good = map.get(GOOD);
List<MyObject> bad = map.get(BAD);
List<MyObject> unknown = map.get(UNRECOGNIZED);
A nice thing about this solution is you don't have to create/publish accessor methods for each category (unless you want to), and if new categories are created, you also don't add new accessors -- just the new enum and the additional classifier logic.

Categories