Exception during Collections.sort() - java

A java.lang.IllegalArgumentException: Comparison method violates its general contract! is thrown when I do a Collections.sort() with a List of ISimulationResultSet.
I have not found the reason why contract is not respected.
If someone has a idea of the reason, it will be great to explain.
This is the Comparator I am using :
public int compare(ISimulationResultSet r1, ISimulationResultSet r2) {
final float r1Esperance = r1.getResults().getEsperanceGainOuPerte();
final float r2Esperance = r2.getResults().getEsperanceGainOuPerte();
final float r1PrctCibleAtteinte = r1.getResults().getPrctCibleAtteinte();
final float r2PrctCibleAtteinte = r2.getResults().getPrctCibleAtteinte();
if (r1Esperance / r2Esperance > 1.05F)
return -1;
else if (r1Esperance / r2Esperance < 0.95F) {
return 1;
}
else {
if (r1PrctCibleAtteinte == r2PrctCibleAtteinte) {
if (r1Esperance > r2Esperance)
return -1;
else if (r1Esperance < r2Esperance)
return 1;
return 0;
}
else if (r1PrctCibleAtteinte > r2PrctCibleAtteinte)
return -1;
else if (r1PrctCibleAtteinte < r2PrctCibleAtteinte)
return 1;
}
return 0;
}

The comparator has to be symetric, i.e. sgn(compare(x, y)) == -sgn(compare(y, x)) (sgn being the signum function here). This is not the case for your comparator:
Let a1 and a2 denote the values of x.getResults().getEsperanceGainOuPerte() and y.getResults().getEsperanceGainOuPerte() respectively and let b1 and b2 denote the values of x.getResults().getPrctCibleAtteinte() and y.getResults().getPrctCibleAtteinte() respectively.
Now consider the following:
1.05 < a1 < 1.052
a2 = 1
b2 > b1
Therefore a2 / a1 > 0.95
compare(x, y) == -1;// first (r1Esperance / r2Esperance > 1.05F) is true
compare(y, x) == -1; // first 3 conditions false, (r1PrctCibleAtteinte > r2PrctCibleAtteinte) is true
That violates the contract.

Related

What is the difference between code examples?

I have my code for "Find the missing integer" in Codility
public static int solution(int[] A) {
ArrayList<Integer> a = new ArrayList<Integer>();
for(int i=0; i<A.length; i++) if(A[i] >= 0) a.add(A[i]);
if(a.isEmpty()) {
return 1;
}
a.sort(null);
if(a.get(0) > 1) {
return 1;
}
for(int i=0; i<a.size()-1; i++) {
if(a.get(i) != a.get(i+1) && a.get(i)+1 != a.get(i+1)) {
return a.get(i)+1;
}
}
return a.get(a.size()-1)+1;
}
This code works for all except Performance tests - large_1.
It gives me an error "got 233 expected 40000".
When i replace this code:
if(a.get(i) != a.get(i+1) && a.get(i)+1 != a.get(i+1))
return a.get(i) +1;
with
int a1 = a.get(i);
int a2 = a.get(i+1);
if(a1 != a2 && a1 +1 != a2) return a.get(i) +1;
or
int sub = a.get(i+1) - a.get(i);
if(sub != 0 && sub != 1) return a.get(i) +1;
then there are no errors.(I got 100/100 score when i replace that line)
Is there anyone who can give some explanation for the difference?
They seem the same to me.
if(a.get(i) != a.get(i+1) && a.get(i)+1 != a.get(i+1))
Because a is an ArrayList<Integer>, a.get(i) is an Integer, so you're comparing Integers by identity here.
When you added the array elements to the list using a.add(A[i]), they were auto-boxed: the compiler rewrote this to a.add(Integer.valueOf(A[i])).
Only Integers in the range -128..127 are guaranteed to be cached by Integer.valueOf; so if the Integer's value is outside this range, you will be comparing Integers that have equal value but different identities.
On the other hand:
int a1 = a.get(i); // Unboxing: int a1 = a.get(i).intValue()
int a2 = a.get(i+1); // Unboxing: int a2 = a.get(i+1).intValue()
if(a1 != a2 && a1 +1 != a2)
a1 and a2 are primitives - you unbox them by assigning them to int variables - so it's fine to compare them by == or !=.
In your first version, replace A != B with !A.equals(B) (or !Objects.equals(A, B)).
Your if condition comparing the Integer objects which obliviously compare the address location of the objects instead of the values. And this is i am sure not expected by you.
Change your if condition to below -
if(a.get(i).intValue() != a.get(i+1).intValue() && a.get(i).intValue()+1 != a.get(i+1).intValue())

Java change long if statement into for loop

