【问题标题】:Data-structure sorted by value按值排序的数据结构
【发布时间】:2015-08-13 15:43:58
【问题描述】:

我正在尝试为大量玩家存储分数。所以我需要一张根据值排序的地图,但由于玩家数量众多,每次检索它时对其进行排序是低效的。我还希望能够在地图中找到玩家排名。它与 redis 中的 score 数据类型非常相似。 比如:

    ScoreMap<String, Integer> scores = new ScoreMap<String, Integer>();

    scores.put("Bill", 2);
    scores.put("Tom", 6);
    scores.put("Jim", 3);
    scores.put("Jake", 3);


    System.out.println("Rank = " + scores.getRank("Bill"));

    System.out.println();
    System.out.println("All:");
    for (Entry<String, Integer> entry : scores.entrySet()) {
        System.out.println(entry.getKey() + " => " + entry.getValue());
    }

    System.out.println();
    System.out.println("Rank Range:");
    for (Entry<String, Integer> entry : scores.entryRankRange(0, 2)) {
        System.out.println(entry.getKey() + " => " + entry.getValue());
    }

    System.out.println();
    System.out.println("Score Range:");
    for (Entry<String, Integer> entry : scores.entryScoreRange(2, 3)) {
        System.out.println(entry.getKey() + " => " + entry.getValue());
    }

这会返回

    Rank = 3

    All:
    Tom => 6
    Jake => 3
    Jim => 3
    Bill => 2

    Rank Range:
    Tom => 6
    Jake => 3
    Jim => 3

    Score Range:
    Jake => 3
    Jim => 3
    Bill => 2

我知道这有点具体,我可能不得不制作一个自定义数据结构。但是,在正确的方向上的一点将不胜感激。 :)

【问题讨论】:

标签: java data-structures


【解决方案1】:

你能不能把你的名字和分数改写成职业玩家,然后你可以用TreeSet来保持顺序,并覆盖equals()和compareTo(),看起来你想用名字作为标识符,让玩家保持有序按分数,那么你可以做

@Override
public boolean equals(Object obj) {
    if(obj instanceof Player)
        return this.name.equals(((Player)obj).name);
    return false;
}
@Override
public int compareTo(Player p) {
    return p.score - this.score;
}

【讨论】:

  • 如果覆盖equals,不覆盖hashCode不是一个好主意。
【解决方案2】:

最简单的方法是使用Set(即TreeSet)并将玩家信息(包括得分)封装到特定的类中:

public class CompetitivePlayer implements Comparable<CompetitivePlayer>{

    private String name;
    private int score;    

    public CompetitivePlayer(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    public void incrementScore() {
        score++;
    }

    @Override
    public int compareTo(CompetitivePlayer o) {
        return score - o.score;
    }
}

TreeSet 假定存储在其中的整体实现了Comparable 接口以确定其元素的自然顺序。

编辑:

如果您需要经常修改玩家的分数,那么基于Map&lt;String, Integer&gt; 的解决方案更合适,因为there's no get in Java's SetThis thread 详细讨论了基于 Map 的方法。

使用Guava 库的一个简单的解决方案(如提到的线程中所建议的)是单行的:

Map<String, Integer> sortedScores = ImmutableSortedMap.copyOf(scores,
    Ordering.natural().onResultOf(Functions.forMap(scores)));

【讨论】:

  • 如果你这样做,你会如何改变球员的得分? (不遍历整个事情,然后删除旧的)
  • 此示例实现仅公开了将分数加一的incrementScore 方法。如果您经常需要更改玩家的得分,那么Map 可能更合适,因为(不幸的是)you cannot get elements from Java Sets。您的具体要求是什么?
  • 啊,是的,我需要能够经常更改分数。我想要的是类似于 TreeMap 但基于值而不是键排序的东西。
  • 我用一个使用 Guava 库的示例编辑了我的答案。如果您的播放器除了名称之外没有其他属性并且您不想包含任何外部库,那么@BruceWayne 的答案看起来是一个不错的解决方案。
  • 我提供的 Guava 示例在不同玩家的分数相同的情况下将不起作用(您会得到 java.lang.IllegalArgumentException: Multiple entries with same key: Jake=3 and Jim=3)。我明天会尝试解决这个问题。
【解决方案3】:

使用Collections.sort() 并提供自定义Comparator根据地图的值对地图进行排序:

Collections.sort( map, new Comparator<Map.Entry<K, V>>()
{
    @Override
    public int compare( Map.Entry<K, V> entry1, Map.Entry<K, V> entry2)
    {
        return (entry1.getValue()).compareTo( entry2.getValue() );
    }
} );

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-22
    • 2013-06-19
    • 2015-08-09
    • 1970-01-01
    • 2016-02-23
    • 2012-05-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多