The method
entriesSortedByValues
creates an anonymous Comparator. Comparators are thread-safe and re-entran, and, as a result, it is just a waste of time to create one each time the method is called. Something like:private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
While in the process of doing that last one above, it appeared to me that the comparators are the wrong way around in there:
if (descending) res = e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue());
This line is highly suspicious... this makes the comparator non-transitive. With a comparator,
compare(a, b)
should be the same sign as-compare(b,a)
, but, in your case, ifa.equals(b)
, then your code will return 1 in both cases.return res != 0 ? -res : 1; // Special fix to preserve items with equal values
Your code will likely fail with "Comparison method violates its general contract!" "Comparison method violates its general contract!"
getRandom
again makes it hard to move to mult-threaded. Consider usingjava.util.concurrent.ThreadLocalRandom
The method
entriesSortedByValues
creates an anonymous Comparator. Comparators are thread-safe and re-entran, and, as a result, it is just a waste of time to create one each time the method is called. Something like:private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
While in the process of doing that last one above, it appeared to me that the comparators are the wrong way around in there:
if (descending) res = e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue());
This line is highly suspicious... this makes the comparator non-transitive. With a comparator,
compare(a, b)
should be the same sign as-compare(b,a)
, but, in your case, ifa.equals(b)
, then your code will return 1 in both cases.return res != 0 ? -res : 1; // Special fix to preserve items with equal values
Your code will likely fail with "Comparison method violates its general contract!"
getRandom
again makes it hard to move to mult-threaded. Consider usingjava.util.concurrent.ThreadLocalRandom
The method
entriesSortedByValues
creates an anonymous Comparator. Comparators are thread-safe and re-entran, and, as a result, it is just a waste of time to create one each time the method is called. Something like:private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
While in the process of doing that last one above, it appeared to me that the comparators are the wrong way around in there:
if (descending) res = e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue());
This line is highly suspicious... this makes the comparator non-transitive. With a comparator,
compare(a, b)
should be the same sign as-compare(b,a)
, but, in your case, ifa.equals(b)
, then your code will return 1 in both cases.return res != 0 ? -res : 1; // Special fix to preserve items with equal values
Your code will likely fail with "Comparison method violates its general contract!"
getRandom
again makes it hard to move to mult-threaded. Consider usingjava.util.concurrent.ThreadLocalRandom
- This is an accumulator for the score attached to a field. It could potentially contain multiple scores. I am disappointed that it is not thread-safe. First impression is that it would be a useful optimization to have the fields all scored by different scorers at the same time. This would not be possible with the current FieldScore
FieldScore
.
PostScorer is an abstract class, but
PreScorer` is an interface. I would expect them to be mirrors of each other, but they are not.The method
onScoringComplete
should be something likeonPreScoringComplete
, right?analyzeAnalyze should be returning some generic type, not
Object
.I would expect the methods on PreScorer
PreScorer
and PostScorerPostScorer
to be similar to each other, but they are not... at all.Again, I would expect a more complicated
<P, F>
signature on the PreScorerPreScorer
... without it, is it doing something wrong?
- inIn all other methods you have been careful to return copies of the stored structures, but in this class you return the basic values. Should the methods use
Collections.unmodifiableMap(....)
ornew HashMap(....)
instead?
- whyWhy have a factory class and method if the constructors are public anyway?
sinceSince when does this make sense?
public class ScoreSet<P, F> extends LinkedHashMap<AbstractScorer<P, F>, Double>
???? ;-)
The method
entriesSortedByValues
creates an anonymous Comparator. Comparators are thread-safe and re-entran, and, as a result, it is just a waste of time to create one each time the method is called. Something like:private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
While in the process of doing that last one above, it appeared to me that the comparators are the wrong way around in there:
if (descending) res = e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue());
This line is highly suspicious... this makes the comparator non-transitive. With a comparator,
compare(a, b)
should be the same sign as-compare(b,a)
, but, in your case, ifa.equals(b)
, then your code will return 1 in both cases.return res != 0 ? -res : 1; // Special fix to preserve items with equal values
return res != 0 ? -res : 1; // Special fix to preserve items with equal values
Your code will likely fail with "Comparison method violates its general contract!"
getRandom
getRandom
again makes it hard to move to mult-threaded. Consider usingjava.util.concurrent.ThreadLocalRandom
The Codecode is good, and hangs together 'quite well.
- This is an accumulator for the score attached to a field. It could potentially contain multiple scores. I am disappointed that it is not thread-safe. First impression is that it would be a useful optimization to have the fields all scored by different scorers at the same time. This would not be possible with the current FieldScore.
PostScorer is an abstract class, but
PreScorer` is an interface. I would expect them to be mirrors of each other, but they are not.The method
onScoringComplete
should be something likeonPreScoringComplete
, right?analyze should be returning some generic type, not
Object
.I would expect the methods on PreScorer and PostScorer to be similar to each other, but they are not... at all.
Again, I would expect a more complicated
<P, F>
signature on the PreScorer... without it, is it doing something wrong?
- in all other methods you have been careful to return copies of the stored structures, but in this class you return the basic values. Should the methods use
Collections.unmodifiableMap(....)
ornew HashMap(....)
instead?
- why have a factory class and method if the constructors are public anyway?
since when does this make sense?
public class ScoreSet<P, F> extends LinkedHashMap<AbstractScorer<P, F>, Double>
???? ;-)
The method
entriesSortedByValues
creates an anonymous Comparator. Comparators are thread-safe and re-entran, and, as a result, it is just a waste of time to create one each time the method is called. Something like:private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
While in the process of doing that last one above, it appeared to me that the comparators are the wrong way around in there:
if (descending) res = e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue());
This line is highly suspicious... this makes the comparator non-transitive. With a comparator,
compare(a, b)
should be the same sign as-compare(b,a)
, but, in your case, ifa.equals(b)
, then your code will return 1 in both cases.return res != 0 ? -res : 1; // Special fix to preserve items with equal values
Your code will likely fail with "Comparison method violates its general contract!"
getRandom again makes it hard to move to mult-threaded. Consider using
java.util.concurrent.ThreadLocalRandom
The Code is good, and hangs together 'quite well.
- This is an accumulator for the score attached to a field. It could potentially contain multiple scores. I am disappointed that it is not thread-safe. First impression is that it would be a useful optimization to have the fields all scored by different scorers at the same time. This would not be possible with the current
FieldScore
.
PostScorer is an abstract class, but
PreScorer` is an interface. I would expect them to be mirrors of each other, but they are not.The method
onScoringComplete
should be something likeonPreScoringComplete
, right?Analyze should be returning some generic type, not
Object
.I would expect the methods on
PreScorer
andPostScorer
to be similar to each other, but they are not... at all.Again, I would expect a more complicated
<P, F>
signature on thePreScorer
... without it, is it doing something wrong?
- In all other methods you have been careful to return copies of the stored structures, but in this class you return the basic values. Should the methods use
Collections.unmodifiableMap(....)
ornew HashMap(....)
instead?
- Why have a factory class and method if the constructors are public anyway?
Since when does this make sense?
public class ScoreSet<P, F> extends LinkedHashMap<AbstractScorer<P, F>, Double>
???? ;-)
The method
entriesSortedByValues
creates an anonymous Comparator. Comparators are thread-safe and re-entran, and, as a result, it is just a waste of time to create one each time the method is called. Something like:private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
While in the process of doing that last one above, it appeared to me that the comparators are the wrong way around in there:
if (descending) res = e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue());
This line is highly suspicious... this makes the comparator non-transitive. With a comparator,
compare(a, b)
should be the same sign as-compare(b,a)
, but, in your case, ifa.equals(b)
, then your code will return 1 in both cases.return res != 0 ? -res : 1; // Special fix to preserve items with equal values
Your code will likely fail with "Comparison method violates its general contract!"
getRandom
again makes it hard to move to mult-threaded. Consider usingjava.util.concurrent.ThreadLocalRandom
The code is good, and hangs together 'quite well.
###AbstractScorer
Are you sure you need the precision of double for scoring? I only ask because I recently had reason to down-convert a system to float because it had many millions of scorable items and the memory saving ended up being significant.
toString()
It is best practice to put a toString on each and every class you implement. The toString method is there to communicate with yourself (when debugging) and other programmers who unfortunately may need to devour your stack traces. There is no benefit in having a toString on an abstract class. Your toString, giving just the class's simple name, is even worst than the defaultObject.toString()
###FieldScore
- This is an accumulator for the score attached to a field. It could potentially contain multiple scores. I am disappointed that it is not thread-safe. First impression is that it would be a useful optimization to have the fields all scored by different scorers at the same time. This would not be possible with the current FieldScore.
###FieldScoreProducer
Some documentation here would be nice. I am not certain 'producer' is the way I would describe the class, but then I am not sure what I would recommend instead.
The
analyze()
method has Generics withObject
type. This should be resolved with an additional Generic type on the class. Object is a poor declaration, and it indicates a lack of structure.detailed
is odd. I can't see why it is not a final field. ThesetDetailed
should be replaced with a flag on the constructor.
###FieldScores
It is customary to put the Constructors before the methods of the class... (and after static declarations and methods). You have some regular methods before the Constructor.
private final Map<F, FieldScore<F>> scores
is a problem. The key of the map is of typeF
, but there is no indication as to what thatF
class is. As a result, you have no idea of whetherF
implementshashCode()
andequals()
in a way that is compatible with the Map contract. I would recommend that you declare that class as anIdentityHashMap
just in case.
-detailed
I don't like that it is not final. Also, you have a setDetailed()
method but no isDetailed()
. It should be set as part of the constructor as a final.
you have the word
analyzes
in various forms throughout the code. I think the right word isanalyzers
.getAnalyzes()
should wrap the return value in the more lightweightCollections.unmodifiableMap(....)
rather than creating a newHashMap
getScores()
ditto
###PostScorer
- again with the
toString()
on an abstract class.
###PreScorer
PostScorer is an abstract class, but
PreScorer` is an interface. I would expect them to be mirrors of each other, but they are not.The method
onScoringComplete
should be something likeonPreScoringComplete
, right?analyze should be returning some generic type, not
Object
.I would expect the methods on PreScorer and PostScorer to be similar to each other, but they are not... at all.
Again, I would expect a more complicated
<P, F>
signature on the PreScorer... without it, is it doing something wrong?
###ScoreConfig
- in all other methods you have been careful to return copies of the stored structures, but in this class you return the basic values. Should the methods use
Collections.unmodifiableMap(....)
ornew HashMap(....)
instead?
###ScoreConfigFactory
- why have a factory class and method if the constructors are public anyway?
###ScoreSet
since when does this make sense?
public class ScoreSet<P, F> extends LinkedHashMap<AbstractScorer<P, F>, Double>
???? ;-)
###ScoreTools
The method
entriesSortedByValues
creates an anonymous Comparator. Comparators are thread-safe and re-entran, and, as a result, it is just a waste of time to create one each time the method is called. Something like:private static final ASCENDING_VALUE_COMPARATOR = new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { int res; e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? -res : 1; // Special fix to preserve items with equal values } }
While in the process of doing that last one above, it appeared to me that the comparators are the wrong way around in there:
if (descending) res = e1.getValue().compareTo(e2.getValue()); else res = e2.getValue().compareTo(e1.getValue());
This line is highly suspicious... this makes the comparator non-transitive. With a comparator,
compare(a, b)
should be the same sign as-compare(b,a)
, but, in your case, ifa.equals(b)
, then your code will return 1 in both cases.return res != 0 ? -res : 1; // Special fix to preserve items with equal values
Your code will likely fail with "Comparison method violates its general contract!"
getRandom again makes it hard to move to mult-threaded. Consider using
java.util.concurrent.ThreadLocalRandom
##Conclusion
Currently it appears that is works, but there is some missing details when it comes to being a multi-threaded application. I see a number of benefits in that direction.
The Code is good, and hangs together 'quite well.