I need to perform structural comparison on two Object[] arrays which may contain themselves:
Object[] o1 = new Object[] { "A", null };
o1[1] = o1;
Object[] o2 = new Object[] { "A", null };
o2[1] = o2;
Arrays.deepEquals(o1, o2); // undefined behavior
Unfortunately, the deepEquals doesn't work in this case. The example above should yield true.
Is there an algorithm which can reliably calculate this?
My idea is roughly as follows:
List<Object> xs = new ArrayList<>();
List<Object> ys = new ArrayList<>();
boolean equal(Object[] o1, Object[] o2, List<Object> xs, List<Object> ys) {
xs.add(o1);
ys.add(o2);
boolean result = true;
for (int i = 0; i < o1.length; i++) {
if (o1[i] instanceof Object[]) {
int idx1 = xs.lastIndexOf(o1[i]);
if (idx1 >= 0) { idx1 = xs.size() - idx1 - 1; }
if (o2[i] instanceof Object[]) {
int idx2 = xs.lastIndexOf(o2[i]);
if (idx2 >= 0) { idx2 = ys.size() - idx2 - 1; }
if (idx1 == idx2) {
if (idx1 >= 0) {
continue;
}
if (!equal(o1[i], o2[i], xs, ys)) {
result = false;
break;
}
}
}
}
}
xs.removeLast();
ys.removeLast();
return result;
}
As I mentioned in my comments above, your code has some compile errors, and you've left out a lot of it, which makes it hard to be 100% sure of exactly how it's supposed to work once the code is completed. But after finishing the code, fixing one clear typo (you wrote idx2 = xs.lastIndexOf(o2[i]), but I'm sure you meant idx2 = ys.lastIndexOf(o2[i])) and one thing that I think is a typo (I don't think that you meant for if (!equal(o1[i], o2[i], xs, ys)) to be nested inside if (idx1 == idx2)), removing some no-op code, and restructuring a bit (to a style that I find clearer; YMMV), I get this:
boolean equal(final Object[] o1, final Object[] o2)
{
return _equal(o1, o2, new ArrayList<Object>(), new ArrayList<Object>());
}
private static boolean _equal(final Object[] o1, final Object[] o2,
final List<Object> xs, final List<Object> ys)
{
if(o1.length != o2.length)
return false;
xs.add(o1);
ys.add(o2);
try
{
for(int i = 0; i < o1.length; i++)
{
if(o1[i] == null && o2[i] == null)
continue;
if(o1[i] == null || o2[i] == null)
return false;
if(o1[i].equals(o2[i]))
continue;
if(! (o1[i] instanceof Object[]) || ! (o2[i] instanceof Object[]))
return false;
final int idx1 = xs.lastIndexOf(o1[i]);
if(idx1 >= 0 && idx1 == ys.lastIndexOf(o2[i]))
continue;
if(! _equal((Object[])o1[i], (Object[])o2[i], xs, ys))
return false;
}
return true;
}
finally
{
xs.remove(xs.size() - 1);
ys.remove(ys.size() - 1);
}
}
which mostly works. The logic is, whenever it gets two Object[]s, it checks to see if if it's currently comparing each of them higher up in the stack and, if so, it checks to see if the topmost stack-frame that's comparing one of them is also the topmost stack-frame that's comparing the other. (That is the logic you intended, right?)
The only serious bug I can see is in this sort of situation:
// a one-element array that directly contains itself:
final Object[] a = { null }; a[0] = a;
// a one-element array that contains itself via another one-element array:
final Object[][] b = { { null } }; b[0][0] = b;
// should return true (right?); instead, overflows the stack:
equal(a, b, new ArrayList<Object>(), new ArrayList<Object>());
You see, in the above, the last element of xs will always be a, but the last element of ys will alternate between b and b[0]. In each recursive call, xs.lastIndexOf(a) will always be the greatest index of xs, while ys.lastIndexOf(b) or ys.lastIndexOf(b[0]) (whichever one is needed) will always be one less than the greatest index of ys.
The problem is, the logic shouldn't be, "the topmost comparison of o1[i] is in the same stack-frame as the topmost comparison of o2[i]"; rather, it should be, "there exists some stack-frame — any stack-frame at all — that is comparing o1[i] to o2[i]". But for efficiency, we can actually use the logic "there is, or has ever been, a stack-frame that is/was comparing o1[i] to o2[i]"; and we can use a Set of pairs of arrays rather than two Lists of arrays. To that end, I wrote this:
private static boolean equal(final Object[] a1, final Object[] a2)
{
return _equal(a1, a2, new HashSet<ArrayPair>());
}
private static boolean _equal
(final Object[] a1, final Object[] a2, final Set<ArrayPair> pairs)
{
if(a1 == a2)
return true;
if(a1.length != a2.length)
return false;
if(! pairs.add(new ArrayPair(a1, a2)))
{
// If we're here, then pairs already contained {a1,a2}. This means
// either that we've previously compared a1 and a2 and found them to
// be equal (in which case we obviously want to return true), or
// that we're currently comparing them somewhere higher in the
// stack and haven't *yet* found them to be unequal (in which case
// we still want to return true: if it turns out that they're
// unequal because of some later difference we haven't reached yet,
// that's fine, because the comparison higher in the stack will
// still find that).
return true;
}
for(int i = 0; i < a1.length; ++i)
{
if(a1[i] == a2[i])
continue;
if(a1[i] == null || a2[i] == null)
return false;
if(a1[i].equals(a2[i]))
continue;
if(! (a1[i] instanceof Object[]) || ! (a2[i] instanceof Object[]))
return false;
if(! _equal((Object[]) a1[i], (Object[]) a2[i], pairs))
return false;
}
return true;
}
private static final class ArrayPair
{
private final Object[] a1;
private final Object[] a2;
public ArrayPair(final Object[] a1, final Object[] a2)
{
if(a1 == null || a2 == null)
throw new NullPointerException();
this.a1 = a1;
this.a2 = a2;
}
#Override
public boolean equals(final Object that)
{
if(that instanceof ArrayPair)
if(a1 == ((ArrayPair)that).a1)
return a2 == ((ArrayPair)that).a2;
else
if(a1 == ((ArrayPair)that).a2)
return a2 == ((ArrayPair)that).a1;
else
return false;
else
return false;
}
#Override
public int hashCode()
{ return a1.hashCode() + a2.hashCode(); }
}
It should be clear that the above cannot result in infinite recursion, because if the program has a finite number of arrays, then it has a finite number of pairs of arrays, and only one stack-frame at a time can be comparing a given pair of arrays (since, once a pair begins to be getting compared, it's added to pairs, and any future attempt to compare that pair will immediately return true), which means that the total stack depth is finite at any given time. (Of course, if the number of arrays is huge, then the above can still overflow the stack; the recursion is bounded, but so is the maximum stack size. I'd recommend, actually, that the for-loop be split into two for-loops, one after the other: the first time, skip all the elements that are arrays, and the second time, skip all the elements that aren't. This can avoid expensive comparisons in many cases.)
It should also be clear that the above will never return false when it should return true, since it only returns false when it finds an actual difference.
Lastly, I think it should be clear that the above will never return true when it should return false, since for every pair of objects, one full loop is always made over all the elements. This part is trickier to prove, but in essence, we've defined structural equality in such a way that two arrays are only structurally unequal if we can find some difference between them; and the above code does eventually examine every element of every array it encounters, so if there were a findable difference, it would find it.
Notes:
I didn't worry about arrays of primitives, int[] and double[] and so on. Adam's answer raises the possibility that you would want these to be compared elementwise as well; if that's needed, it's easily added (since it wouldn't require recursion: arrays of primitives can't contain arrays), but the above code just uses Object.equals(Object) for them, which means reference-equality.
The above code assumes that Object.equals(Object) implements a symmetric relation, as its contract specifies. In reality, however, that contract is not always fulfilled; for example, new java.util.Date(0L).equals(new java.sql.Timestamp(0L)) is true, while new java.sql.Timestamp(0L).equals(new java.util.Date(0L)) is false. If order matters for your purposes — if you want equal(new Object[]{java.util.Date(0L)}, new Object[]{java.sql.Timestamp(0L)}) to be true and equal(new Object[]{java.sql.Timestamp(0L)}, new Object[]{java.util.Date(0L)}) to be false — then you'll want to change ArrayPair.equals(Object), and probably ArrayPair.hashCode() as well, to care about which array is which.
You could add all visited objects to a temporary Map<Object, Object> structure to make sure, that you do not visit/inspect them again. The value is always a new Object, which will be used to replace already visited instances in your result lists.
Every time you see an object,
Check, if the map contains the instance
if not, put it to the map, the map value is a new Object
if yes, use the map value (the unique, new Object) in your list (xs or ys)
In your example, the result lists should look like this (pseudo language):
xs == {o1, "A", obj2} // obj2 == map.get(o2);
ys == {o2, "A", obj1} // obj1 == map.get(o1);
This will prevent from infinite loops.
I wrote this helper function to flatten each of the arrays and replaced its own reference with a string (or any primitive) for each level. The map will use a key-value store to keep track of the existing array objects (new at initialization). Assuming you have only arrays and comparable objects in the original array, you can compare the two flattened lists (with the same start level) without having to worry about infinite loops. This comparison should be linear.
private List<Object> flatten(Object[] array, Map<Object, String> map, int level) {
List<Object> list = new ArrayList<>();
for (Object o : array) {
if (o instanceof Object[]) {
if (map.get(o) != null) {
list.add(map.get(o));
} else {
map.put(array, "level"+level);
List<Object> flattened = flatten((Object[]) o, map, level+1);
for (Object obj : flattened)
list.add(obj);
}
} else {
list.add(o);
}
}
return list;
}
hope this helps.
I think I've managed it, at least works in all test cases I've tried so far. Please can someone confirm if my logic is OK. Needed to use the lesser-know IdentityHashMap to keep track of previously visited nodes by reference.
Not sure how to deal with nested arrays of other primitives like int[] etc. I added case for int[], but there are float, double, byte, short, long, boolean to add.
public static boolean deepEquals(Object [] o1, Object [] o2) {
return deepEquals(o1, o2, new IdentityHashMap<Object, Integer>());
}
public static boolean deepEquals(Object o1, Object o2, IdentityHashMap<Object, Integer> visited) {
if (! visited.containsKey(o1)) {
visited.put(o1, 0);
} else {
visited.put(o1, visited.get(o1) + 1);
}
if (! visited.containsKey(o2)) {
visited.put(o2, 0);
} else {
visited.put(o2, visited.get(o2) + 1);
}
boolean ret = false;
if (o1 == o2) {
ret = true;
} else if (o1 instanceof Object[] && o2 instanceof Object[]){
Object [] a1 = (Object[]) o1;
Object [] a2 = (Object[]) o2;
if (a1.length != a2.length ) {
ret = false; // different length, can't be equal
} else if (visited.get(o1) > 0 || visited.get(o2) > 0) {
ret = true; // been here before, stop searching
} else {
ret = true;
for (int i = 0; i < a1.length; i++) {
if (! deepEquals(a1[i], a2[i], visited)) {
ret = false;
break;
}
}
}
} else if (o1 instanceof int[] && o2 instanceof int[]){
ret = Arrays.equals((int[])o1, (int[])o2);
} else if (o1 == null && o2 == null){
ret = true; // null = null?
} else {
ret = o1.equals(o2); // just use equals
}
return ret;
}
Related
Can someone explain me in simple terms, why does this code throw an exception, "Comparison method violates its general contract!", and how do I fix it?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
Your comparator is not transitive.
Let A be the parent of B, and B be the parent of C. Since A > B and B > C, then it must be the case that A > C. However, if your comparator is invoked on A and C, it would return zero, meaning A == C. This violates the contract and hence throws the exception.
It's rather nice of the library to detect this and let you know, rather than behave erratically.
One way to satisfy the transitivity requirement in compareParents() is to traverse the getParent() chain instead of only looking at the immediate ancestor.
Just because this is what I got when I Googled this error, my problem was that I had
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
the value >= other.value should (obviously) actually be value > other.value so that you can actually return 0 with equal objects.
The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects. For example, you might want to perform a string compare and force empty strings to sort to the end with:
if ( one.length() == 0 ) {
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
But this overlooks the case where BOTH one and two are empty - and in that case, the wrong value is returned (1 instead of 0 to show a match), and the comparator reports that as a violation. It should have been written as:
if ( one.length() == 0 ) {
if ( two.length() == 0 ) {
return 0; // BOth empty - so indicate
}
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
Even if your compareTo is holds transitivity in theory, sometimes subtle bugs mess things up... such as floating point arithmetic error. It happened to me. this was my code:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
else
return 0;
}
The transitive property clearly holds, but for some reason I was getting the IllegalArgumentException. And it turns out that due to tiny errors in floating point arithmetic, the round-off errors where causing the transitive property to break where they shouldn't! So I rewrote the code to consider really tiny differences 0, and it worked:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if ((this.tfidf - compareTfidf.tfidf) < .000000001)
return 0;
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
return 0;
}
Editing VM Configuration worked for me.
-Djava.util.Arrays.useLegacyMergeSort=true
In our case were were getting this error because we had accidentally flipped the order of comparison of s1 and s2. So watch out for that. It was obviously way more complicated than the following but this is an illustration:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
In my case I was doing something like the following:
if (a.someField == null) {
return 1;
}
if (b.someField == null) {
return -1;
}
if (a.someField.equals(b.someField)) {
return a.someOtherField.compareTo(b.someOtherField);
}
return a.someField.compareTo(b.someField);
What I forgot to check was when both a.someField and b.someField are null.
I've seen this happen in a piece of code where the often recurring check for null values was performed:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
Java does not check consistency in a strict sense, only notifies you if it runs into serious trouble. Also it does not give you much information from the error.
I was puzzled with what's happening in my sorter and made a strict consistencyChecker, maybe this will help you:
/**
* #param dailyReports
* #param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* #param o1
* #param o2
*/
#Override
public void pair(T o1, T o2) {
final int diff = comparator.compare(o1, o2);
if (diff < Compare.EQUAL) {
checkConsistency(objectMapSmallerOnes, o1, o2);
getListSafely(objectMapSmallerOnes, o2).add(o1);
} else if (Compare.EQUAL < diff) {
checkConsistency(objectMapSmallerOnes, o2, o1);
getListSafely(objectMapSmallerOnes, o1).add(o2);
} else {
throw new IllegalStateException("Equals not expected?");
}
}
});
}
/**
* #param objectMapSmallerOnes
* #param o1
* #param o2
*/
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> smallerThan = objectMapSmallerOnes.get(o1);
if (smallerThan != null) {
for (final T o : smallerThan) {
if (o == o2) {
throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* #param keyMapValues
* #param key
* #param <Key>
* #param <Value>
* #return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* #author Oku
*
* #param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* #param o1
* #param o2
*/
void pair(T o1, T o2);
}
/**
*
* Iterates through each distinct unordered pair formed by the elements of a given iterator
*
* #param it
* #param callback
*/
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
#Override
public Iterator<T> iterator() {
return it;
}
});
for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
callback.pair(list.get(outerIndex), list.get(innerIndex));
}
}
}
If compareParents(s1, s2) == -1 then compareParents(s2, s1) == 1 is expected. With your code it's not always true.
Specifically if s1.getParent() == s2 && s2.getParent() == s1.
It's just one of the possible problems.
In my case, it was an infinite sort.
That is, at first the line moved up according to the condition, and then the same line moved down to the same place.
I added one more condition at the end that unambiguously established the order of the lines.
You can't compare object data like this:s1.getParent() == s2 - this will compare the object references. You should override equals function for Foo class and then compare them like this s1.getParent().equals(s2)
I faced the same issue and I solved it.
//This this your code
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
The violation is comparing different things with each other.
//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2
In my code, I wanted to sort addresses by their coordination. In the comparator, I compared between X and Y (by mistake), instead of X and X.
//My code:
private void sortBasedOnX(){
//addresses is a list of addresses where each address has X and Y
addresses.sort((o1, o2) -> {
String a = o1.getAddress().getX();
String b = o2.getAddress().getY(); //<-- this is supposed to be getX
return Integer.parseInt(a)-Integer.parseInt(b);
});
}
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1
I am trying to add objects into a Treeset but the objects not all are getting added.
class Fruits
{
String name ;
int weight;
int price;
Fruits(String n, int w, int p)
{
this.name=n;
this.weight=w;
this.price =p;
}
#Override
public int hashCode() {
System.out.println("hashcode called");
int prime =31;
int result =1;
result = prime*result +(this.name.hashCode()+this.price+this.weight);
return result;
}
#Override
public boolean equals(Object obj) {
System.out.println("Equals called");
if(null!=obj)
{
Fruits f= (Fruits) obj;
if(this.name.equals(f.name) && this.price==f.price && this.weight == f.price)
{
return true;
}
}
return false;
}
}
class FruitsComparator implements Comparator<Fruits>
{
//Order by Name, then quanity and then Price
#Override
public int compare(Fruits f1, Fruits f2)
{
if(f1.name.equals(f2.name) && f1.weight == f2.weight && f1.price == f2.price)
{
System.out.println(1);
return 0;
}
else if(f1.name.equals(f2.name) && f1.weight==f2.weight && f1.price < f2.price)
{
System.out.println(2);
return -1;
}
else if (f1.name.equals(f2.name) && f1.weight==f2.weight && f1.price > f2.price)
{
System.out.println(3);
return 1;
}
else if (f1.name.equals(f2.name) && f1.weight<f2.weight && f1.price == f2.price)
{
System.out.println(4);
return -1;
}
else if (f1.name.equals(f2.name) && f1.weight>f2.weight && f1.price == f2.price)
{
System.out.println(5);
return 1;
}
else if (f1.name.compareTo(f2.name) <1 && f1.weight==f2.weight && f1.price == f2.price)
{
System.out.println(6);
return -1;
}
else if (f1.name.compareTo(f2.name) >1 && f1.weight==f2.weight && f1.price == f2.price)
{
System.out.println(7);
return 1;
}
return 0;
}
}
From public static void main of another class.
Fruits f1= new Fruits("Apple",1,3);
Fruits f2= new Fruits("Apple",10,1);
Fruits f3= new Fruits("Apple",15,2);
Set<Fruits> sf = new TreeSet<Fruits>(new FruitsComparator());
sf.add(f1);
sf.add(f2);
sf.add(f3);
System.out.println("--Fruits Example--");
for( Fruits f: sf)
{
System.out.println(f.name+"-"+f.weight+"-"+f.price);
}
The output I get is :
--Fruits Example--
Apple-1-3
But when I have fruits objs as below i get the all the objects
just keeping everything same but the third element.
Fruits f1= new Fruits("Apple",1,3);
Fruits f2= new Fruits("Apple",1,1);
Fruits f3= new Fruits("Apple",1,2);
The output get for this is
--Fruits Example--
Apple-1-1
Apple-1-2
Apple-1-3
So somehow my objects are treated as same when I keep different elements on weight and price. I couldn't figure out as why the objects are treated as same. Please help.
The primary issue is, you are always checking two fields to be equal and only one to be different.
At the final else, that happens if at least 2 fields are different, you return 0 which means they should be treated as equal, and that is the reason you have this issue.
Since the order you want is to first sort by name, then by quantity and then by price, remove the && f1.price == f2.price from the 4th condition onwards, and remove && f1.weight==f2.weight on the last two.
You can avoid this issue completely if you use Java 8 style.
Set<Fruits> sf = new TreeSet<Fruits>(Comparator.comparing(Fruits::getName)
.thenComparing(Fruits::getWeight)
.thenComparing(Fruits::getPrice)
);
I have added the working code in codiva - online java compiler ide. I have also included a slightly cleaner implementation in FruitsComparator.java file.
Tree related collections don't use equals() or hashCode(). Those come into play for Map.
Your conditions in the compare result in a 0, hence the fruit isn't inserted.
First Apple goes in as the tree is empty. The 2nd & 3rd Apple result in false in all the if conditions, thus returning the final 0. Put a System.out.println() before the final return to confirm.
If you want to sort the fruits first by name, then by weight & then finally by price, here's a more compact way doing it:
#Override
public int compare(Fruits f1, Fruits f2) {
if (f1.name.equals(f2.name)) {
if (f1.weight < f2.weight) {
return -1;
} else if (f1.weight > f2.weight) {
return 1;
} else {
if (f1.price < f2.price) {
return -1;
} else if (f1.price > f2.price) {
return 1;
} else {
return 0;
}
}
} else {
return f1.name.compareTo(f2.name);
}
}
TreeSet, when used with a Comparator, the elements' equality is decided by the compare method of the Comparator, otherwise would use the compareTo method of its element since they are required to implement the Comparable interface. The hashcode and equals methods will only be used by the Set interface itself (such as method contains uses equals method to check if the elements are presented). And hashcode is not something that a TreeSet to use while it is used by HashSet which is totally another way to implement Set interface. Thus, in your code, since the compare method you've overridden of the Comparator treats these elements equal, so they cannot be inserted for multiple times. One guideline that the Java Tutorial points out is, the compare method should comply with the equals methods, which is, the elements should be treated equal in the compare method if and only if the equals method do.
And in your equals method, you did use this.weight == f.price to compare two fruits, which I don't think is what you intended to do. This makes your equals methods not consistent with the compare method.
For your reference, see Java Object Ordering tutorial, and as well as a question I asked two days ago.
You have an error in your equals method in class Fruits:
if(this.name.equals(f.name) && this.price==f.price && this.weight == f.price)
should have been:
if(this.name.equals(f.name) && this.price==f.price && this.weight == f.weight)
(note the last part).
Can someone explain me in simple terms, why does this code throw an exception, "Comparison method violates its general contract!", and how do I fix it?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
Your comparator is not transitive.
Let A be the parent of B, and B be the parent of C. Since A > B and B > C, then it must be the case that A > C. However, if your comparator is invoked on A and C, it would return zero, meaning A == C. This violates the contract and hence throws the exception.
It's rather nice of the library to detect this and let you know, rather than behave erratically.
One way to satisfy the transitivity requirement in compareParents() is to traverse the getParent() chain instead of only looking at the immediate ancestor.
Just because this is what I got when I Googled this error, my problem was that I had
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
the value >= other.value should (obviously) actually be value > other.value so that you can actually return 0 with equal objects.
The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects. For example, you might want to perform a string compare and force empty strings to sort to the end with:
if ( one.length() == 0 ) {
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
But this overlooks the case where BOTH one and two are empty - and in that case, the wrong value is returned (1 instead of 0 to show a match), and the comparator reports that as a violation. It should have been written as:
if ( one.length() == 0 ) {
if ( two.length() == 0 ) {
return 0; // BOth empty - so indicate
}
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
Even if your compareTo is holds transitivity in theory, sometimes subtle bugs mess things up... such as floating point arithmetic error. It happened to me. this was my code:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
else
return 0;
}
The transitive property clearly holds, but for some reason I was getting the IllegalArgumentException. And it turns out that due to tiny errors in floating point arithmetic, the round-off errors where causing the transitive property to break where they shouldn't! So I rewrote the code to consider really tiny differences 0, and it worked:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if ((this.tfidf - compareTfidf.tfidf) < .000000001)
return 0;
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
return 0;
}
Editing VM Configuration worked for me.
-Djava.util.Arrays.useLegacyMergeSort=true
In our case were were getting this error because we had accidentally flipped the order of comparison of s1 and s2. So watch out for that. It was obviously way more complicated than the following but this is an illustration:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
In my case I was doing something like the following:
if (a.someField == null) {
return 1;
}
if (b.someField == null) {
return -1;
}
if (a.someField.equals(b.someField)) {
return a.someOtherField.compareTo(b.someOtherField);
}
return a.someField.compareTo(b.someField);
What I forgot to check was when both a.someField and b.someField are null.
I've seen this happen in a piece of code where the often recurring check for null values was performed:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
Java does not check consistency in a strict sense, only notifies you if it runs into serious trouble. Also it does not give you much information from the error.
I was puzzled with what's happening in my sorter and made a strict consistencyChecker, maybe this will help you:
/**
* #param dailyReports
* #param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* #param o1
* #param o2
*/
#Override
public void pair(T o1, T o2) {
final int diff = comparator.compare(o1, o2);
if (diff < Compare.EQUAL) {
checkConsistency(objectMapSmallerOnes, o1, o2);
getListSafely(objectMapSmallerOnes, o2).add(o1);
} else if (Compare.EQUAL < diff) {
checkConsistency(objectMapSmallerOnes, o2, o1);
getListSafely(objectMapSmallerOnes, o1).add(o2);
} else {
throw new IllegalStateException("Equals not expected?");
}
}
});
}
/**
* #param objectMapSmallerOnes
* #param o1
* #param o2
*/
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> smallerThan = objectMapSmallerOnes.get(o1);
if (smallerThan != null) {
for (final T o : smallerThan) {
if (o == o2) {
throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* #param keyMapValues
* #param key
* #param <Key>
* #param <Value>
* #return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* #author Oku
*
* #param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* #param o1
* #param o2
*/
void pair(T o1, T o2);
}
/**
*
* Iterates through each distinct unordered pair formed by the elements of a given iterator
*
* #param it
* #param callback
*/
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
#Override
public Iterator<T> iterator() {
return it;
}
});
for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
callback.pair(list.get(outerIndex), list.get(innerIndex));
}
}
}
If compareParents(s1, s2) == -1 then compareParents(s2, s1) == 1 is expected. With your code it's not always true.
Specifically if s1.getParent() == s2 && s2.getParent() == s1.
It's just one of the possible problems.
In my case, it was an infinite sort.
That is, at first the line moved up according to the condition, and then the same line moved down to the same place.
I added one more condition at the end that unambiguously established the order of the lines.
You can't compare object data like this:s1.getParent() == s2 - this will compare the object references. You should override equals function for Foo class and then compare them like this s1.getParent().equals(s2)
I faced the same issue and I solved it.
//This this your code
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
The violation is comparing different things with each other.
//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2
In my code, I wanted to sort addresses by their coordination. In the comparator, I compared between X and Y (by mistake), instead of X and X.
//My code:
private void sortBasedOnX(){
//addresses is a list of addresses where each address has X and Y
addresses.sort((o1, o2) -> {
String a = o1.getAddress().getX();
String b = o2.getAddress().getY(); //<-- this is supposed to be getX
return Integer.parseInt(a)-Integer.parseInt(b);
});
}
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1
i'm trying to remove duplicate objects from my array.
I have my custom which is made of two double : x and y.
What i want to do is to remove duplicate ( (x && y) == (x1 && y1)) and if x == x1 i want to keep the object which has the higher y.
ArrayList<MyObject> list = [x(0),y(0)], [x(0),y(0)], [x(0.5),y(0.5], [x(0.5),y(0.6)], [x(1),y(1)];
ArrayList<MyObject> results = [x(0),y(0)], [x(0.5),y(0.6)], [x(1),y(1)];
I tried to implement the equals method but i do not how to use it :
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof MyObject)) {
return false;
}
return (this.x == ((MyObject)obj).x);
}
list is always ordered using Collections.sort by x.
Thanks for all.
Given MyObject like this:
class MyObject {
private final double x;
private final double y;
public MyObject(double x, double y) {
this.x = x;
this.y = y;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyObject myObject = (MyObject) o;
if (Double.compare(myObject.x, x) != 0) return false;
if (Double.compare(myObject.y, y) != 0) return false;
return true;
}
#Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(x);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
You can implement a unique method that returns a list with unique elements only:
private List<MyObject> unique(List<MyObject> list) {
List<MyObject> uniqueList = new ArrayList<>();
Set<MyObject> uniqueSet = new HashSet<>();
for (MyObject obj : list) {
if (uniqueSet.add(obj)) {
uniqueList.add(obj);
}
}
return uniqueList;
}
And a unit test for it to verify it works:
#Test
public void removeDups() {
List<MyObject> list = Arrays.asList(new MyObject(0, 0), new MyObject(0, 0), new MyObject(0.5, 0.5), new MyObject(0.5, 0.6), new MyObject(1, 1));
List<MyObject> results = Arrays.asList(new MyObject(0, 0), new MyObject(0.5, 0.5), new MyObject(0.5, 0.6), new MyObject(1, 1));
assertEquals(results, unique(list));
}
Note: it's important to implement both equals and hashCode for this to work,
because of the use of a hash map. But you should always do this anyway in your custom classes: provide appropriate equals and hashCode implementations. Btw, I didn't write those equals and hashCode methods. I let my IDE (IntelliJ) generate them automatically from the fields x and y of the class.
Make sure to override equals() method in your custom Object(MyObject).
Then add them into a Set. Now you have unique result set.
Use a Set instead of a List ...
You have to override equals() and hashCode(). The most IDE can generate that for you!
To "convert" a List to a Set you can simply use this:
ArrayList<MyObject> list = ...
Set<MyObject> mySet = new HashSet<MyObject>(list);
Then you have a set with unique elements. You can iterate over the set like this:
for (MyObject o : mySet){
o.getX();
}
The most optimal solution would be if you could use a Set. However, there are two Set implementations in Java: HashSet and TreeSet. HashSet requires that you declare equals and hashCode methods, while TreeSet requires your class to implement Comparable with a compareTo method or supply a Comparator. Neither solution will work in your case because you want to keep the higher y when x is equal. If you sort/calculate equality based on x, y you will have duplicate x, and if you sort/calculate equality based on x only you will get the first x entered, which is not what you want.
Therefore, what we need to do is:
Sort by x ascending, y descending
Convert to Set that preserves original order, but bases equality only on x
Convert back to list (if necessary)
XAscYdesc Comparator Method (Not accounting for nulls):
public int compare(MyObject left, MyObject right) {
int c = left.x - right.x;
if(c != 0) {
return c;
}
return right.y - left.y;
}
XAsc Comparator Method (Not accounting for nulls):
public int compare(MyObject left, MyObject right) {
return left.x - right.x;
}
(Using the Guava library; it's very useful for one-liners like this):
Collections.sort(list, new XAscYdesc());
Lists.newArrayList(ImmutableSortedSet.copyOf(new XAsc(), list));
You need to order your collection on both x and y with x first (think about it as x and y forming an hypothetical number with x on the left side of the decimal point, and y on the right side: when you sort number in growing order, if the integral parts are equal, you sort on the decimal part).
Now, with an equal predicate, it will be difficult to sort values (you can only tell if they are equal, not if one is before another). Instead, you need to implement the comparable interface, with the following method:
public int compare(Object obj) {
if (obj == null || !(obj instanceof MyObject)) {
// raise an Exception
}
MyObject other = (MyObject)obj;
if (x < other.x) return -1;
if (this.x > other.x) return 1;
if (this.y < other.y) return -1;
if (this.y > other.y) return 1;
return 0;
}
If your array is sorted according to this comparison, you just need to keep the last entry with the same x in your array to get what you want. This means that you remove entries unless its successor has a different x.
This method is interesting of you don't want to keep your original data, but only keep the result: it will update the existing array in place, for a complexity of O(n) in time (not counting the sorting, which should happen anyway if I understand your question correctly).
Alternatively, the whole filtering can be achieved by applying a fold on your collection, where folding here is simply keeping the highest y for a same x (as you stated it precisely in your question). Because your collection is already sorted on x, it means that it is naturally partitioned on values of x, so you can build a new collection by accumulating the correct x for each partition in another array. For each element of the array, you compare it with the last inserted entry in your new array, if the x are the same, you replace it with the object with the highest y. If the x are different, you add it to the new array.
The advantage of this approach is that you don't need to change the sorting algorithm, but on the other hand you need a new array. This algorithm should therefore work in O(n) space and time.
Finally, this algorithm may be adapted to an in place update of the original array. It is slightly more complicated, but would let you avoid the extra allocation (crucial on embedded systems).
Pseudo code:
int idx = 0;
while (idx + 1 < Objarray.size()){
MyObj oi = Objarray.get(idx), on = Objarray.get(idx+1);
if (oi.x == on.x) {
if (on.y < oi.y)
Objarray.remove(idx++);
else
Objarray.remove(idx+1);
} else
idx++;
}
Note that while working in constant space, it might be slightly less efficient than the allocating algorithm, because of the way ArrayList works internally (though it should be better than using other usual container types).
/**
*
*/
package test1;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* #author raviteja
*
*/
public class UinquecutomObjects {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Employee e1=new Employee();
e1.setName("abc");
e1.setNo(1);
Employee e2=new Employee();
e2.setName("def");
e2.setNo(2);
Employee e3=new Employee();
e3.setName("abc");
e3.setNo(1);
List<Employee> empList=new ArrayList<Employee>();
empList.add(e1);
empList.add(e2);
empList.add(e3);
System.out.println("list size is "+empList.size());
Set<Employee> set=new HashSet<Employee>(empList);
System.out.println("set size is "+set.size());
System.out.println("set elements are "+set);
}
}
class Employee{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
private int no;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + no;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (no != other.no)
return false;
return true;
}
}
I´m trying to find similar rows in multiple two-dimensional arrays as it was described in my previous post. For the below-given example, the answer is false, true, although it should be false, false.
Another very important question is how to adjust this code to arrays with the different number of rows.
I appreciate very much any help. Thanks.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
ArrayList<Integer[]> array1 = new ArrayList<Integer[]>();
ArrayList<Integer[]> array2 = new ArrayList<Integer[]>();
ArrayList<Integer[]> array3 = new ArrayList<Integer[]>();
array1.add(new Integer[]{1,2,3}); array1.add(new Integer[]{1,0,3});
array2.add(new Integer[]{1,0,3}); array2.add(new Integer[]{0,0,3});
array3.add(new Integer[]{1,2,3}); array3.add(new Integer[]{0,3,3});
for (int i=0; i<array1.size(); i++) {
boolean answ = equalRows(array1.get(i),array2.get(i),array3.get(i));
System.out.println(answ);
}
}
static class Row extends Object {
private int value;
public Row(int val) {
this.value = val;
}
#Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Row at this point
Row row = (Row)obj;
return (value == row.value);
}
#Override
public int hashCode () {
return this.value;
}
}
private static Map<Row, Integer> map(Integer[] row) {
Map<Row, Integer> rowMap = new HashMap<Row, Integer>();
for (int i=0; i<row.length; i++)
rowMap.put(new Row(row[i]), i);
return rowMap;
}
private static boolean equalRows(Integer[] row1, Integer[] row2, Integer[] row3){
Map<Row, Integer> map1 = map(row1);
Map<Row, Integer> map2 = map(row2);
for (int i=0; i<row3.length; i++){
Row row = new Row(row3[i]);
Integer result1 = map1.get(row);
Integer result2 = map2.get(row);
if (result1 == null || result2 == null) {
return false;
}
}
return true;
}
}
Edit#1
In the first test I´m comparing {1,2,3}, {1,0,3} and {1,2,3}. In the second: {1,0,3}, {0,0,3}, {0,3,3}. The problem with the second row is that {0,0,3} and {0,3,3} are tackled as {0,3}. I don´t know how to modify the code to deferentiate between {0,0,3} and {0,3,3} (I still should use HashMap).
Edit#2
The idea is that first I take rows from array1 and array2 and I put them into maps. Then I take a row from array3 and try to find it in maps. If I can´t find it in any of these maps, then it means that rows are not similar.
To compare two arrays, ignoring nulls you can have
public static <T> boolean equalsExceptForNulls(T[] ts1, T[] ts2) {
if (ts1.length != ts2.length) return false;
for(int i = 0; i < ts1.length; i++) {
T t1 = ts1[i], t2 = ts2[i];
if (t1 != null && t2 != null && !t1.equals(t2))
return false;
}
return true;
}
public static <T> boolean equalsExceptForNulls3(T[] ts1, T[] ts2, T[] ts3) {
return equalsExceptForNulls(ts1, ts2) &&
equalsExceptForNulls(ts1, ts3) &&
equalsExceptForNulls(ts2, ts3);
}
// or generically
public static <T> boolean equalsExceptForNulls(T[]... tss) {
for(int i = 0; i < tss.length - 1; i++)
for(int j = i + 1; i < tss.length; j++)
if(!equalsExceptForNulls(tss[i], tss[j])
return false;
return true;
}
The problem you have is that array3 is being used to determine which rows to compare.
In the first test you are comparing rows 1,2,3 and the second test you are comparing rows 0 and 3. The first test should be false and the second should be true.
I found the issue by stepping through your code in a debugger. I suggest you do the same.
I would also use int[] instead of Integer[]
I'm not quite sure what you are trying to accomplish but could it be that your problem lies in the method
public boolean equals(Object obj) {
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Row at this point
Row row = (Row)obj;
return (value == row.value);
}
because with
if(this == obj)
for example you want to have an value comparison - But what you get using the "==" comperator is a comparison of two objects references ?
So maybe
if(this.equals(obj))
is what you want ?
Furthermore, have you tried to step through your code in debugging mode statement per statement ? I guess doiing so could locate your fault quickly ...
cheers :)
There is a basic problem with your approach which leads to this bug. You are using a map to determine the position of an element in the other rows. When constructing the map if there are duplicate elements in the rows, their previous indices will be overwitten. This is exactly what is happening in your case. There is a duplcate zero in the second row of second array.
here is what the maps look like for the second row
map1 = ((Row(1), 0), (Row(0), 1), (Row(3), 3))
map2 = ((Row(0), 1), (Row(3), 3))
Nnotice there are only two elements in map2 bcoz the first one was overwitten with the second one. When you do a lookup with the elements of the second row from the third array the lookup always succeeds (because it it looks only for a 0 and a 3 and never for a 1)
Moreover the condition you check for failure is incomplete i.e
if (result1 == null || result2 == null) {
return false;
}
should be
if (result1 == null || result2 == null || !result1.equals(i) || !result2.equals(i)) {
return false;
}
In my opinion you shouldn't be using a map at all. Instead compare each element one by one. To generalize the code for arrays of different lengths try using size() method of the ArrayList class.
If is important to you that a map should be used, then you should use the index of each array element as the key and the Row object as the value instead of doing the reverse.