Memory-Bounded Ranked Ballot Counter
Millions of ranked ballots have to be counted without ever holding them all in memory: votes are read in batches, and memory must stay proportional to the number of candidates.
A ballot is a list of candidate names, most preferred first. Score it Borda-style: a ballot of n names gives n - 1 points to its first name, n - 2 to the second, and so on down to 0 for the last. Implement RankedBallotCounter(batchSize):
add(ballot)— buffer the ballot; as soon as the buffer holdsbatchSizeballots, flush it into the running totals and empty it. Never keep every ballot.extend(ballots)—addeach one.results(topK?)— flush whatever is buffered, then return[[candidate, score], ...]sorted by score descending, then name ascending;topKtruncates when given.firstPlaceCounts()— flush, then return[[candidate, ballotsRankingThemFirst], ...]with the same ordering.stats()—{ ballotsSeen, batchesFlushed, buffered }without flushing: ballots counted so far by flushes, the number of non-empty flushes, and how many ballots are still buffered.
Every candidate that appears on any ballot appears in results(), even with a score of 0. An empty ballot is a ballot: it counts toward ballotsSeen and scores nobody.
counter = RankedBallotCounter(10) counter.add(["alice", "bob", "carol"]) // alice 2, bob 1, carol 0 counter.add(["bob", "alice", "carol"]) // bob 2, alice 1 counter.add(["alice", "carol", "bob"]) // alice 2, carol 1 counter.results() -> [["alice", 5], ["bob", 3], ["carol", 1]]
Hints
Hint 1
The score is a sum over ballots, so the state you need is one counter per candidate — O(candidates), independent of how many ballots arrive. The buffer exists only to model batch processing; flushing it means folding each buffered ballot into the counters and clearing it.
Hint 2
Sort the results with a compound key: score descending, then name ascending. Deterministic tie-breaks matter more in an evaluation harness than anywhere else, because a flaky ranking looks like a regression.