/
vote.py
143 lines (103 loc) · 4.95 KB
/
vote.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
from csv import reader
from collections import defaultdict
from six import next
from argparse import ArgumentParser
TEST_MODE = False
class BookRanker:
def __init__(self, inputFile, colsToDelete, question):
self.tallies = {}
self.inputFile = inputFile
self.colsToDelete = colsToDelete
self.question = question
class TallyObj:
def __init__(self, title, tallies):
self.title = title
self.tallies = tallies
def __gt__(self, other):
if TEST_MODE:
print()
print(self.title)
print(other.title)
def compareTallies(lastPlace, scoreSelf, scoreOther):
while lastPlace > 1:
# Kind of treating rank as "displeasure points". Not quite
# sure if that works perfectly, but it works well enough
scoreSelf += self.tallies[lastPlace]
scoreOther += other.tallies[lastPlace]
if scoreSelf != scoreOther:
if TEST_MODE:
print("Found after scoring with {place}".format(place=lastPlace))
return scoreSelf > scoreOther
lastPlace -= 1
# If their scores are equal, just go alphabetically
if TEST_MODE:
print("Found alphabetically")
return self.title > other.title
if not isinstance(other, type(self)):
return False
lastPlaceSelf = max(self.tallies, key=int)
lastPlaceOther = max(other.tallies, key=int)
# The idea is to pick books that everyone can tolerate reasonably
# well, so the "loser" is the book that's most hated by anyone
if lastPlaceSelf != lastPlaceOther:
if TEST_MODE:
print("found by last place")
return lastPlaceSelf > lastPlaceOther
startingScoreSelf = self.tallies[lastPlaceSelf]
startingScoreOther = other.tallies[lastPlaceOther]
if startingScoreSelf != startingScoreOther:
if TEST_MODE:
print("found by last place tally")
return startingScoreSelf > startingScoreOther
return compareTallies(lastPlaceSelf - 1, startingScoreSelf,
startingScoreOther)
def vote(self):
with open(self.inputFile) as rankFile:
resultReader = reader(rankFile)
header = next(resultReader)
# Date column is unnecessary
for col in self.colsToDelete:
header.pop(col)
for title in header:
self.tallies[title] = defaultdict(int)
for row in resultReader:
# Discarding date column again
for col in self.colsToDelete:
row.pop(col)
# Keeps track of how many nth place votes each title has, of the
# form {title: {n: tally}} where n is some rank (like 1 for first
# place)
for idx, rank in enumerate(row):
self.tallies[header[idx]][int(rank)] += 1
tallyObjs = []
for title, tallyDict in self.tallies.items():
# Get rid of Microsoft Form question title which shows up in every
# entry for some reason...
tallyObjs.append(self.TallyObj(title.replace(self.question, ""),
tallyDict))
# Abuse the overloaded operator to figure out the ordering
tallyObjs = sorted(tallyObjs)
tallyObjs.reverse()
n = 1
while tallyObjs:
print("BOOK {N}:".format(N=n))
print("\t{TITLE}\n".format(TITLE=tallyObjs.pop().title)
.replace("[", "").replace("]", ""))
n += 1
if __name__ == "__main__":
parser = ArgumentParser(description="This is a script to order book club "
"books given a CSV of ranked choice "
"votes per participant")
parser.add_argument('-i', '--input',
help='Input CSV (will be rankings.csv by default)',
default="rankings.csv")
parser.add_argument('-c', '--columns',
help='Column indices to ignore in input file (will be 0 by default)',
type=int, nargs='+', default=[0])
parser.add_argument('-q', '--question',
help='Question string from the form to delete from titles'
' (will be \'Please rank your choices \' by default)',
default="Please rank your choices ")
args = parser.parse_args()
BookRanker(inputFile=args.input, colsToDelete=args.columns,
question=args.question).vote()