ArticleVoteService

 avatar
unknown
java
23 days ago
3.5 kB
4
Indexable
import java.util.*;

public class ArticleVoteService {
    private int nextArticleId = 1;
    private Map<Integer, String> articles = new HashMap<>();
    // userId -> (articleId -> vote: 1=UP, -1=DOWN)
    private Map<Integer, Map<Integer, Integer>> userVotes = new HashMap<>();
    // userId -> recently flipped articleIds (insertion order, max 3)
    private Map<Integer, LinkedHashSet<Integer>> userFlips = new HashMap<>();
    // articleId -> score
    private Map<Integer, Integer> articleScores = new HashMap<>();
    // score -> set of articleIds
    private TreeMap<Integer, Set<Integer>> scoreIndex = new TreeMap<>();

    public int addArticle(String title) {
        int id = nextArticleId++;
        articles.put(id, title);
        return id;
    }

    public void upvoteArticle(int articleId, int userId) {
        vote(articleId, userId, 1);
    }

    public void downvoteArticle(int articleId, int userId) {
        vote(articleId, userId, -1);
    }

    private void vote(int articleId, int userId, int newVote) {
        userVotes.putIfAbsent(userId, new HashMap<>());
        Map<Integer, Integer> voteMap = userVotes.get(userId);
        Integer oldVote = voteMap.get(articleId);
        // calculate score delta
        int delta = 0;
        if (oldVote == null) {
            delta = newVote;
        } else if (oldVote != newVote) {
            delta = newVote - oldVote; // flip: +2 or -2;
        } else {
            return; // same vote, do nothing
        }

        // update flip tracking
        if (oldVote != null && oldVote != newVote) {
            userFlips.putIfAbsent(userId, new LinkedHashSet<>());
            LinkedHashSet<Integer> flips = userFlips.get(userId);
            flips.remove(articleId);
            flips.add(articleId);
            if (flips.size() > 3) {
                Iterator<Integer> it = flips.iterator();
                it.next();
                it.remove();
            }
        }
        
        // calculate new score
        int oldScore = articleScores.getOrDefault(articleId, 0);
        int newScore = oldScore + delta;

        // remove from old score
        if (scoreIndex.containsKey(oldScore)) {
            scoreIndex.get(oldScore).remove(articleId);
            if (scoreIndex.get(oldScore).isEmpty()) {
                scoreIndex.remove(oldScore);
            }
        }

        // add to new score 
        scoreIndex.putIfAbsent(newScore, new HashSet<>());
        scoreIndex.get(newScore).add(articleId);

        articleScores.put(articleId, newScore);
        voteMap.put(articleId, newVote);
    }

    public List<String> printLastThreeFlips(int userId) {
        List<String> result = new ArrayList<>();
        LinkedHashSet<Integer> flips = userFlips.get(userId);
        if (flips == null) return result;

        List<Integer> flipList = new ArrayList<>(flips);
        for (int i = flipList.size() - 1; i >= 0; i--) {
            result.add(articles.get(flipList.get(i)));
        }
        return result;
    }

    // Time Complexity normal case O(K), worst case O(N)
    public List<String> getTopK(int k) {
        List<String> result = new ArrayList<>();
        // TreeMap is asending, so iterate from tail
        for (int score : scoreIndex.descendingKeySet()) {
            for (int articleId : scoreIndex.get(score)) {
                result.add(articles.get(articleId));
                if (result.size() == k) {
                    return result;
                }
            }
        }
        return result;
    }
}
Editor is loading...
Leave a Comment