Using comparator for several characteristics in java - java

I have a list to be sorted but it cannot be done if values are represented as strings. Example:
to sort: OB123, OB1212, Maintenance, Daily check, OB123
desired result: Daily check, Maintenance, OB123, OB123, OB1212
if values are strings result is: Daily check, Maintenance, OB1212, OB123,OB123
Therefore I need to use comparator to first sort aircraft numbers such OB123 by their carrier(OB), than by their number (123) and sometimes suffix (""). And after that I would like to compare the whole name with all the rest values as "daily check" etc.
So far I can sort only flight Ids:
#Override
public int compareTo(FlightNumberDisplay toCompare) {
int result = _carrier.compareTo(toCompare.getCarrier());
if (result == 0) {
result = _number.compareTo(toCompare.getNumber());
if (result == 0) {
result = _suffix.compareTo(toCompare.getSuffix());
}
}
return result;
}
So since "Daily check" has also carrier+number+suffix representation it is sorted according to it. The question is how to sort them according to their names.

Well, you can make a comparison checking for numbers in the strings:
FlightComparator.java
public class FlightComparator implements Comparator<String> {
public int compare(String arg0, String arg1) {
// both have numbers, compare them
if (containsNumber(arg0) && containsNumber(arg0)) {
Integer i1, i2;
try {
i1 = getNumber(arg0);
} catch (NumberFormatException ex) {
return 1;
}
try {
i2 = getNumber(arg1);
} catch (NumberFormatException ex) {
return -1;
}
return i1.compareTo(i2);
} else {
// no numbers
return arg0.compareTo(arg1);
}
}
private boolean containsNumber(String string) {
return string.matches(".*\\d+.*");
}
private Integer getNumber(String string) throws NumberFormatException {
return Integer.parseInt(string.replaceAll("\\D+",""));
}
}
TEST IT
public static void main(String[] args) {
String[] ss = {"OB123", "OB1212", "Maintenance", "Daily check", "OB123"};
Collections.sort(Arrays.asList(ss), new FlightComparator());
list(ss);
}
private static void list(String[] ss) {
for (String s : ss) {
System.out.println(s);
}
}
OUTPUT
Daily check
Maintenance
OB123
OB123
OB1212
DISCLAIMER
Now, while this data seems correct for what you ask, is not a real answer to your problem. Also if flight letters are different ie, OM1212, OR1212, this will only compare the numbers, so to complete solve your problem now, you can choose between
use this Comparator and compare data as shown (not using attributes)
adapt this Comparator<String> to Comparator<Flight> (best option)
CREDITS
Method getNumber created from this answer
Method containsNumber from this answer

You can extract the carrier information in another List, there you can parse it by Integer using parseInt() and then sort it.
Later you can merge both lists.

Related

How can I compare all the values of an enum with a given array in java?

