|
28 | 28 | 27, 40, 17, 4
|
29 | 29 | 1, 1, 3, 34
|
30 | 30 | 3, 19, 7, 4
|
31 |
| -
|
32 |
| -To Run: |
33 |
| -
|
34 |
| - Lindsays-MBP:~ Lindsay$ .../ranked_match.py PATH_TO_CSV_FILE |
35 | 31 | '''
|
36 | 32 |
|
37 | 33 |
|
| 34 | +import argparse |
| 35 | +import collections |
38 | 36 | import csv
|
39 | 37 | import sys
|
40 | 38 | import os
|
41 | 39 |
|
42 | 40 |
|
43 |
| -class Student: |
44 |
| - def __init__(self, name, prefs, rank): |
45 |
| - self.name = name |
46 |
| - self.prefs = [p for p in prefs if p] |
47 |
| - self.rank = rank |
48 |
| - self.topic = '' |
49 |
| - self.choice = 0 |
50 |
| - |
51 |
| - def __str__(self): |
52 |
| - return '%s %r' % (self.name, self.prefs) |
| 41 | +Result = collections.namedtuple('Result', ['name', 'topic', 'choice']) |
53 | 42 |
|
54 | 43 |
|
55 | 44 | def parseInteger(s):
|
56 |
| - s = s.replace('\xCA', '').strip() |
| 45 | + s = s.strip('\uFEFF').strip().strip('\xCA') |
57 | 46 | try:
|
58 | 47 | return int(s)
|
59 | 48 | except ValueError:
|
60 |
| - if len(s): |
61 |
| - raise |
62 |
| - return None |
| 49 | + return s if len(s) else None |
63 | 50 |
|
64 | 51 |
|
65 | 52 | def parseCSVFile(infile):
|
66 | 53 | reader = csv.reader(infile)
|
67 |
| - names = [n.strip().strip('\uFEFF') for n in next(reader)] |
68 |
| - num_names = len(names) |
69 |
| - inverse_prefs = [] |
| 54 | + data = [] |
70 | 55 | for line in reader:
|
71 |
| - assert len(line) <= num_names |
72 | 56 | line = [parseInteger(x) for x in line]
|
73 |
| - if not any(line): |
74 |
| - continue |
75 |
| - if len(line) < num_names: |
76 |
| - line.extend([None for x in range(len(line), num_names)]) |
77 |
| - inverse_prefs.append(line) |
78 |
| - return (names, inverse_prefs) |
79 |
| - |
80 |
| - |
81 |
| -def rangedMatch(names, inverse_prefs): |
82 |
| - all_prefs = [list(x) for x in zip(*inverse_prefs)] |
83 |
| - |
84 |
| - number_topics = max(topic for prefs in all_prefs for topic in prefs if topic is not None) |
85 |
| - |
86 |
| - assert number_topics >= len(all_prefs[0]) |
87 |
| - |
88 |
| - assert all(topic is None or topic > 0 for prefs in all_prefs for topic in prefs) |
89 |
| - |
90 |
| - for prefs in all_prefs: |
91 |
| - prefs.extend([None for x in range(len(prefs), number_topics)]) |
92 |
| - |
93 |
| - students = [Student(name, prefs, rank) |
94 |
| - for rank, (name, prefs) in enumerate(zip(names, all_prefs))] |
95 |
| - |
96 |
| - assignments = dict((i, None) for i in range(1, number_topics + 1)) |
97 |
| - |
98 |
| - while any(student.topic == '' for student in students): |
99 |
| - for student in students: |
100 |
| - if student.topic != '': |
| 57 | + if line: |
| 58 | + data.append(line) |
| 59 | + return data |
| 60 | + |
| 61 | + |
| 62 | +def getPrefs(csv_data): |
| 63 | + allPrefs = [[] for n in csv_data[0]] |
| 64 | + for idx, prefs in enumerate(allPrefs): |
| 65 | + for row in csv_data[1:]: |
| 66 | + if idx < len(row): |
| 67 | + topic = row[idx] |
| 68 | + if topic is not None: |
| 69 | + prefs.append(topic) |
| 70 | + return csv_data[0], allPrefs |
| 71 | + |
| 72 | + |
| 73 | +def rankedMatch(names, allPrefs): |
| 74 | + assignments = dict() |
| 75 | + N = len(names) |
| 76 | + topics = ['' for _ in range(N)] |
| 77 | + choices = [0 for _ in range(N)] |
| 78 | + while any(topic == '' for topic in topics): |
| 79 | + for idx in range(N): |
| 80 | + if topics[idx] != '': |
101 | 81 | continue
|
102 |
| - if not student.prefs: |
103 |
| - student.topic = None |
| 82 | + prefs = allPrefs[idx] |
| 83 | + if not prefs: |
| 84 | + topics[idx] = None |
104 | 85 | continue
|
105 |
| - topic = student.prefs.pop(0) |
106 |
| - student.choice += 1 |
107 |
| - current_student = assignments[topic] |
| 86 | + topic = prefs.pop(0) |
| 87 | + choices[idx] += 1 |
| 88 | + current_student = assignments.get(topic) |
108 | 89 | if current_student is None:
|
109 |
| - assignments[topic] = student |
110 |
| - student.topic = topic |
111 |
| - elif current_student.rank > student.rank: |
| 90 | + assignments[topic] = idx |
| 91 | + topics[idx] = topic |
| 92 | + elif current_student > idx: |
112 | 93 | # less rank is better
|
113 |
| - assignments[topic] = student |
114 |
| - student.topic = topic |
115 |
| - current_student.topic = '' |
116 |
| - |
117 |
| - return students |
| 94 | + assignments[topic] = idx |
| 95 | + topics[idx] = topic |
| 96 | + topics[current_student] = '' |
| 97 | + return [Result(n, t, c) for n, t, c in zip(names, topics, choices)] |
118 | 98 |
|
119 | 99 |
|
120 |
| -def main(): |
121 |
| - if len(sys.argv) < 2: |
122 |
| - sys.stdout.write("Usage:\n\t%s CSV_FILE_PATH\n" % ( |
123 |
| - os.path.basename(sys.argv[0]))) |
124 |
| - sys.stdout.write(__doc__ + '\n') |
125 |
| - exit(1) |
126 |
| - |
127 |
| - with open(sys.argv[1], 'r') as infile: |
128 |
| - names, inverse_prefs = parseCSVFile(infile) |
129 |
| - students = rangedMatch(names, inverse_prefs) |
| 100 | +def print_students(output, students): |
130 | 101 | name_length = max(len(s.name) for s in students)
|
131 | 102 | for student in students:
|
132 |
| - sys.stdout.write('%-*s topic=%r (ranked=%d)\n' % ( |
| 103 | + output.write('%-*s topic=%r (ranked=%d)\n' % ( |
133 | 104 | name_length, student.name, student.topic, student.choice))
|
134 | 105 |
|
135 | 106 |
|
| 107 | +def main(): |
| 108 | + argparser = argparse.ArgumentParser( |
| 109 | + formatter_class=argparse.RawDescriptionHelpFormatter, |
| 110 | + description=__doc__) |
| 111 | + argparser.add_argument( |
| 112 | + 'CSV_FILE', |
| 113 | + type=argparse.FileType('r'), |
| 114 | + help='Path of CSV file to read.') |
| 115 | + args = argparser.parse_args(sys.argv[1:]) |
| 116 | + data = parseCSVFile(args.CSV_FILE) |
| 117 | + args.CSV_FILE.close() |
| 118 | + print_students(sys.stdout, rankedMatch(*getPrefs(data))) |
| 119 | + |
| 120 | + |
136 | 121 | if __name__ == '__main__':
|
137 | 122 | main()
|
0 commit comments