Retrieve fixed number of entries around an entry i

2019-05-06 21:40发布

问题:

The POJO viz. Entry.java represents an entry in the leaderboard. 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;

@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;

        if (!(obj instanceof Entry))
            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 + "]";
    }
}

These entries are stored in a Map in a class as shown below :

public class GameDefault {

Map<String, Entry> leaderBoardUserEntryMap;

public void submitScore(String uid, int score) {

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

public List<Entry> getLeaderBoard(String uid) {

    /* Option-3 : A Map of uid-Entry */
    leaderBoardUserEntryMap.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.comparing(Entry::getScore, Integer::compare).reversed()))
                .filter(/*What to put here*/);

        return null;
    }
}

The method getLeaderBoard() is supposed to return

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

.

I couldn't figure out the predicate to use to return exactly 5 entries, including the one being searched for. Another aspect is performance since the leaderBoard can have hundreds of thousands of entries.

**********Edit-1**********

The following snippet provided by @nullpointer does the trick but I have some points on my mind

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

int indexOfnewEntry = selectedEntries.indexOf(leaderBoardUserEntryMap.get(uid));
return  selectedEntries.subList(indexOfnewEntry-2,indexOfnewEntry+2);

Note : The leaderBoardUserEntryMap can have millions of entries

  • The indexOfnewEntry and +- 2 can cause IndexOutOfBoundsException, guarding against it seems a bit tedious, any optimal ways here?

  • Will using parallelStream() cause problems ?

    List entries = leaderBoardUserEntryMap.entrySet().parallelStream().sorted(Map.Entry.comparingByValue(Comparator.comparing(Entry::getScore, Integer::compare).reversed())).parallel(). map(Map.Entry::getValue).collect(Collectors.toList());

回答1:

Stream#limit shall help you restrict finding the top N(5) users on the reversed list you've created and further you can map List> using the values and collect a List<Entry> from it finally as:

return leaderBoardUserEntryMap.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(Comparator.comparing(Entry::getScore, Integer::compare).reversed()))
            .limit(5).map(Map.Entry::getValue).collect(Collectors.toList());

Edit: thanks to @Yogesh for the use case

say there are 100 users, and the user that is being searched is at 93. List should return 91, 92, 93, 94, 95. This solution will return 1, 2, 3, 4, 5

Since the use case is to have a subList around the current entry, this could be modified as:

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

int indexOfnewEntry = selectedEntries.indexOf(leaderBoardUserEntryMap.get(uid));
return  selectedEntries.subList(indexOfnewEntry-2,indexOfnewEntry+2);

Edit 2:

The indexOfnewEntry and +- 2 can cause IndexOutOfBoundsException, guarding against it seems a bit tedious, any optimal ways here?

Since the index of the entry might vary on the score, and the subList access further also relies on the number of output desired before/after it. Guarding shall be a better option than any other. Also what could be considered is a customSubList implementation which could internally check your collection type. How to use subList() explains this with top voted answers. I specifically liked this one though :

dataList.subList(Math.max(0, first), Math.min(dataList.size(), last) );

Will using parallelStream() cause problems?

Unless there are any synchronized blocks executed that might alter and make concurrent updates to the stream it wouldn't cause any problems.

But you should know when to use parallel stream - Should I always use a parallel stream when possible?