I have an if statement that looks like this:
if (pan[x + 1][y + 1].getBackground() == TeamColor &&
pan[x + 1][y].getBackground() == TeamColor &&
pan[x + 1][y -1].getBackground() == TeamColor &&
pan[x][y - 1].getBackground() == TeamColor &&
pan[x - 1][y - 1].getBackground() == TeamColor &&
pan[x - 1][y].getBackground() == TeamColor &&
pan[x - 1][y + 1].getBackground() == TeamColor &&
pan[x][y + 1].getBackground() == TeamColor) {
// do something
}
The goal is to check every item (in a 2d array) around the current x and y values and make sure they are the correct color.
I assume there is a simple way to do such. I would assume creating a for loop would solve the problem by iterating through each item but unfortunately was not able to think of a way to do this because the items are not all in sequence.
NOTE: i found many other posts on stackoverflow that where titled "solution for very long if statement" unfortunately they were in different programing languages (such as python, android and javascript)
NOTE 2: this is Not a duplicate of this post. It was a question of strings and regex and unfortunately not the solution to my problem
Hopefully someone will have an answer!
Try something like this:
boolean match = true;
for (int dx = -1; match && (dx < 2); ++dx) {
for (int dy = -1; match && (dy < 2); ++dy) {
if (dx != 0 || dy != 0) {
match = pan[x+dx][y+dy].getBackground() == TeamColour;
}
}
}
if (match) {
// do something
}
Basically, you want to check offsets -1, 0 and 1 in each direction, so we have two for loops, each producing those offsets in one dimension. We then check the array element corresponding to each offset, and keep track using the match variable.
Note though that, like the original code, this will fail near boundaries (e.g. if x == 0). This can be fixed if necessary.
It is possible, of course, to instead have the loops run over the actual indices to check (e.g. for (int x2 = x-1; x2 < x+2; ++x)). It's much the same in the end.
for (int a = x-1;a <= x+1;a++)
{
if (a < 0 || a >= pan.length) continue;
for (int b = y-1; b <= y+1; b++)
{
if (b < 0 || b >= pan[a].length) continue;
if (a == x && b == y) continue;
if (pan[a][b].getBackground() != TeamColor)
return false;
}
}
return true;
I can propose two ways :
1) Full object way
You could introduce a custom class Coordinate that holds two values : the x and y coordinates.
Create a List of Coordinate where you had the Coordinate element you want to test and iterate on it to achieve your need.
public class Coordinate{
private final int x;
private final int y;
public Coordinate(int x, int y){
this.x = x;
this.y = y;
}
public getX(){
return x;
}
public getY(){
return y;
}
}
And you can use it :
List<Coordinate> coordinates = new ArrayList<>();
coordinates.add(new Coordinate(1,1));
coordinates.add(new Coordinate(1,0));
coordinates.add(new Coordinate(1,-1));
coordinates.add(new Coordinate(0,-1));
coordinates.add(new Coordinate(-1,-1));
coordinates.add(new Coordinate(-1,0));
coordinates.add(new Coordinate(-1,1));
coordinates.add(new Coordinate(0,1));
// you can also init them with a loop
boolean isMatched = true;
for (Coordinate coordinate : coordinates){
if (pan[x + coordinate.getX()][y + coordinate.getY()].getBackground() != TeamColor){
isMatched = false;
break;
}
}
The object way is more verbose but it has the advantage to expose rules.
So you can read and change it easily.
Suppose, the rules to check become more complex, it becomes very valuable.
2) Shorter code way
It is the same logical even by inlining values of Coordinate and by ignoring the specific case that you don't want to test (no change case).
boolean isMatched = true;
for (int xDelta = -1; xDelta <=1; xDelta++){
for (int yDelta = -1; yDelta <=1; yDelta++){
// as you don't want to test if no change
if (yDelta == 0 && xDelta ==0){
continue;
}
if (pan[x + xDelta][y + yDelta ].getBackground() != TeamColor){
isMatched = false;
break;
}
}

Comparison error for isometric sorting

So I'm implementing an isometric sorter for my sprites and I'm having some issues with the comparison of when the tiles should be rendered. I'm sorting all the isometric sprites that will be rendered by implementing them as comparable.
The problem is, when I'm implementing the following compareTo method:
// 1 = render this after
// 0 == render same
// -1 = render this before
#Override
public int compareTo(IsoSprite o) {
if(z >= o.z && maxY <= o.minY && maxX <= o.minX){
return 1;
}
if(z >= o.z && maxY >= o.minY && maxX >= o.minX){
return -1;
}
if(z > o.z){
return 1;
}
if(z < o.z){
return -1;
}
//z == o.z && maxY == o.maxY && minY == o.minY && minX == o.minX && maxX == o.maxX
return 0;
}
I get the error "Comparison method violates its general contract!" from the array.sort call in the LibGDX Array (which I use for sorting). I can't tell how I am supposed to solve this when looking at other peoples issue with this error, but those problems are mostly trivial. Anyone know how I should solve this in my isometric comparison?
My isometric world (for reference):
Edit:
Found something interesting when only sorting by Z:
//Doesn't work
public int compareTo(IsoSprite o) {
if(maxZ > o.z){
return 1;
}
if (maxZ < o.z){
return -1;
}
return 0;
}
//Works
#Override
public int compareTo(IsoSprite o) {
if(z > o.z){
return 1;
}
if(z < o.z){
return -1;
}
return 0;
}
I realised I won't be able to do the comparisons needed in a comparable. So instead I'm using my own implementation of Quicksort to sort using my own compareTo method that basically checks if a sprite is behind or infront of another one.
Thanks for all the help anyway!
This message indicates that there is something wrong
with transitive logic in comparator, for if A > B then also B < A must be true. The compiler is smart enough to point it out to the user.
The problem in code is that a different values are compared to each other. To correct it you have to compare the same values minY < o.minY, and not to use <= and >= operators.
This should work:
public int compareTo(IsoSprite o) {
if (isoDepth > o.isoDepth) return 1;
if (isoDepth < o.isoDepth) return -1;
return 0;
}
See algorithm to calculate isoDepth that could be used to sort /compare IsoSprites.

Why does this comparison method violate its general contract?

I am using a special comparator to sort a list of pairs according to the second part of the pair:
Collections.sort(ans, new Comparator<Pair<Component, Double>>()
{
public int compare(Pair<Component, Double> l, Pair<Component, Double> r)
{
if (r.second - l.second < 0) return -1;
else if(r.second==l.second) return 0;
else return 1;
}
});
The compare method seems to be both transitive (a < b < c => a < c) and each component
is equal to itself. What could cause the exception?
There are edge cases you haven't considered—and shouldn't, because the JDK already provides a fully compliant method, which I present here for completeness::
public static int compare(double d1, double d2) {
if (d1 < d2)
return -1; // Neither val is NaN, thisVal is smaller
if (d1 > d2)
return 1; // Neither val is NaN, thisVal is larger
// Cannot use doubleToRawLongBits because of possibility of NaNs.
long thisBits = Double.doubleToLongBits(d1);
long anotherBits = Double.doubleToLongBits(d2);
return (thisBits == anotherBits ? 0 : // Values are equal
(thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
1)); // (0.0, -0.0) or (NaN, !NaN)
}

Comparison method violates its general contract, how to make it transitive?

I dont see how it is not transitive, please someone suggest me the right way.
if both values are null
I return 0, the two other statements are pretty obvious.
Why i got IllegalArgumentException with:
Comparison method violates its general contract
My compare method: (I compare doubles)
#Override
public int compare(HashMap<String, String> lhs, HashMap<String, String> rhs) {
double dist1 = 0;
double dist2 = 0;
int compInt1 = 0;
int compInt2 = 0;
if (lhs.get("dist") != null && rhs.get("dist") != null && !lhs.get("dist").equals("") && !rhs.get("dist").equals("")) {
dist1 = Double.parseDouble(lhs.get("dist").substring(0, lhs.get("dist").length() - 3));
dist2 = Double.parseDouble(rhs.get("dist").substring(0, rhs.get("dist").length() - 3));
dist1 = dist1 * 100;
dist2 = dist2 * 100;
compInt1 = (int) dist1;
compInt2 = (int) dist2;
}
if (compInt1 < compInt2) {
return -1;
} else if (compInt1 >= compInt2) {
return 1;
} else {
return 0;
}
}
Look at this code:
if (compInt1 < compInt2) {
return -1;
} else if (compInt1 >= compInt2) {
return 1;
} else {
return 0;
}
How do you expect that ever to return 0? What values of compInt1 and compInt2 would make both if conditions fail?
This violates symmetry too - it means that compare(x, y) and compare(y, x) can both return 1...
Assuming you're really just trying to compare compInt1 and compInt2 in the obvious way at this point, just replace the whole block with:
// As of Java 7...
return Integer.compare(compInt1, compInt2);
If you're using Java 6 (or earlier) you could use:
return Integer.valueOf(compInt1).compareTo(compInt2);
That's a little inefficient, but it's at least worth using to start with, just to get everything working.
Additionally, I'd strongly recommend extracting the lhs.get("dist") and rhs.get("dist") expressions from your first part - it's horribly repetitive at the moment. And just declare dist1 and dist2 within the block - they're not used elsewhere.
The condition else if (compInt1 >= compInt2) is ambiguous as it would return a value of 1 even if both values are equal. Replace your comparison logic of ints as follows :
return compInt1 - compInt2;
Transitivity defines as :
compare(a, b) == -compare(b, a)
Which implies compare(a, a) == -compare(a, a) == 0
Now, considering your code,
compare(a, a) == 1

Categories

Resources