-
Notifications
You must be signed in to change notification settings - Fork 177
fix: make ComparableValue fallback to comparing identity hashcode when compareTo returns 0 (instead of when index are identity equal) #2056
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Some questions:
|
The tests before used
It stores objects like
We did half with identity, and half with value, which did not make any sense; @Override
public int compareTo(ComparableValue<Value_, Point_> other) {
if (this == other) {
return 0;
}
var point1 = index;
var point2 = other.index;
if (point1 == point2) {
return compareWithIdentityHashCode(value, other.value);
}
return point1.compareTo(point2);
}
private int compareWithIdentityHashCode(Value_ o1, Value_ o2) {
if (o1 == o2) {
return 0;
}
// Identity Hashcode for duplicate protection; we must always include duplicates.
// Ex: two different games on the same time slot
var identityHashCode1 = System.identityHashCode(o1);
var identityHashCode2 = System.identityHashCode(o2);
return Integer.compare(identityHashCode1, identityHashCode2);
}This does not fix the regression, but does fix a different bug when a user uses an entity inside the constraint collector and overrides equals to depend on a variable. |
|
I'm thinking we should just always use a
The split between |
|
It seems the regression was related to 0bd527f given what the fix is (do identity hashcode if compareTo returns 0) |
|
I am of the opinion the |
There was an inconsistency between valueCountMap and the other maps (such as itemMap) in ConsecutiveSetTree. valueCountMap used Object.equals to check objects for equality, whereas the other maps used identity for equality. As a result, if you have a planning entity value that uses variables in its equals method, the maps become inconsistent and causes exception. The fix is to use IdentityHashMap here instead. The tests were modified to use MutableInt (which is value equal) instead of AtomicInteger (which is identity equal). If you change the IdentityHashMap back to a normal HashMap, you will see several tests fail.
Previous, the code assumed a.compareTo(b) == 0 if and only if a == b; this is false; in particular, when LocalDate or other reference type is used as the point.
5bb97c2 to
21d3c44
Compare
I agree that all the maps should be the same map implementation. There is no question there. But here's why the
From these three points, it follows that it must be a Technically, we can remove that check from the comparator - because we can just agree that if the two are equal, they are equal and no further checks are necessary. We simply do not operate on identity at all; which should IMO have been the case all along. Identity hash maps are supposed to be rare, and not everywhere like we do it. IMO, time to move on. |
...in/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ComparableValue.java
Outdated
Show resolved
Hide resolved
...in/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ComparableValue.java
Outdated
Show resolved
Hide resolved
triceo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM when comments resolved.
Also please update description of the PR so that, when I merge it, I merge something that matches what the PR does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After having deeply reviewed the code and its use in examples, quickstarts and models, I came to the following conclusion:
- We need
HashMapbecause we need to support value types. - We must not switch to
IdentityHashMapsinceHashMapis status quo, all code in the world assumes this to behave like aHashMap. Unless we can show thatHashMapis a bug, it has to stay. - The
ConsecutiveSetTreeTestis wrong, because the way it mimics planning var change is not actually how var changes happen. They happen via remove+add, not just by changing the value directly. When that test adjustment is made, almost all tests pass withMutableInt. (The rest should be evaluated; are the assertions still correct under these circumstances?) - The
ConsecutiveSetTreeTest#testNonconsecutiveNumbersis additionally wrong, because it decided to enforce identity. If it didn't deliberately trigger a failure state, it would not fail.
Therefore I propose:
- Keeping
HashMapin the codebase. - Keeping
MutableIntin the test and fixing the test coverage.
.../ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java
Outdated
Show resolved
Hide resolved
|



Previous, the code assumed a.compareTo(b) == 0 if and only if
a == b; this is false; in particular, when LocalDate or other
reference type is used as the point. This cause the fallback identity hashcode
compare to not happen, and thus erases an entry in the
TreeMap.