Skip to main content
deleted 142 characters in body
Source Link
gboffi
  • 273
  • 1
  • 8
Source Link
gboffi
  • 273
  • 1
  • 8

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) enter image description here 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.