I'm curious how New York City will physically and computationally implement ranked-choice vote counting.
Physically, voters received a tabular ballot (see this NYC government page)
where rows are candidates and columns are order of preference, and the ballots are scanned to obtain
varying length records (the voter can express 1÷5 choices) containing the voted candidates in order of preference.
Computationally, NYC has a more fail-safe version of this (relatively) simple Python script
from random import seed, shuffle
from timeit import default_timer as dt
seed(20210622)
n_candidates, n_choices, n_ballots = 14, 5, 10**6
# ##### generate the ballots
start_generation = dt()
candidates = list(range(n_candidates))+[0] # introducing a bias for candidate 0
ballots = [candidates[:n_choices] for _ in range(n_ballots)
if not shuffle(candidates)]
print("\nGeneration of %d ballots required %.2f s"%(
n_ballots, dt()-start_generation))
# ##### process the ballots
start_count = dt()
while True:
counts = {candidate:0 for candidate in candidates}
# count 1st choices
for ballot in ballots: counts[ballot[0]] += 1
# sort candidates according to 1st choices
ranks = sorted(list(counts.items()), key=lambda t:t[1])
# max no. of 1st choices
max_1st = ranks[-1][1]
# if there is a winner, stop counting
if max_1st > len(ballots)//2: break
# who is the loser?
loser = ranks[0][0]
# remove loser from ballots and from list of candidates
for ballot in ballots:
if loser in ballot: ballot.remove(loser)
candidates.remove(loser)
# remove any empty ballot from list of ballots
# (copying not empty ballots is WAY FASTER than deleting void ballots)
ballots = [ballot for ballot in ballots if ballot]
# we have a winner
print(*reversed(ranks), sep='\n')
print("Counting %d ballots required %.2f s"%(
n_ballots, dt()-start_count))
When I ran it on my old, low end notebook I got the following output
Generation of 1000000 ballots required 9.29 s
(0, 490711)
(12, 245897)
Counting 1000000 ballots required 6.08 s
Even in this simulation it is apparent that the real problem is preparing the data in a format that is suitable for counting, then it's a matter of seconds…
As a foot note, initially I thought I had to optimize the script using vector math libraries but it turned out it is not necessary.