Refactoring generic compareTo method - java

What am I doing:
I have a container class named Os, that can contains different type elements and also instances of class Os. When I compare this class, I want to see :
shallow equals for elements
deep equals for Os elements
I have ensured, that every single element contained in class:
Can not be null.
Is comparable to same type elements.
Is immutable. Well, at least part that I'm checking.
Following is what I have at the moment.
Example:
For example, this test case will pass.
Os o1 = Os.of(3, 4d, Os.of("-"));
Os o2 = Os.of(Os.of(Character.toString('-')), 4.0, new Integer(3));
assertEquals(o1.toString(), "[3, 4.0, [-]]");
assertEquals(o2.toString(), "[[-], 4.0, 3]");
assertTrue(o1.reverse().compareTo(o2) == 0);
Code example:
compareTo method:
#Override
public int compareTo(final Os that) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
int subresult = 0;
Comparable<?> othis;
Comparable<?> othat;
if (that == null)
return AFTER;
if (this == that)
return EQUAL;
subresult = ((Integer) this.o.size()).compareTo(that.o.size());
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
try {
for (int i = 0; i < this.o.size(); i++) {
othis = this.o.get(i);
othat = that.o.get(i);
if (othis.getClass() == othat.getClass()) {
if (othat instanceof Os) {
subresult = ((Os) othis).compareTo(((Os) othat));
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
} else {
subresult = hackCMP(othis, othat);
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
}
} else {
subresult = othis.getClass().getName()
.compareTo(othat.getClass().getName());
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
}
}
return EQUAL;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return BEFORE;
}
private static int hackCMP(Object val, Object val2)
throws SecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
Method m = val.getClass().getMethod("compareTo", val.getClass());
return (Integer) m.invoke(val, val2);
}
Question:
I would like to refactor the code.
For example:
I would prefer not using hackCMP method, if possible.
Following code segment seems to repeat itself a lot. Can I replace it with something?
subresult = <expression>;
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
//else ...
What can I refactor and how to do it?
Edit:
#wolfcastle : Data is stored in private final ImmutableList<Comparable<?>> o;.
I'd like to mention, that every answer was useful. Following seems to work:
#Override
public int compareTo(final Os that) {
Ordering<Iterable<Comparable<?>>> order = //
Ordering.natural().<Comparable<?>> lexicographical();
int result = -1;
try {
result = ComparisonChain.start()
.compare(this.o.size(), that.o.size())
.compare(this.o, that.o, order).result();
} catch (Exception e) { //ignore: type mismatch
}
return result;
}

One option I would consider would be storing the elements in a class that allows them to be compared by class rather than by their compareTo method if they aren't the same class:
private static class Element implements Comparable<Element> {
// raw Comparable allows you to call compareTo
private final Comparable comparable;
Element(Comparable comparable) {
this.comparable = comparable;
}
#Override #SuppressWarnings("unchecked")
public int compareTo(Element o) {
Comparable other = o.comparable;
if(comparable.getClass().isInstance(other)) {
return comparable.compareTo(other);
}
return comparable.getClass().getName().compareTo(other.getClass().getName());
}
#Override
public boolean equals(Object obj) {
return obj instanceof Element && comparable.equals(((Element) obj).comparable);
}
#Override
public int hashCode() {
return comparable.hashCode();
}
#Override
public String toString() {
return comparable.toString();
}
}
Then, with your internal list being a List<Element>, your compareTo method in Os could be pretty simple. Using Guava, it could be extremely simple:
#Override
public int compareTo(Os o) {
return ComparisonChain.start()
.compare(list.size(), o.list.size())
.compare(list, o.list, Ordering.natural().<Element>lexicographical())
.result();
}

You could have a method that returned BEFORE | AFTER | INDETERMINATE (say), then call it.
result = newMethod(subresult);
if (result != INDETERMINATE) return result;
That's not much of an improvement, and it still needs to be duplicated everywhere, but it's a little tighter.

Since the generic type of the List<Comparable<?>> o property is not fixed, I'd get rid of the generic type and rely on the raw type. It costs one #SuppressWarnings("rawtypes"), but it minimizes a lot.
#Override
#SuppressWarnings("rawtypes")
public int compareTo(final Os that) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
if (that == null)
return AFTER;
if (this == that)
return EQUAL;
int subresult = ((Integer) this.o.size()).compareTo(that.o.size());
if (subresult != EQUAL)
return subresult;
for (int i = 0; i < this.o.size(); i++) {
Comparable othis = this.o.get(i);
Comparable othat = that.o.get(i);
subresult = othis.compareTo(othat);
if (subresult != EQUAL)
return subresult;
}
return EQUAL;
}

