描述
此代码最常见的应用是游戏中有一个计算机对手,需要根据一些参数做出决定。可以以某种方式创建/检索所有可能做出的决策的列表(请注意,对于实时策略游戏,这可能是诸如
Build A
,Build B
,Attack C
,Explore D
等的列表)。为了做出决定,每个选项都从称为Scorers
的多个来源获得分数。每个AI均配置为使用一组计分器,并将权重应用于每个计分器。下面是一些有关我的Minesweeper Flags游戏中当前使用方式的图片(即,基于轮次的Minesweeper 2玩家模式,目的是夺走地雷而不是避开地雷)。在每个可能继续前进的字段上,都有一个数字确定“等级”,1是可能的最佳动作之一,2是次佳的动作之一,依此类推(可能的等级数量取决于情况和得分的对象)
将要得分的地图:
“ AI HardPlus”应用得分后的领域排名:
“ AI Nightmare”应用得分后的领域排名:
排名由“ AI失败者”应用得分后的字段:(偏爱具有较低地雷概率的字段)
类摘要(13行中的740行,总共21944个字节)得分配置,可以生成FieldScores。
FieldScores:存储FieldScore对象r几个字段
PostScorer:用于在所有AbstractScorers完成评分之后将得分应用于字段的抽象类
PreScorer:在AbstractScorers开始评分之前,负责根据需要分析事物的接口
ScoreConfig:得分配置
ScoreConfigFactory:工厂类创建
ScoreConfig
ScoreParameters:允许计分员访问分析数据和发送用于评分的参数的接口
Scorer:负责应用计分的类(即Scorers和PostScorers)的Marker接口
ScoreSet:一个
Map<AbstractScorer, Double>
,用于跟踪应应用于计分器的权重ScoreStrategy:提供应计分字段列表的接口
ScoreTools.java:仅是几种实用方法
此代码也可以从GitHub上的存储库中下载。
AbstractScorer.java:(31行,993字节) >
FieldScore.java:(99行,2434字节)
/**
* Scorer that is responsible to give score to fields
*
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public abstract class AbstractScorer<P, F> implements Scorer {
/**
* Determine if this scorer should apply scores to the fields under the given circumstances.
*
* @param scores Score parameters and analyzes for the scoring
* @return True to work with the parameters, false to exclude this scorer entirely from the current scoring process
*/
public boolean workWith(ScoreParameters<P> scores) {
return true;
}
/**
* Determine the score for the given field and parameters.
* @param field Field to score
* @param scores Parameters and analyzes for the scoring
* @return The score to give to the field
*/
public abstract double getScoreFor(F field, ScoreParameters<P> scores);
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
FieldScoreProducer.java:(68行,1887字节)
/**
* Score container for a specific field.
*
* @param <F> The type to apply scores to.
*/
public class FieldScore<F> implements Comparable<FieldScore<F>> {
private int rank;
private double score;
private final F field;
private final Map<Scorer, Double> specificScorers;
private double normalized;
public FieldScore(F field) {
this(field, false);
}
/**
*
* @param field Field to score
* @param detailed If true, details about how much score is given from each scorer will be saved
*/
public FieldScore(F field, boolean detailed) {
this.field = field;
this.specificScorers = detailed ? new HashMap<Scorer, Double>() : null;
}
void addScore(AbstractScorer<?, F> scorer, double score, double weight) {
double add = score * weight;
this.saveScore(scorer, add);
}
private void saveScore(Scorer scorer, double score) {
this.score += score;
if (scorer != null && specificScorers != null) {
this.specificScorers.put(scorer, score);
}
}
void setRank(int rank) {
this.rank = rank;
}
void setNormalized(double normalized) {
this.normalized = normalized;
}
@Override
public int compareTo(FieldScore<F> other) {
return Double.compare(this.score, other.score);
}
public double getScore() {
return this.score;
}
/**
* Get the field represented by this {@link FieldScore}
* @return The field that this object contains score for
*/
public F getField() {
return this.field;
}
void giveExtraScore(PostScorer<?, F> scorer, double bonus) {
this.saveScore(scorer, bonus);
}
/**
* Get this field's rank.
* @return The rank score of this field, where 1 is the best rank
*/
public int getRank() {
return rank;
}
/**
* Get this field's normalized score
* @return Normalized score, from 0 to 1.
*/
public double getNormalized() {
return this.normalized;
}
/**
* Get detailed information about which scorer gave what score to this field
* @return Detailed information, or null if this field did not save details
*/
public Map<Scorer, Double> getScoreMap() {
return this.specificScorers == null ? null : new HashMap<Scorer, Double>(this.specificScorers);
}
@Override
public String toString() {
return "(" + this.field + " score " + this.score + ")";
}
}
FieldScores.java:(184行,5823字节)
/**
*
*
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public class FieldScoreProducer<P, F> {
private final ScoreConfig<P, F> config;
private boolean detailed;
private final ScoreStrategy<P, F> scoreStrategy;
public FieldScoreProducer(ScoreConfig<P, F> config, ScoreStrategy<P, F> strat) {
this.config = config;
this.scoreStrategy = strat;
}
public FieldScores<P, F> score(P params, Map<Class<?>, Object> analyzes) {
FieldScores<P, F> scores = new FieldScores<P, F>(params, config, scoreStrategy);
scores.setAnalyzes(analyzes);
scores.setDetailed(this.detailed);
scores.determineActiveScorers();
scores.calculateScores();
scores.rankScores();
scores.postHandle();
for (PreScorer<P> prescore : config.getPreScorers()) {
prescore.onScoringComplete();
}
return scores;
}
public boolean isDetailed() {
return detailed;
}
/**
* Set whether or not each FieldScore should contain detailed information about how much score the field got from all different scorers (including post scorers)
* @param detailed True if detailed, false otherwise.
*/
public void setDetailed(boolean detailed) {
this.detailed = detailed;
}
public Map<Class<?>, Object> analyze(P param) {
Map<Class<?>, Object> analyze = new HashMap<Class<?>, Object>();
for (PreScorer<P> preScorers : this.config.getPreScorers()) {
Object data = preScorers.analyze(param);
if (data == null)
continue; // avoid NullPointerException
analyze.put(data.getClass(), data);
}
return analyze;
}
public ScoreConfig<P, F> getConfig() {
return this.config;
}
public FieldScores<P, F> analyzeAndScore(P params) {
return this.score(params, this.analyze(params));
}
}
PostScorer.java:(53行,1779字节)
/**
* Class containing scores, information about ranks, analyzes, and which score configuration that was used.
*
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public class FieldScores<P, F> implements ScoreParameters<P> {
private final ScoreConfig<P, F> config;
private final Map<F, FieldScore<F>> scores = new HashMap<F, FieldScore<F>>();
private final P params;
private final ScoreStrategy<P, F> scoreStrategy;
private List<AbstractScorer<P, F>> activeScorers;
private List<List<FieldScore<F>>> rankedScores;
private Map<Class<?>, Object> analyzes;
private boolean detailed;
@SuppressWarnings("unchecked")
public <E> E getAnalyze(Class<E> clazz) {
E value = (E) this.analyzes.get(clazz);
if (value == null)
throw new NullPointerException("Analyze " + clazz + " not found. Did you forget to add a PreScorer using ScoreConfigFactory.withPreScorer?");
return value;
}
@Override
public Map<Class<?>, Object> getAnalyzes() {
return new HashMap<Class<?>, Object>(this.analyzes);
}
FieldScores(P params, ScoreConfig<P, F> config, ScoreStrategy<P, F> strat) {
this.params = params;
this.config = config;
this.scoreStrategy = strat;
}
@Override
public ScoreStrategy<P, F> getScoreStrategy() {
return scoreStrategy;
}
/**
* Call each {@link AbstractScorer}'s workWith method to determine if that scorer is currently applicable
*/
void determineActiveScorers() {
activeScorers = new ArrayList<AbstractScorer<P, F>>();
for (AbstractScorer<P, F> scorer : config.getScorers().keySet()) {
if (scorer.workWith(this)) {
activeScorers.add(scorer);
}
}
}
/**
* Process the {@link AbstractScorer}s to let them add their score for each field. Uses the {@link ScoreStrategy} associated with this object to determine which fields should be scored.
*/
void calculateScores() {
for (F field : this.scoreStrategy.getFieldsToScore(params)) {
if (!this.scoreStrategy.canScoreField(this, field))
continue;
FieldScore<F> fscore = new FieldScore<F>(field, detailed);
for (AbstractScorer<P, F> scorer : activeScorers) {
double computedScore = scorer.getScoreFor(field, this);
double weight = config.getScorers().get(scorer);
fscore.addScore(scorer, computedScore, weight);
}
scores.put(field, fscore);
}
}
/**
* Call {@link PostScorer}s to let them do their job, after the main scorers have been processed.
*/
void postHandle() {
for (PostScorer<P, F> post : this.config.getPostScorers()) {
post.handle(this);
this.rankScores(); // Because post-scorers might change the result, re-rank the scores to always have proper numbers.
}
}
@Override
public P getParameters() {
return this.params;
}
/**
* Get a List of all the ranks. Each rank is a list of all the {@link FieldScore} objects in that rank
* @return A list of all the ranks, where the first item in the list is the best rank
*/
public List<List<FieldScore<F>>> getRankedScores() {
return rankedScores;
}
/**
* @return A {@link HashMap} copy of the scores that are contained in this object
*/
public Map<F, FieldScore<F>> getScores() {
return new HashMap<F, FieldScore<F>>(this.scores);
}
/**
* Get the {@link FieldScore} object for a specific field.
* @param field Field to get data for
* @return FieldScore for the specified field.
*/
public FieldScore<F> getScoreFor(F field) {
return scores.get(field);
}
/**
* (Re-)calculates rankings for all the fields, and also calculates a normalization of their score
*/
public void rankScores() {
SortedSet<Entry<F, FieldScore<F>>> sorted = ScoreTools.entriesSortedByValues(this.scores, true);
rankedScores = new LinkedList<List<FieldScore<F>>>();
if (sorted.isEmpty())
return;
double minScore = sorted.last().getValue().getScore();
double maxScore = sorted.first().getValue().getScore();
double lastScore = maxScore + 1;
int rank = 0;
List<FieldScore<F>> currentRank = new LinkedList<FieldScore<F>>();
for (Entry<F, FieldScore<F>> score : sorted) {
if (lastScore != score.getValue().getScore()) {
lastScore = score.getValue().getScore();
rank++;
currentRank = new LinkedList<FieldScore<F>>();
rankedScores.add(currentRank);
}
score.getValue().setRank(rank);
double normalized = ScoreTools.normalized(score.getValue().getScore(), minScore, maxScore - minScore);
score.getValue().setNormalized(normalized);
currentRank.add(score.getValue());
}
}
/**
* Get all {@link FieldScore} objects for a specific rank
* @param rank From 1 to getRankLength()
* @return A list of all FieldScores for the specified rank
*/
public List<FieldScore<F>> getRank(int rank) {
if (rankedScores.isEmpty()) return null;
return rankedScores.get(rank - 1);
}
/**
* Get the number of ranks
* @return The number of ranks
*/
public int getRankCount() {
return rankedScores.size();
}
/**
* @return The score configuration that was used to calculate these field scores.
*/
public ScoreConfig<P, F> getConfig() {
return this.config;
}
void setAnalyzes(Map<Class<?>, Object> analyzes) {
this.analyzes = new HashMap<Class<?>, Object>(analyzes);
}
/**
* @param detailed True to store detailed information about which scorer gives which score to which field. False otherwise
*/
public void setDetailed(boolean detailed) {
this.detailed = detailed;
}
}
PreScorer.java :( 18行,549字节)
/**
* A scorer that can apply/modify scores after the regular {@link AbstractScorer}s have done their job.
*
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public abstract class PostScorer<P, F> implements Scorer {
@Override
public String toString() {
return "Post-" + this.getClass().getSimpleName();
}
/**
* Optionally apply any scores to the given {@link FieldScores} object.
* @param scores The collection of scores to work on.
*/
public abstract void handle(FieldScores<P, F> scores);
/**
* Add score to a field
* @param fscore {@link FieldScore} container for the field
* @param score Score to give
*/
protected void addScore(FieldScore<F> fscore, double score) {
if (fscore == null)
throw new NullPointerException("FieldScore was null.");
fscore.giveExtraScore(this, score);
}
/**
* Add score to a field
* @param scores {@link FieldScores} object containing the field
* @param field Field to apply score for
* @param score Score to apply
*/
protected void addScore(FieldScores<P, F> scores, F field, double score) {
FieldScore<F> fscore = scores.getScoreFor(field);
this.addScore(fscore, score);
}
/**
* Set score to an exact value for a field
* @param scores {@link FieldScores} object containing the field
* @param field Field to apply score for
* @param score Score to apply
*/
protected void setScore(FieldScores<P, F> scores, F field, double score) {
FieldScore<F> fscore = scores.getScoreFor(field);
if (fscore == null)
throw new NullPointerException("Field " + field + " does not have any matching FieldScore.");
fscore.giveExtraScore(this, score - fscore.getScore());
}
}
ScoreConfig.java:(43行,1255字节)
/**
* Interface for performing analyze work before scorers start scoring.
* @param <P> Score parameter type
*/
public interface PreScorer<P> {
/**
* Perform analyze and return result of analyze
* @param params The score parameters
* @return The object that can be retrieved by the scorers
*/
Object analyze(P params);
/**
* Method that can be used to clean-up variables and resources. Called when a {@link FieldScores} object has been fully completed.
*/
void onScoringComplete();
}
ScoreConfigFactory .java:(124行,3601字节)
/**
* Score Configuration containing instances of {@link PreScorer}, {@link PostScorer} and {@link AbstractScorer}
*
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public class ScoreConfig<P, F> {
private final ScoreSet<P, F> scorers;
private final List<PostScorer<P, F>> postScorers;
private final List<PreScorer<P>> preScorers;
public ScoreConfig(ScoreConfig<P, F> copy) {
this(copy.preScorers, copy.postScorers, copy.scorers);
}
public ScoreConfig(List<PreScorer<P>> preScorers, List<PostScorer<P, F>> postScorers, ScoreSet<P, F> scorers) {
this.postScorers = new ArrayList<PostScorer<P,F>>(postScorers);
this.preScorers = new ArrayList<PreScorer<P>>(preScorers);
this.scorers = new ScoreSet<P, F>(scorers);
}
public List<PostScorer<P, F>> getPostScorers() {
return postScorers;
}
public ScoreSet<P, F> getScorers() {
return scorers;
}
public List<PreScorer<P>> getPreScorers() {
return preScorers;
}
@Override
public String toString() {
return "Scorers:{PreScorer: " + preScorers + ", PostScorer: " + postScorers + ", Scorers: " + scorers + "}";
}
}
ScoreParameters.java :( 24行,627字节)
/**
* Factory class for creating a {@link ScoreConfig}
*
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public class ScoreConfigFactory<P, F> {
private ScoreSet<P, F> scoreSet;
private final List<PostScorer<P, F>> postScorers;
private final List<PreScorer<P>> preScorers;
public static <Params, Field> ScoreConfigFactory<Params, Field> newInstance() {
return new ScoreConfigFactory<Params, Field>();
}
public ScoreConfigFactory() {
this.scoreSet = new ScoreSet<P, F>();
this.postScorers = new LinkedList<PostScorer<P, F>>();
this.preScorers = new LinkedList<PreScorer<P>>();
}
public ScoreConfigFactory<P, F> withScoreConfig(ScoreConfig<P, F> config) {
ScoreConfigFactory<P, F> result = this;
for (PreScorer<P> pre : config.getPreScorers()) {
if (!preScorers.contains(pre))
result = withPreScorer(pre);
}
for (PostScorer<P, F> post : config.getPostScorers()) {
if (!postScorers.contains(post))
result = withPost(post);
}
for (Entry<AbstractScorer<P, F>, Double> scorer : config.getScorers().entrySet()) {
AbstractScorer<P, F> key = scorer.getKey();
double value = scorer.getValue();
if (!scoreSet.containsKey(key))
result = withScorer(key, value);
else {
scoreSet.put(key, value + scoreSet.get(key));
}
}
return result;
}
public ScoreConfigFactory<P, F> copy() {
ScoreConfigFactory<P, F> newInstance = new ScoreConfigFactory<P, F>();
return newInstance.withScoreConfig(this.build());
}
/**
* Add a scorer to this factory
* @param scorer Scorer to add
* @return This factory
*/
public ScoreConfigFactory<P, F> withScorer(AbstractScorer<P, F> scorer) {
scoreSet.put(scorer, 1.0);
return this;
}
/**
* Add a scorer with the specified weight to this factory.
* @param scorer Scorer to add
* @param weight Weight that should be applied to the scorer
* @return This factory
*/
public ScoreConfigFactory<P, F> withScorer(AbstractScorer<P, F> scorer, double weight) {
scoreSet.put(scorer, weight);
return this;
}
/**
* Multiply all current {@link AbstractScorer}s in this factory's {@link ScoreSet} weights by a factor
* @param value Factor to multiply with
* @return This factory
*/
public ScoreConfigFactory<P, F> multiplyAll(double value) {
ScoreSet<P, F> oldScoreSet = scoreSet;
scoreSet = new ScoreSet<P, F>();
for (Map.Entry<AbstractScorer<P, F>, Double> ee : oldScoreSet.entrySet()) {
scoreSet.put(ee.getKey(), ee.getValue() * value);
}
return this;
}
/**
* Add a {@link PostScorer} to this factory.
* @param post PostScorer to add
* @return This factory
*/
public ScoreConfigFactory<P, F> withPost(PostScorer<P, F> post) {
postScorers.add(post);
return this;
}
/**
* Create a {@link ScoreConfig} from this factory.
* @return A {@link ScoreConfig} for the {@link PreScorer}s, {@link PostScorer} and {@link AbstractScorer}s that has been added to this factory.
*/
public ScoreConfig<P, F> build() {
return new ScoreConfig<P, F>(this.preScorers, this.postScorers, this.scoreSet);
}
/**
* Add a {@link PreScorer} to this factory
* @param analyzer PreScorer to add
* @return This factory
*/
public ScoreConfigFactory<P, F> withPreScorer(PreScorer<P> analyzer) {
this.preScorers.add(analyzer);
return this;
}
}
Scorer.java:(6行,178字节)
/**
* Interface for retrieving analyzes and parameters that are used for scoring
* @param <P> Score parameter type
*/
public interface ScoreParameters<P> {
/**
* @param clazz The class to get the analyze for
* @return The analyze for the specified class, or null if none was found
*/
<E> E getAnalyze(Class<E> clazz);
/**
* @return Parameter object that are used in the scoring
*/
P getParameters();
/**
* @return All available analyze objects
*/
Map<Class<?>, Object> getAnalyzes();
ScoreStrategy<P, ?> getScoreStrategy();
}
ScoreSet.java :( 19行,471字节)
/**
* Marker for classes that are a part of scoring things, i.e. {@link PostScorer} and {@link AbstractScorer}.
*/
public interface Scorer { }
ScoreStrategy.java:(24行,964字节)
/**
* Map of {@link AbstractScorer}s and the weight that should be applied to them.
*
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public class ScoreSet<P, F> extends LinkedHashMap<AbstractScorer<P, F>, Double> {
private static final long serialVersionUID = 5924233965213820945L;
ScoreSet() {
}
ScoreSet(ScoreSet<P, F> copy) {
super(copy);
}
}
ScoreTools.java:(50行,1571字节)
/**
* Responsible for determining which fields to score with the specified params
* @param <P> Score parameter type
* @param <F> The type to apply scores to
*/
public interface ScoreStrategy<P, F> {
/**
* Determine the collection of fields to score given the specified params
* @param params Parameter for the scoring
* @return A collection of fields which to score
*/
Collection<F> getFieldsToScore(P params);
/**
* Determine whether or not scoring of a particular field should be done, allowing {@link PreScorer}s from the analyze to be taken into consideration.
* @param parameters Parameters, including analyze objects created by {@link PreScorer}s.
* @param field The field to score or not to score, that is the question.
* @return True to score field, false to skip scoring of it.
*/
boolean canScoreField(ScoreParameters<P> parameters, F field);
}
使用/测试
GitHub上提供了如何使用此代码的示例。有关在简单的井字游戏中如何使用此系统的示例,请参见
test.net.zomis.aiscores.ttt.TTMain
类。您也可以在我的TicTacToe Ultimate应用程序中查看该系统的运行情况。字段上的数字和颜色根据所应用的得分显示该字段的好坏(绿色为好,数字高为好)。
问题
我想知道该代码的实现方式和设计方式。我还想知道是否有一种更干净的方法来解决getAnalyze-business,因为在分析人员和记分员之间需要“多对多”的关系。
我是我主要不是在寻找有关我的格式和命名的评论,我对此感到非常满意。我使用Java 6是因为我需要支持Android。我还需要支持GWT,这使得
String.format
变得不可能。除了能够为机器人做出决策外,还可以与遗传算法配合使用,并且可以将得分应用于其他玩家的游戏方式并据此确定玩家与AI的相似程度。我还对将来如何使用该系统有一些想法。
还有更多与此系统相关的代码(例如,一种分析+得分+返回该字段的方法得分最高),但不打算进行复审(如果您想复习,请告诉我,我会提出一个问题)。
有关的相关问题该系统本身可在Computer Science Stack Exchange网站上找到。
#1 楼
AbstractScorer您确定要加倍精度才能得分吗?我之所以只问是因为最近我有理由将一个系统下变频为浮动系统,因为它有成千上万的可计分项目,而节省的内存却是可观的。
您实施的课程。 toString方法可以在调试时与您自己进行通信,而其他程序员可能不幸地需要吞噬您的堆栈跟踪。在抽象类上使用toString没有任何好处。您的toString仅给出类的简单名称,甚至比默认的
toString()
更糟糕。FieldScore
这是附加到字段的分数的累加器。它可能包含多个分数。我对它不是线程安全的感到失望。第一印象是对所有字段同时由不同记分员进行记分将是一个有用的优化。当前的
Object.toString()
无法实现。FieldScoreProducer
这里的一些文档很好。我不确定“生产者”是我描述类的方式,但是我不确定我会推荐什么。
FieldScore
方法具有analyze()
类型的泛型。应该在类上使用其他泛型类型解决此问题。对象是一个不良的声明,它表示缺少结构。Object
很奇怪。我不明白为什么它不是最终领域。 detailed
应该用构造函数上的标志替换。FieldScores
习惯上将构造函数放在类的方法之前...(以及静态声明和方法之后)。构造函数之前有一些常规方法。
setDetailed
是个问题。映射的键是private final Map<F, FieldScore<F>> scores
类型,但没有指示F
类是什么。结果,您不知道F
是否以与Map协定兼容的方式实现F
和hashCode()
。我建议您将此类声明为equals()
,以防万一。-
IdentityHashMap
我不喜欢它不是最终的。另外,您有detailed
方法,但没有setDetailed()
。应该将它作为最终构造函数的一部分来设置。在整个代码中,
isDetailed()
是各种形式的单词。我认为正确的词是analyzes
。analyzers
应该将返回值包装在更轻量的getAnalyzes()
中,而不是创建新的Collections.unmodifiableMap(....)
。HashMap
同上PostScorer
再次在抽象类上使用
getScores()
。PreScorer
toString()
PreScorer是一个接口。我希望它们互为镜像,但不是。方法
PostScorer is an abstract class, but
应该类似于onScoringComplete
,对吗?分析应该返回某种通用类型,而不是
onPreScoringComplete
。我希望
Object
和PreScorer
上的方法彼此相似,但它们根本不...。再次,我希望
PostScorer
上的<P, F>
签名更加复杂...没有它,它做错什么了吗?ScoreConfig
在所有其他方法中,您都小心返回了存储结构的副本,但是在此类中,您返回了基本价值观。该方法应该使用
PreScorer
还是Collections.unmodifiableMap(....)
? /> ScoreSet 因为什么时候才有意义?
public class ScoreSet<P, F> extends LinkedHashMap<AbstractScorer<P, F>, Double>
??? ;-)
ScoreTools
方法
new HashMap(....)
创建一个匿名比较器。比较器是线程安全的,并且可以重新输入,因此,每次调用该方法都创建一个比较器是浪费时间。像这样: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
}
}
在进行上述最后一个处理的过程中,在我看来,比较器是错误的处理方式:
if (descending) res = e1.getValue().compareTo(e2.getValue());
else res = e2.getValue().compareTo(e1.getValue());
此行高度可疑...这使比较器不可传递。使用比较器时,
entriesSortedByValues
应该与compare(a, b)
是相同的符号,但是,在您的情况下,如果是-compare(b,a)
,则在两种情况下您的代码都将返回1。 br /> 您的代码可能会因“比较方法违反其一般约定!”而失败。考虑使用
a.equals(b)
结论
当前看来是可行的,但是在成为多线程应用程序时却缺少一些细节。我在这方面看到了很多好处。
代码很好,并且可以很好地结合在一起。
#2 楼
几天前,我开始审查代码,看来rolfl更快。无论如何,还有其他一些想法,包括在github仓库中的东西。构建系统:将项目加载到Eclipse花费了一段时间。没有适用于Maven的
.project
文件也没有pom.xml
。我建议您使用构建工具,Maven和Gradle在当今非常流行。它可以为其他开发人员提供很多帮助,因为它可以为他们启动应用程序(或至少创建一个可以通过常规java -jar
命令启动的jar。)令人困惑的是
test
目录包含示例。我希望在那里进行单元测试。另请参阅:Maven的标准目录布局-许多对此熟悉的开发人员。AbstractScorer
可能是一个接口。仅对于toString
和return true
就是一种滥用继承的简单方法。我会在这里考虑对
null
进行检查:public FieldScore(final F field, final boolean detailed) {
this.field = field;
FieldScores.detailed
:我会考虑将此职责移至另一个类(可能带有装饰器模式)。标志参数闻起来有点:Robert C. Martin的清洁代码,标志参数,p41:
标志参数很丑陋。将布尔值传递给函数是一种非常糟糕的做法。它立即使该方法的签名
复杂化,大声地宣布此函数的作用不只是一件事。如果标志为true则执行一件事,而
标志为false则执行另一件事!
这使我想起时间耦合:
FieldScores<P, F> scores = new FieldScores<P, F>(params, config, scoreStrategy);
scores.setAnalyzes(analyzes);
scores.determineActiveScorers();
scores.calculateScores();
scores.rankScores();
scores.postHandle();
清洁代码,作者Robert C. Martin,G31:隐藏的时间性联轴器
评论
\ $ \ begingroup \ $
您建议应改为调用什么目录?例子会更好吗?
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年3月27日在17:44
\ $ \ begingroup \ $
非常感谢您的审核,我将考虑您的意见! ham愧我忘了将.project添加到git中
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年3月27日在17:44
\ $ \ begingroup \ $
@SimonAndréForsberg:是的,我会举一些例子。
\ $ \ endgroup \ $
–palacsint
2014年3月27日在18:25
#3 楼
目前,我只评论一个部分,恕我直言,这是非常危险的,应该得到它:ScoreTools
public static <K, V extends Comparable<? super V>> SortedSet<Map.Entry<K, V>> entriesSortedByValues(Map<K, V> map, final boolean descending) {
SortedSet<Map.Entry<K, V>> sortedEntries = new TreeSet<Map.Entry<K, V>>(
new Comparator<Map.Entry<K, V>>() {
@Override
public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) {
int res;
if (descending) 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
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
您的特殊修复很容易导致地图找不到任何东西。如rolfl所述,您的
Comparator
是非传递性的。但这不是反对称的,也不是自反的。因此,您违反了合同的每个要点。假设它现在可以正常工作,则随时可能中断。如果没有
e.compare(e)
返回非零值,则无法找到条目,只能使用==
,这是当前TreeMap
版本中可能包含的优化。当您想要保留这样的
Comparator
时,可以做得更好: /> int res;
if (descending) res = e1.getValue().compareTo(e2.getValue());
else res = e2.getValue().compareTo(e1.getValue());
if (res != 0 || e1.equals(e2)) {
return res;
}
确保反射性。现在,我们不在乎结果,而是需要诸如传递性和反对称性之类的东西。我们使用已有的东西
res = Integer.compare(e1.getKey().hashCode(), e2.getKey().hashCode());
if (res != 0 || e1.equals(e2)) {
return res;
}
到目前为止,我们获得的机会很少,但非零。
res = Integer.compare(e1.getValue().hashCode(), e2.getValue().hashCode());
if (res != 0 || e1.equals(e2)) {
return res;
}
现在,我们无可比拟了。厄运。您可以确定它不太可能,并且允许丢失一些条目。您现在可以使用丑陋的骇客了。您也可以使用
if (!uniqueIdMap.contains(e1)) {
uniqueIdMap.put(e1, uniqueIdMap.size());
}
if (!uniqueIdMap.contains(e2)) {
uniqueIdMap.put(e2, uniqueIdMap.size());
}
return uniqueIdMap.get(e1) - uniqueIdMap.get(e2);
这样的硬路线,使用
private Map<Map.Entry<K, V>, Integer> uniqueIdMap;
这里不必担心性能,因为双重哈希冲突的可能性非常低。您可能需要使用
WeakHashMap
来避免内存泄漏,或者使用ConcurentHashMap
来避免内存泄漏。您可能希望懒惰地创建它,因为它需要的机会很少。如果您的对象不在乎
putIfAbsent
,则可以使用Guava equals
,它是一个平局决胜者(并替换了我上面所有的冗长代码) )。
评论
\ $ \ begingroup \ $
我不同意提出IdentityHashMap。要么密钥实现正确等于相等,然后正常的HashMap就可以了。或者他们继承Object :: equals,然后HashMap就像IdentityHashMap一样工作,这也很好,因为省略equals就像说默认值很好。还是坏了,或者是另外一个故事。 +++ IMHO IdentityHashMap仅在您确实想忽略潜在的现有equals时使用。
\ $ \ endgroup \ $
– maaartinus
15年7月29日在6:15