I have an enum class with values:
enum carBrand{BMW,HONDA,MERC,AUDI};
And there's an array called Sales with Array values:
sales[] = {CHEVVY, BMW , MERC, AUDI};
So how could I check that the sales[] has all the values of enum carBrand?
I'm trying to put it in a for loop as:
for(int i = 0; i<sales.length;i++){
if(carBrand.sales == sales[i]){
return true;
}
return false;
}
Add carBrand values to list
loop sales, remove the carBrand from the list
check if list is empty, if so they have all the values
Note: Class names should be names in PascalCase (CarBrand, Sales)
I would, personally, suggest using a list object rather than an Array where you are using such, however, this should work.
public static boolean checkArray(carBrand[] array) {
for (carBrand c : carBrand.values()) {
boolean found = false;
for (carBrand a : array) {
if (a == c) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
where the "array" parameter would be invoked as the sales object in your code.
This code will return false if not every enum value exists within your array.
Off-topic:
Things like this are actually all over the internet - here, google, even Bing (as garbo as Bing is), so searching before requesting help, probably a viable choice
public class Enumeration {
enum carBrand{BMW,HONDA,MERC,AUDI};
public static void main(String[] args) {
String sales[] = {"CHEVVY", "BMW" , "MERC", "AUDI"};
for(carBrand brand:carBrand.values()) {
boolean bran=false;
for(int i=0;i<sales.length;i++) {
if(brand.toString()==sales[i]) {
bran=true;
break;
}
}
if(!bran==true) {
System.out.println("Sales doesn't have " +brand);
}
}
}
}

Comparable interface with many conditions

The Question is how can use comparable interface and collections.sort to do the sorting with model , production and price. Can i do these three sorting in ascending order within "public int compareto(car other)"?
For example, It will be sorted with model in alphabetical order. If model is same, then sorted with production in alphabetical order. if production is also same , then finally sorted with price in ascending order.
Thank you for attention, i stuck with this problem many days. Please help me.
public static void main(String[] args) {
ArrayList<Car> car = new ArrayList<car>();
// something ignored//
Collections.sort(car); <----------------------Problem
for (Car c : car) {
System.out.println(c);
}
}
class car implements Comparable<car>{
protected String model;
protected String production;
protected int price;
public Tablet(String model ,String production , int price)
{
this.model=model;
this.price=price;
this.production = production;
}
public int compareTo (car other)
{
?????????????????
}
}
class mini-bus extends car
{
private door;
public Tablet(String model ,String production , int price ,int door)
{
super(model , production , price);
this.door = door;
}
}
The principle is quite straightforward:
Compare the first pair of properties. If they are different, return the negative/positive compare value; otherwise...
Compare the second pair of properties. If they are different, return the negative/positive compare value; otherwise...
... (repeat for as many pairs of properties as you have) ...
Compare the last pair of properties. This is the last property, so return the compare value.
For example:
int compareModels = this.model.compareTo(that.model);
if (compareModels != 0) {
return compareModels;
}
int compareProd = this.production.compareTo(that.production);
if (compareProd != 0) {
return compareProd;
}
return Integer.compare(this.price, that.price);
Note that there is also a nice class in Guava called ComparisonChain which reduces a lot of this boilerplate logic:
return ComparisonChain.start()
.compare(this.model, that.model)
.compare(this.production, that.production)
.compare(this.price, that.price)
.result();
This stops comparing once a difference is found between any pair of properties. It will still access the subsequent properties, but that should hopefully be an irrelevantly cheap thing to do anyway.
Here is the general approach to the problem of multi-attribute sorting:
Decide on the ordered list of attributes by which you sort
For each attribute on your list, compare the values on both sides
If the result is not zero, return it right away
If the result is zero, go to the next attribute on your list
If you ran out of attributes, return zero
If the number of attributes is fixed, the "loop" on the ordered list of attributes is unrolled, i.e. each individual attribute is compared separately:
int res;
res = this.getA().compareTo(other.getA()); // e.g. model
if (res != 0) return res;
res = this.getB().compareTo(other.getB()); // e.g. production
if (res != 0) return res;
res = this.getC().compareTo(other.getC());
if (res != 0) return res;
...
// For the last attribute return the result directly
return this.getZ().compareTo(other.getZ()); // e.g. price
This should do:
public int compareTo(Car other){
if(this.getModel().compareTo(other.getModel()) != 0){
return this.getModel().compareTo(other.getModel());
}else if(this.getProduction().compareTo(other.getProduction()) != 0){
return this.getProduction().compareTo(other.getProduction());
}else{
return Integer.compare(this.getPrice(), other.getPrice());
}
}

Sorting an Array List of String[] arrays

I am reading in a .csv file sort of like a spreadsheet in excel. There are a certain number of columns, determined by the file, and I read each line into a string array using the .split(",") method. I then put this into an array list so it can hold all of the string arrays without giving it a specific size. However, when I go to sort the array list using Collections.sort(), the program breaks. What could the problem be? Here is my code to sort:
Collections.sort(stringList, new Comparator < String[] > () {
public int compare(String[] strings, String[] otherStrings) {
return -1 * (strings[sortNum].compareTo(otherStrings[sortNum]));
}
});
Two points:
Don't multiply the result of compare by -1 to reverse a comparison. Integer.MIN_VALUE * -1 is still Integer.MIN_VALUE. Instead, reverse the order of the comparison itself
My guess is that you've actually got some rows without enough columns. Perhaps you should put those at the end?
Something like:
Collections.sort(stringList, new Comparator < String[] > () {
public int compare(String[] x1, String[] x2) {
if (x1.length > sortNum && x2.length > sortNum) {
return x2[sortNum].compareTo(x1[sortNum]);
}
if (x1.length > sortNum) {
return 1;
}
if (x2.length > sortNum) {
return -1;
}
return x2.length - x1.length;
}
});
Alternatively, filter your list first to make absolutely sure that all rows have enough columns.
Well, either strings[sortNum] or otherStrings[sortNum] could be out of bounds. You need to do some checks to prevent that. Also, strings[sortNum] or otherStrings[sortNum] could be null. I bet you're running into one of these 2 things. What does the call stack indicate?
Try using this
First your class comparator with a constructor:
public class MyStringArrayComparator implements Comparator<String[]>{
Integer sortNum;
public MyStringComparator(Integer index) {
sortNum = index;
}
#Override
public int compare(String[] strings, String[] otherStrings) {
return -1*(strings[sortNum].compareTo(otherStrings[sortNum]));
}
}
and in your code
Collections.sort(stringList,new MyStringArrayComparator<String[]>(index));
Hope that works for you
Sharing code in case someone need to do the sort on multiple columns.
public final class ArrayComparatorWithIndex<T extends Comparable<T>> implements Comparator<T[]>
{
private final int[] indexToSort;
public ArrayComparatorWithIndex(int[] indexToSort)
{
if(indexToSort == null || indexToSort.length == 0){
throw new IllegalArgumentException("Index to use for sorting cannot be null or empty.");
}
this.indexToSort = indexToSort;
}
#Override
public int compare(T[] str, T[] otherStr)
{
int result= 0;
for (int index : indexToSort)
{
result= str[index].compareTo(otherStr[index]);
if (result != 0){
break;
}
}
return result;
}
}
//Example how to use it:
int[] indexForSorting= new int[] { 1, 3 };
Collections.sort(stringList, new ArrayComparatorWithIndex<String>(indexForSorting));
I suspect you might have a closure problem in reference to the 'sortNum' variable. See Jon Skeet's closure article for some guidance, even though it deals with closures in C# it should still be relevant. Even if you don't have this issue, it's a good read. :)
you can provide default values for empty "cells":
public int compare(String[] strings, String[] otherStrings) {
String one, other;
one = other = ""; // default value
if (sortNum<strings.length && strings[sortNum] != null) {
one = strings[sortNum];
}
if (sortNum<otherStrings.length && otherStrings[sortNum] != null) {
other = otherStrings[sortNum];
}
return -1 * (one.compareTo(other));
}

How can I compare null values using Comparator?

I've got a few Comparators -- one for Dates, one for decimals, one for percentages, etc.
At first my decimal comparator looked like this:
class NumericComparator implements Comparator<String> {
#Override
public int compare(String s1, String s2) {
final Double i1 = Double.parseDouble(s1);
final Double i2 = Double.parseDouble(s2);
return i1.compareTo(i2);
}
}
Life was simple. Of course, this doesn't handle the case where the strings aren't parseable. So I improved compare():
class NumericComparator implements Comparator<String> {
#Override
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
try {
i1 = Double.parseDouble(s1);
} catch (NumberFormatException e) {
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e2) {
return 0;
}
return -1;
}
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e) {
return 1;
}
return i1.compareTo(i2);
}
}
Life was better. Tests felt more solid. However, my code reviewer pointed out, "What about nulls?"
Great, so now I have to repeat the above with NullPointerException or prepend the method body with:
if (s1 == null) {
if (s2 == null) {
return 0;
} else {
return -1;
}
} else if (s2 == null) {
return 1;
}
This method is huge. The worst part is, I need to repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions while parsing.
I'm not a Java expert. Is there a cleaner, neater solution than -- gasp -- copying and pasting? Should I trade correctness for lack of complexity so as long as it is documented?
Update: Some have suggested that it's not the Comparator's job to handle null values. Since the sort results are displayed to users I indeed want nulls to be sorted consistently.
You are implementing a Comparator<String>. String's methods, including compareTo throw a NullPointerException if a null is handed in to them, so you should too. Similarly, Comparator throws a ClassCastException if the arguments' types prevent them from being compared. I would recommend you implement these inherited behaviors.
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
if(s1 == null)
{
throw new NullPointerException("s1 is null"); // String behavior
}
try {
i1 = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException("s1 incorrect format"); // Comparator behavior
}
if(s2 == null)
{
throw new NullPointerException("s2 is null"); // String behavior
}
try {
i2 = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException("s2 incorrect format"); // Comparator behavior
}
return i1.compareTo(i2);
}
}
You can almost regain the original elegance by extracting a method to do the type checking and conversion.
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
i1 = parseStringAsDouble(s1, "s1");
i2 = parseStringAsDouble(s2, "s2");
return i1.compareTo(i2);
}
private double parseStringAsDouble(String s, String name) {
Double i;
if(s == null) {
throw new NullPointerException(name + " is null"); // String behavior
}
try {
i = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException(name + " incorrect format"); // Comparator behavior
}
return i;
}
}
If you are not particular about the Exception messages, you can lose the "name" parameter. I'm sure you can lose an extra line here or word there by applying little tricks.
You say you need to repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions. It's difficult to offer specifics there without seeing the situation, but you may be able to use "Pull Up Method" on a version of my parseStringAsDouble into a common ancestor of NumericComparator that itself implements java's Comparator.
There are a lot of subjective answers to this question. Here's my own $.02.
First, the trouble you're describing is the canonical symptom of a language that lacks first-class functions, which would enable you to succinctly describe these patterns.
Second, in my opinion, it should be an error to compare two Strings as Doubles if one of them cannot be considered a representation of a double. (The same is true for nulls, etc.) Therefore, you should permit the exceptions to propagate! This will be a contentious opinion, I expect.
Here's how I'd improve the comparator:
First, exctract a method for converting the value. It's being repeated, multiple try...catches are always ugly -> better to have as few of them as possible.
private Double getDouble(String number) {
try {
return Double.parseDouble(number);
} catch(NumberFormatException e) {
return null;
}
}
Next, write down simple rules to show how you want the flow of the comparator to be.
if i1==null && i2!=null return -1
if i1==null && i2==null return 0
if i1!=null && i2==null return 1
if i1!=null && i2!=null return comparison
Finally do horrible obfuscation to the actual comparator to raise a few WTF:s in code review (or like others like to say it, "Implement the Comparator"):
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1 = getDouble(s1);
final Double i2 = getDouble(s2);
return (i1 == null) ? (i2 == null) ? 0 : -1 : (i2 == null) ? 1 : i1.compareTo(i2);
}
private Double getDouble(String number) {
try {
return Double.parseDouble(number);
} catch(NumberFormatException e) {
return null;
}
}
}
...yes, that's a branching nested ternary. If anyone complains about it, say what others here have been saying: Handling nulls isn't Comparator's job.
You could create a utility method that handles parsing and returns a certain value in the case of nulls or parse exceptions.
Take a step back. Where does those Strings come from? For what is this Comparator to be used? Do you have a Collection of Strings which you would like to sort or so?
Try this:
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
Ordering.nullsFirst().onResultOf(
new Function<String, Double>() {
public Double apply(String s) {
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return null;
}
})
The only problem, if it you consider it that, is that null Strings and other non-parseable Strings will all be intermingled. That's probably not a big deal, considering the benefits -- this gives you a comparator that is guaranteed to be correct, whereas with a hand-coded comparator, even relatively simple ones, it's amazing how easy it is to commit a subtle error that breaks transitivity or, umm, antisymmetricity.
http://google-collections.googlecode.com
It seems that there are two concerns being mixed here and maybe should be broken up into separate components. Consider the following:
public class ParsingComparator implements Comparator<String> {
private Parser parser;
public int compare(String s1, String s2) {
Object c1 = parser.parse(s1);
Object c2 = parser.parse(s2);
new CompareToBuilder().append(c1, c2).toComparison();
}
}
The Parser interface would have implementations for numbers, dates, etc. You could potentially use the java.text.Format class for your Parser interface. If you don't want to use commons-lang, you could replace the use of CompareToBuilder with some logic to handle nulls and use Comparable instead of Object for c1 and c2.
tl;dr: Take guidance from the JDK. The Double comparator is not defined for either non-numbers or nulls. Make people give you useful data (Doubles, Dates, Dinosaurs, whatever) and write your comparators for that.
As near as I can tell, this is a case of user input validation. For example, if you are taking input from a dialog box, the correct place to ensure that you have a parseable String that is a Double, Date or whatever is in the input handler. Make sure it's good before the user can tab away, hit "Okay" or equivalent.
Here's why I think this:
First question: if the Strings aren't parseable as numbers, I think you're trying to solve the problem in the wrong place. Say, for instance, I try to compare "1.0" to "Two". The second is clearly not parseable as a Double but is it less than the first? Or is it greater. I would argue that the users should have to turn their Strings into Doubles before they ask your which is greater (which you can easily answer with Double.compareTo, for instance).
Second question: if the Strings are "1.0" and null, which is greater? The JDK source doesn't handle NullPointerExceptions in the Comparator: if you give it a null, autoboxing will fail.
The worst part is, I need to repeat
this pattern with three other classes
which compare different types of
strings and could raise three other
exceptions while parsing.
Exactly why I would argue that the parsing should happen outside your Comparator with exception-handling dealt with before it arrives at your code.
If you are able to change the signature I would suggest you write the method so that it can accept any supported Object.
public int compare(Object o1, Object o2) throws ClassNotFoundException {
String[] supportedClasses = {"String", "Double", "Integer"};
String j = "java.lang.";
for(String s : supportedClasses){
if(Class.forName(j+s).isInstance(o1) && Class.forName(j+s).isInstance(o1)){
// compare apples to apples
return ((Comparable)o1).compareTo((Comparable)o2);
}
}
throw new ClassNotFoundException("Not a supported Class");
}
You might even define it recursively where you cast your Strings to Doubles and then return the result of calling itself with those objects.
IMHO you should first create a method that returns a Double from a String, embedding the null and parsing failure cases (but you must define what to do in such cases : throw an exception ? return a default value ??).
Then your comparator just have to compare obtained Double instances.
In other words, refactoring...
But I still wonder why you need to compare strings though expecting they represent doubles. I mean, what prevents you from manipulating doubles in the code that would actually use this comparator ?
according to your needs and Ewan's post, I think there's a way to extract the structure that you can reuse:
class NumericComparator implements Comparator<String> {
private SafeAdaptor<Double> doubleAdaptor = new SafeAdaptor<Double>(){
public Double parse(String s) {
return Double.parseDouble(s);
}
};
public int compare(String s1, String s2) {
final Double i1 =doubleAdaptor.getValue(s1, "s1");
final Double i2 = doubleAdaptor.getValue(s2, "s2");
return i1.compareTo(i2);
}
}
abstract class SafeAdaptor<T>{
public abstract T parse(String s);
public T getValue(String str, String name) {
T i;
if (str == null) {
throw new NullPointerException(name + " is null"); // String
}
try {
i = parse(str);
} catch (NumberFormatException e) {
throw new ClassCastException(name + " incorrect format"); // Comparator
}
return i;
}
}
I extract the method as an abstract class which can be reuse in other cases(although the class name is suck).
cheers.
So I improved compare()...
sure you did.
first, the Comparator interface doesn't specify what happens with nulls. if your null checking if statement works for your use case, that's great, but the general solution is throwing an npe.
as to cleaner... why final? why all the catch/throws? why use compareTo for a primitive wrapper?
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) throws NullPointerException, NumberFormatException {
double test = Double.parseDouble(s1) - Double.parseDouble(s2);
int retVal = 0;
if (test < 0) retVal = -1;
else if (test > 0) retVal = 1;
return retVal;
}
}
seems you might find it clearer renaming test to t1 and retVal to q.
as to repeating the pattern... eh. you might be able to use generics with reflection to invoke appropriate parseX methods. seems like that'd not be worth it though.

