Store entries(custom pojo) in sorted order and ret

2019-07-22 13:35发布

问题:

I am trying to simulate a game board where multiple players can submit their game scores.

The POJO viz. Entry.java represents an entry in the leaderboard. Note the overriden equals() method.

Position is the position in the leaderboard, 1 being the user with the highest score

public class Entry {

private String uid;
private int score;
private int position;

public Entry(String uid, int score) {

    this.uid = uid;
    this.score = score;

}

public Entry() {

}

public String getUid() {
    return uid;
}

public void setUid(String uid) {
    this.uid = uid;
}

public int getScore() {
    return score;
}

public void setScore(int score) {
    this.score = score;
}

public int getPosition() {
    return position;
}

public void setPosition(int position) {
    this.position = position;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + score;
    result = prime * result + ((uid == null) ? 0 : uid.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Entry other = (Entry) obj;
    if (score != other.score)
        return false;
    if (uid == null) {
        if (other.uid != null)
            return false;
    } else if (!uid.equals(other.uid))
        return false;
    return true;
}

@Override
public String toString() {
    return "Entry [uid=" + uid + ", score=" + score + ", position=" + position + "]";
}

}

The GameBoard class has a couple of methods :

  • submitScore(String uid, int score) Each player calls this method to submit his score to the game board. There is only one entry per player/user, hence, if a player calls this method multiple times, his latest score is stored
  • getLeaderBoard(String uid)

If the user is in the leaderboard, returns max two entries that have larger score than the user (the users that are immediately above the user in the leaderboard), the user’s own entry and max two entries that are immediately after the user in the leaderboard

e.g:

The leader board is :
Entry [uid=user1, score=14, position=1]
Entry [uid=user2, score=8, position=2]
Entry [uid=user3, score=7, position=3]
Entry [uid=user4, score=7, position=3]
Entry [uid=user5, score=4, position=4]
Entry [uid=user6, score=3, position=5]
Entry [uid=user7, score=3, position=5]
Entry [uid=user8, score=1, position=6]

For user5, entries returned should be :
Entry [uid=user3, score=7, position=3]
Entry [uid=user4, score=7, position=3]
Entry [uid=user5, score=4, position=4]
Entry [uid=user6, score=3, position=5]
Entry [uid=user7, score=3, position=5]

For user4, entries returned should be :
Entry [uid=user1, score=14, position=1]
Entry [uid=user2, score=8, position=2]
Entry [uid=user4, score=7, position=3]
Entry [uid=user5, score=4, position=4]
Entry [uid=user6, score=3, position=5]

For user6, entries returned should be :
Entry [uid=user4, score=7, position=3]
Entry [uid=user5, score=4, position=4]
Entry [uid=user6, score=3, position=5]
Entry [uid=user8, score=1, position=6]

For user7, entries returned should be :

Entry [uid=user4, score=7, position=3]
Entry [uid=user5, score=4, position=4]
Entry [uid=user7, score=3, position=5]
Entry [uid=user8, score=1, position=6]

I decided to use HashMap for the leader board.

public class GameDefault {

    private Map<String, Entry> leaderBoardUserEntryMap;

    {
        leaderBoardUserEntryMap = new HashMap<>();
    }


    public void submitScore(String uid, int score) {

        Entry newEntry = new Entry(uid, score);
        leaderBoardUserEntryMap.put(uid, newEntry);
    }

    public List<Entry> getLeaderBoard(String uid) {

        if (!leaderBoardUserEntryMap.containsKey(uid))
            return Collections.emptyList();

        List<Entry> entriesOptionOne = leaderBoardUserEntryMap.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.comparing(Entry::getScore, Integer::compare).reversed()))
                .map(Map.Entry::getValue).collect(Collectors.toList());

        System.out.println("---------Current leader board---------");
        entriesOptionOne.forEach(System.out::println);

        if (entriesOptionOne != null && !entriesOptionOne.isEmpty()) {
            int indexOfUserEntry = entriesOptionOne.indexOf(leaderBoardUserEntryMap.get(uid));
            return CollectionUtil.getSubList(entriesOptionOne, indexOfUserEntry - 2, indexOfUserEntry + 3);
        }

        return Collections.emptyList();
    }

}

Now, there are following issues and concerns :

  • The program doesn't return proper entries since I am using indexes and subList, what is required is like 'higherKey(key)' & 'lowerKey(key)' in TreeMap
  • When(ideally, during submitScore()) and how shall the 'position' be calculated.Though it's used for keys, I was wondering if Map.compute() can help in any way !
  • Since the leader board can have even a million entries, sorting and then retrieving on every call to getLeaderBoard() seems infeasible. I tried using a a SortedMap of Entry-Uid(TreeMap) but the equals() contract in the SortedMap doc is a challenge

Note that the ordering maintained by a sorted map (whether or not an explicit comparator is provided) must be consistent with equals if the sorted map is to correctly implement the Map interface. (See the Comparable interface or Comparator interface for a precise definition of consistent with equals.) This is so because the Map interface is defined in terms of the equals operation, but a sorted map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior of a tree map is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Map interface.