Related

compareTo with objects returns a false while it is true

I am trying to check whether my levelorder of my Binary Search Tree is equal to the other one. To do this, I tried to make a compareTo method. I only give equal values to the method, but it keeps on saying the condition is false. When I place breakpoints, I see that the values are still equal. I am probably not understanding it correctly. Does anyone know how to solve this?
Here is what I did, as you can see below, the compareTo returns a 1 instead of a 0:
import edu.princeton.cs.algs4.BST;
import java.util.*;
public class MyBST implements Comparable<MyBST>{
private Object e;
public MyBST(Object e){
this.e = e;
}
private Object getE(){
return e;
}
public static void main(String[] args) {
int size = 4;
Random r = new Random();
Set<Integer> tes = new LinkedHashSet<>(size);
Stack<Integer> stack = new Stack<>();
while (tes.size() < size) {
tes.add(r.nextInt(10));
}
System.out.println("possible combinations");
Set<Stack<Integer>> combos = combos(tes, stack, tes.size());
Object[] arr = combos.toArray();
List<String> d = new ArrayList<>();
for (Object s : arr) {
String b = s.toString();
b = b.replaceAll("\\[", "").replaceAll("\\]", "");
d.add(b);
}
int index = 0;
do {
BST<String, Integer> bst1 = new BST<String, Integer>();
BST<String, Integer> bst2 = new BST<String, Integer>();
String key1 = d.get(index);
String key2 = d.get(index);
key1 = key1.replaceAll(" ", "");
String[] m = key1.split(",");
key2 = key2.replaceAll(" ", "");
String[] n = key2.split(",");
System.out.println("1e order");
for (int j = 0; j < m.length; j++) {
System.out.println(m[j]);
bst1.put(m[j], 0);
}
System.out.println("2e order");
for (int j = 0; j < n.length; j++) {
System.out.println(n[j]);
bst2.put(n[j], 0);
}
System.out.println("levelorder 1e BST");
MyBST e = new MyBST(bst1.levelOrder());
MyBST y = new MyBST(bst2.levelOrder());
System.out.println(bst1.levelOrder());
System.out.println("levelorder 2e BST");
System.out.println(bst2.levelOrder());
System.out.println(e.compareTo(y) + "\n");
index++;
} while (index < arr.length - 1);
}
public static Set<Stack<Integer>> combos(Set<Integer> items, Stack<Integer> stack, int size) {
Set<Stack<Integer>> set = new HashSet<>();
if (stack.size() == size) {
set.add((Stack) stack.clone());
}
Integer[] itemz = items.toArray(new Integer[0]);
for (Integer i : itemz) {
stack.push(i);
items.remove(i);
set.addAll(combos(items, stack, size));
items.add(stack.pop());
}
return set;
}
#Override
public int compareTo(MyBST o) {
if (this.e == o.e) {
return 0;
}
else
return 1;
}
}
Here you can find the BST.java class: BST.java
And the output is something like:
The breakpoint at the compareTo method says:
When you're using the == operator you're actually checking to see if the references point to the same object in memory. From your debugging screenshot you can see that they are not. this.e points to object Queue#817 while o.e points to Queue#819.
If all you want to do is test for equality, then just override equals and hashCode. You can do it like this (rest of class omitted):
public class MyBST {
private Object e;
public MyBST(Object e) {
this.e = e;
}
public Object getE(){
return e;
}
#Override
public int hashCode() {
return Objects.hashCode(e);
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MyBST))
return false;
MyBST me = (MyBST) obj;
if (e == null) {
if (me.e != null)
return false;
} else if (!e.equals(me.e))
return false;
return true;
}
}
Implementing Comparable is more involved since you need to check for less, equal, or greater than other instances of MyBST. Unfortunately, the only field in MyBST is an Object which does not tell you anything about its actual fields. So without specific fields with which to test you need to ensure that the Object you pass also implements Comparable. Then you can declare your class like this. Rest of class omitted.
It simply says that
MyBST is comparable.
And the object that is passed in the constructor is comparable.
class MyBST<T extends Comparable<? super T>> implements Comparable<MyBST<T>>{
private T e;
public MyBST(T e){
this.e = e;
}
public T getE(){
return e;
}
#Override
public int compareTo(MyBST<T> o) {
return e.compareTo(o.e);
}
}
The other alternative is to simply pass the actual object type and store it as such, not as Object. Then just implement Comparable in MyBST and use the appropriate fields of the passed object. Lets say the object was an Apple object, you could do this.
class Apple {
String type;
int weight;
}
class MyBST implements Comparable<MyBST> {
private Apple apple;
public MyBST(Apple apple) {
this.apple = apple;
}
#Override
public int compareTo(MyBST e) {
// this could be different depending on how you wanted
// to compare one apple to another. This comparison favors
// type over weight.
// check type - String class implements comparable
int ret = apple.type.compareTo(e.apple.type);
if (ret != 0) {
return ret;
}
// same type so check weight
if (apple.weight < e.apple.weight) {
return -1;
}
if (apple.weight > e.apple.weight) {
return 1;
}
return 0; // equals apples based on criteria
}
}
Finally, you have this.
private Object getE(){
return e;
}
A private getter is not usually very useful. Make it public.