How to sort a LinkedHashMap by its value class's field?

I use the following lines to sort a LinkedHashMap, but not all items are sorted, anything wrong ?
LinkedHashMap<String,PatternData> statisticsMap;
// fill in the map ...
LinkedHashMap<String,PatternData> sortedStatisticsMap=new LinkedHashMap<String,PatternData>(); // Sort it by patternData's average
ArrayList<PatternData> statisticsMapValues=new ArrayList<PatternData>(statisticsMap.values());
Collections.sort(statisticsMapValues,Collections.reverseOrder()); // Sorting it (in reverse order)
patternData last_i=null;
for (PatternData i : statisticsMapValues) // Now, for each value
{
if (last_i==i) continue; // Without dublicates
last_i=i;
for (String s : statisticsMap.keySet()) // Get all hash keys
if (statisticsMap.get(s)==i) // Which have this value
{
sortedStatisticsMap.put(s,i);
}
}
class PatternData implements Comparable<PatternData>
{
float sum=0,average;
int totalCount=0;
Vector<String> records=new Vector<String>();
public PatternData() { }
public void add(float data)
{
sum+=data;
totalCount++;
average=sum/totalCount;
}
public void add(float data,String record)
{
add(data);
records.add(record);
}
float getAverage() { return average; }
public int compareTo(patternData o) { return (int)(average-o.average); }
}
When you return int, the range when average-o.average is between -1 and 1 will always return 0.
One solution is simply change your compareTo function to:
return Float.compare(average, o.average);
You're sorting floating point numbers using integers. Integers don't get rounded; they get truncated. Also, given the way you're actually doing the sorting, consider using a TreeHashMap instead.
(and just to nitpick, Java convention uses lowercase for method and variables names)

Categories

Resources