Track article votes and last three flips
Company: Rippling
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates a developer's ability to design in-memory data structures that support constant-time vote updates, score aggregation, and recent flip-event tracking while handling vote-change semantics.
Constraints
- 0 <= len(operations) <= 2 * 10^5
- Each operation is either ["vote", user_id, article_id, new_vote], ["getScore", article_id], or ["getLast3Flips", article_id]
- user_id and article_id are non-empty strings, and new_vote is one of {"UP", "DOWN", "NONE"}
- Your approach should run in O(1) average time per operation
Examples
Input: [['vote', 'u1', 'a1', 'UP'], ['vote', 'u2', 'a1', 'DOWN'], ['getScore', 'a1'], ['vote', 'u1', 'a1', 'DOWN'], ['getLast3Flips', 'a1'], ['vote', 'u2', 'a1', 'UP'], ['vote', 'u3', 'a1', 'UP'], ['vote', 'u3', 'a1', 'DOWN'], ['getScore', 'a1'], ['getLast3Flips', 'a1']]
Expected Output: [0, [['u1', 'UP', 'DOWN', 1]], -1, [['u1', 'UP', 'DOWN', 1], ['u2', 'DOWN', 'UP', 2], ['u3', 'UP', 'DOWN', 3]]]
Explanation: The first score is 0 because one user voted UP and one voted DOWN. Then three flips happen globally on the same article with sequence numbers 1, 2, and 3. The final score is -1.
Input: [['vote', 'u1', 'a2', 'DOWN'], ['getScore', 'a2'], ['getLast3Flips', 'a2'], ['vote', 'u1', 'a2', 'NONE'], ['getScore', 'a2'], ['vote', 'u1', 'a2', 'DOWN'], ['vote', 'u1', 'a2', 'UP'], ['getLast3Flips', 'a2']]
Expected Output: [-1, [], 0, [['u1', 'DOWN', 'UP', 1]]]
Explanation: Changing from DOWN to NONE is not a flip. After voting DOWN again and then UP, there is exactly one flip event.
Input: [['vote', 'u1', 'a1', 'UP'], ['vote', 'u1', 'a1', 'UP'], ['vote', 'u1', 'a1', 'NONE'], ['vote', 'u1', 'a1', 'NONE'], ['getScore', 'a1'], ['getLast3Flips', 'a1'], ['getScore', 'missing'], ['getLast3Flips', 'missing']]
Expected Output: [0, [], 0, []]
Explanation: Repeatedly voting the same value does nothing. Moving to or from NONE is not a flip. Unseen articles return score 0 and no flips.
Input: [['vote', 'u1', 'a1', 'UP'], ['vote', 'u1', 'a1', 'DOWN'], ['vote', 'u2', 'a2', 'DOWN'], ['vote', 'u2', 'a2', 'UP'], ['vote', 'u3', 'a1', 'DOWN'], ['vote', 'u3', 'a1', 'UP'], ['vote', 'u4', 'a1', 'UP'], ['vote', 'u4', 'a1', 'DOWN'], ['vote', 'u5', 'a1', 'DOWN'], ['vote', 'u5', 'a1', 'UP'], ['getLast3Flips', 'a1'], ['getLast3Flips', 'a2'], ['getScore', 'a1'], ['getScore', 'a2']]
Expected Output: [[['u3', 'DOWN', 'UP', 3], ['u4', 'UP', 'DOWN', 4], ['u5', 'DOWN', 'UP', 5]], [['u2', 'DOWN', 'UP', 2]], 0, 1]
Explanation: Flip sequence numbers are global across both articles. Article a1 has 4 flips total, so only its last 3 are returned: sequences 3, 4, and 5.
Input: []
Expected Output: []
Explanation: No operations means there are no query results.
Solution
def solution(operations):
from collections import defaultdict, deque
def delta(vote):
if vote == 'UP':
return 1
if vote == 'DOWN':
return -1
return 0
current_vote = {}
article_score = defaultdict(int)
article_flips = defaultdict(lambda: deque(maxlen=3))
flip_sequence = 0
answers = []
for op in operations:
kind = op[0]
if kind == 'vote':
_, user_id, article_id, new_vote = op
key = (user_id, article_id)
old_vote = current_vote.get(key, 'NONE')
if new_vote == old_vote:
continue
article_score[article_id] += delta(new_vote) - delta(old_vote)
if old_vote != 'NONE' and new_vote != 'NONE' and old_vote != new_vote:
flip_sequence += 1
article_flips[article_id].append([user_id, old_vote, new_vote, flip_sequence])
if new_vote == 'NONE':
current_vote.pop(key, None)
else:
current_vote[key] = new_vote
elif kind == 'getScore':
_, article_id = op
answers.append(article_score.get(article_id, 0))
elif kind == 'getLast3Flips':
_, article_id = op
answers.append([event[:] for event in article_flips.get(article_id, [])])
else:
raise ValueError(f'Unknown operation: {op}')
return answersTime complexity: O(n), where n is the number of operations. Space complexity: O(n) in the worst case for stored vote state and per-article metadata.
Hints
- Map votes to numbers: UP = +1, DOWN = -1, NONE = 0. Then a vote update changes the score by value(new_vote) - value(old_vote).
- Store the current vote for each (user_id, article_id) pair, and keep a fixed-size queue/deque of at most 3 flip events per article.