Find array if it is subset of another array

This function should return true only if the parameter object is a subset of the calling object but it always returns true. How to fix it?
public boolean contains(FileCollection other) {
int i = 0;
int j = 0;
for (i = 0; i<other.files.length; i++) {
for (j = 0; j<this.files.length; j++) {
if ((other.files[i]).equals((this.files[j]))) //this refers to the equals method defined in File class
break;
}
if (j==this.files.length)
return false;
}
return true;//this method is in FileCollection class
}
(Since you didn't explicitly express what the data type of the array elements is, I'll assume it's File, inferred from comments.)
If you don't mind converting between data structures, maybe converting your arrays (temporarily) to Collections is the most simple way. For example, converting to List:
/* #param other
* #return true if the calling object contains
* all files in the parameter object, false otherwise
*/
public boolean contains(FileCollection other) {
List<File> myList = Arrays.asList(this.files);
List<File> otherList = Arrays.asList(other.files);
return myList.containsAll(otherList);
}
Based on your clarify of what to be considered as "contains" when duplicated items are allowed, I'd say you need to count the number of existence for each element. Here is how:
Based on the answer of #Eritrean , you can get and store the count to a map. I made modifications to check the count too:
public boolean contains(FileCollection other) {
Map<File,Integer> otherFrequency = Arrays.stream(other.files)
.collect(Collectors.toMap(Function.identity(), v->1,Integer::sum));
Map<File,Integer> thisFrequency = Arrays.stream(this.files)
.collect(Collectors.toMap(Function.identity(), v->1,Integer::sum));
if (thisFrequency.entrySet().containsAll(otherFrequency).entrySet()) {
for (File entry : otherFrequency.entrySet()) {
if (thisFrequency.get(entry) < otherFrequency.get(entry))
return false;
}
return true;
}
return false;
}
For other.files contains this.files to hold, every this.file must be in other.files.
for (int j = 0; j < this.files.length; j++) {
boolean found = false;
for (int i = 0; i < other.files.length; i++) {
if (other.files[i].equals(this.files[j])) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
Not knowing the class of files, probably you can do:
for (String file : this.files) {
boolean found = false;
for (String otherFile : other.files) {
if (otherFile.equals(file)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
Or even
for (String file : this.files) {
boolean found = other.files.indexOf(file) != -1;
if (!found) {
return false;
}
}
return true;
There are nicer datastructures that speed things up, and have predefined methods for things like contains.
With duplicates
Comparator<File> comparator = new Comparator<File>() {
#Override
public int compare(File lhs, File rhs) {
int cmp = lhs.getBase().compareIgnoreCase(rhs.getBase());
if (cmp == 0) {
cmp = lhs.getExtension().compareIgnoreCase(rhs.getExtension());
}
if (cmp == 0) {
cmp = Long.compare(lhs.getSize(), rhs.getSize());
}
return cmp;
}
};
Arrays.sort(this.files, comparator);
Arrays.sort(other.files, comparator);
int otherI = 0;
for (File file : this.files.length) {
boolean found = false;
while (otherI < other.files.length) {
int comparison = comparator.compare(other.files[otherI], file);
++otherI;
if (comparison >= 0) {
found = comparison == 0;
break;
}
}
if (!found) {
return false;
}
}
return true;
By sorting both arrays you can synchronize the comparison at locations in both arrays. The above handles duplicates.
Apart from the #renyuneyun suggestion to convert your arrays into Lists you could also make use of the String contains method
public boolean contains(FileCollection other) {
String myList = Arrays.toString(this.files);
String otherList = Arrays.toString(other.files);
return myList.contains(otherList);
}
Of course both of these suggestions are not the optimum solutions from the complexity point of view, but are for sure the shortests :)
What about using a map with File as key and frequency as value:
public boolean contains(FileCollection other) {
Map<File,Integer> otherFrequency = Arrays.stream(other.files)
.collect(Collectors.toMap(Function.identity(), v->1,Integer::sum));
Map<File,Integer> thisFrequency = Arrays.stream(this.files)
.collect(Collectors.toMap(Function.identity(), v->1,Integer::sum));
return thisFrequency.entrySet().containsAll(otherFrequency.entrySet());
}
Only this answer works for me: (Credit to #Joop Eggen for the Comparator part)
public boolean contains(FileCollection other) {
Comparator<File> comparator = new Comparator<File>() {
#Override
public int compare(File lhs, File rhs) {
int cmp = lhs.getBase().compareToIgnoreCase(rhs.getBase());
if (cmp == 0) {
cmp = lhs.getExtension().compareToIgnoreCase(rhs.getExtension());
}
if (cmp == 0) {
cmp = Long.compare(lhs.getSize(), rhs.getSize());
}
if (cmp == 0) {
cmp = Long.compare(lhs.getPermissions(), rhs.getPermissions());
}
return cmp;
}
};
Arrays.sort(this.files, comparator);
Arrays.sort(other.files, comparator); //THIS AND THE COMPARATOR SORT THE ARRAYS BASED ON ALL FILE ATTRIBUTES
int i = 0;
int j = 0;
if (this.files.length<other.files.length)
return false;
while (i<other.files.length && j<this.files.length) {
if (!(this.files[j].equals(other.files[i])))
j++;
else {
j++;
i++;
}
}
if (i<other.files.length)
return false;
else
return true;
}

Inspect a NullPointerException cause

I have an ArrayList of a certain class which i try to sort, However i get a NullPointerException during the sort.
I wrapped my command with a try-catch in order to find which element in the array causes the exception.
How can i inspect the catch in order to figure out which is the problematic element?
Following is the code:
List<SingleMeasurementValuePoint> sortedList = new ArrayList<SingleMeasurementValuePoint>(deviceMeasurementPoints);
try {
Collections.sort(sortedList, new TimeAndComponentSort());
} catch (Exception e) {
System.out.println();
}
The code within the comparator, i.e TimeAndComponentSort is:
public class TimeAndComponentSort implements Comparator<SingleMeasurementValuePoint> {
#Override
public int compare(SingleMeasurementValuePoint point1, SingleMeasurementValuePoint point2) {
int val = point1.compareTo(point2);
if (val == 0) {
return point1.getComponentId().compareTo(point2.getComponentId());
}
else {
return val;
}
}
}
I don't think you can look at a stack trace and determine which element in a List was null. If you have null elements in your List, the easiest workaround is probably to fix your Comparator to handle null(s). Also, you could use the Comparator to log the null(s). Basically, something like
#Override
public int compare(SingleMeasurementValuePoint point1,
SingleMeasurementValuePoint point2) {
if (point1 == null && point2 == null) {
System.out.println("null point1 and point2");
return 0;
} else if (point1 == null) {
System.out.println("null point1");
return -1;
} else if (point2 == null) {
System.out.println("null point2");
return 1;
}
int val = point1.compareTo(point2);
if (val == 0) {
return point1.getComponentId().compareTo(
point2.getComponentId());
} else {
return val;
}
}
That still won't tell you which element at what original index was null. If that's what you really need then you could write a method to return the index of the first null (or -1) like
public static <T> int findFirstNull(List<T> al) {
for (int i = 0, len = al.size(); i < len; i++) {
if (al.get(i) == null) {
return i;
}
}
return -1;
}
Finally, your catch block(s) should log their Exception(s)
} catch (Exception e) {
// System.out.println();
e.printStackTrace();
}

Null pointer Exception in CompareTo method

Structure of my class:
public class Priorityy implement Comparable {
public int compareTo(Object pe) {
Priorityy p = (Priorityy) pe;
if (this.key < p.key) {
return 1;
} else if (this.key > p.key) {
return -1;
} else {
return 0;
}
}
}
Th problem is that p.key is always null, why exactly is that? I have my array initialized with elements in it but it always throws NullPointerException whenever I try Arrays.sort(arr).
How can I fix this?
Edit: Here is the complete code and print did print the elements of array arr:
import java.util.Arrays;
class Priorityy implements Comparable {
int size;
int front = 0;
int rear = 0;
static Priorityy[] arr = new Priorityy[3];
int key;
String value;
public Priorityy(int key, String value) {
this.key = key;
this.value = value;
insert();
}
public void insert() {
arr[front] = this;
System.out.println(arr[front].value);
while (front + 1 != 3) {
front = front + 1;
}
}
public Priorityy remove() {
Priorityy x = arr[front];
front = front - 1;
return x;
}
public int compareTo(Object pe) {
Priorityy p = (Priorityy) pe;
if (this.key < p.key) {
System.out.println(p.key);
return 1;
} else if (this.key > p.key) {
System.out.println("3");
return -1;
} else {
System.out.println("4");
return 0;
}
}
public static void main(String... s) {
new Priorityy(10, "Watch");
new Priorityy(40, "Laptop");
new Priorityy(60, "Wallet");
Arrays.sort(arr);
for (Priorityy element : arr) {
System.out.println(element.key);
System.out.println(element.value);
}
}
}
As per your code
Priorityy p = (Priorityy)pe;
^^ ---------- this is null
You have null object in the array. Handle null object gracefully.
For example
if(pe instanceof Priorityy){ // return false for null object
// your code goes here
}
Better use Generic Comparable and use Integer.compare(int,int) to compare two int values.
class Priorityy implements Comparable<Priorityy> {
public int compareTo(Priorityy pe) {
if (pe != null) {
return Integer.compare(this.key, pe.key);
} else {
// return what ever if pe is null
}
}
}
You're putting things into your array in a really strange manner.
But given that, the problem is that you're not using a static field to store the next position to insert an element into, so the next time you create an instance of Priorityy, the field first contains the value zero again. So you're inserting all three objects into element zero of the array.
Change one line of your code and it will work:
int front = 0;
To:
static int front = 0;
I don't see where you are using size and rear but you probably want these to be static too.
One other suggestion: Java has a nice short syntax for increasing or decreasing the value of a variable by one using the ++ or -- operator, so you can shorten things by saying:
front++;
instead of
front = front + 1;
(and front-- instead of front = front - 1)

Sorting throws java.lang.IllegalArgumentException: Comparison method violates its general contract

I have this method that contains two comparators for sorting items by price.
This works fine 90% of the time but sometimes it throws the exception in the title. Anyone know why
void sort(
Collections.sort(masterList, new Comparator<Item>() {
#Override
public int compare(Item o1, Item o2) {
try {
if (o1.getPriceLevel() == 999 && o1.getPriceLevel() < o2.getPriceLevel()) {
return 1;
}
if (o2.getPriceLevel() == 999) {
return -1;
}
return Double.compare(o1.getPriceLevel(), o2.getPriceLevel());
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
});
//Null pointer happens on the line below
Collections.sort(masterList, Collections.reverseOrder(new Comparator<Item>() {
#Override
public int compare(Item o1, Item o2) {
try {
if (o1.getPriceLevel() == 999 || o2.getPriceLevel() == 999) {
return 1;
}
return Double.compare(o1.getPriceLevel(), o2.getPriceLevel());
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}));
}
edit: stacktrace
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:743)
at java.util.TimSort.mergeAt(TimSort.java:479)
at java.util.TimSort.mergeCollapse(TimSort.java:406)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2038)
at java.util.Collections.sort(Collections.java:1891)
This is a check in the TimSort algorithm used by Java7 - which validates that your comparator method is valid ( e.g. symetric- a == b && b == a )
your equals case is invalid (returning 0) - it depends on the order the arguments are given if one element is null. Try to handle null values explicitly before doing anything else e.g.:
if(o1 == null || o2 == null)
{
return 0;
}

Categories

Resources