Skip to content

Commit 14678db

Browse files
committed
refactor
1 parent 554b5c6 commit 14678db

5 files changed

+294
-123
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
all:
2-
./test_bioscript.py
32
./test_pycodestyle.py
3+
./test_bioscript.py
44
./test_ranked_match.py
55
.PHONY: all

README.md

+79-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
Biology Scripts
44

5-
*Copyright 2023 Hal W Canary III, Lindsay R Saunders PhD.*
5+
*Copyright 2023 Hal W Canary III, Lindsay R Saunders PhD.*
66
*Use of this program is governed by contents of the LICENSE file.*
77

88
* * *
99

1010
## Install
1111

12-
1. Install Python (version 3.) <https://www.python.org/downloads/>
12+
0. Open a terminal. For MacOS, get the instructions here:
13+
<https://google.com/search?q=OPEN+MACOS+TERMINAL>
14+
15+
1. Install Python (version ≥ 3.) <https://www.python.org/downloads/>
1316

1417
Verify that the command
1518

@@ -19,7 +22,10 @@ Biology Scripts
1922
2023
works.
2124
22-
2. Clone this repo:
25+
2. Install `git`. Check if it is installed with the command `git --version` .
26+
"If you don’t have it installed already, it will prompt you to install it."
27+
28+
3. Clone this repo with `git`:
2329
2430
```
2531
cd ~
@@ -33,6 +39,9 @@ Biology Scripts
3339
git clone [email protected]:HalCanary/bioscript.git
3440
```
3541
42+
This will install into the directory `~/bioscript/` .
43+
44+
3645
* * *
3746
3847
## Unit Testing
@@ -91,3 +100,70 @@ What it does:
91100
4. Use the one with the longer sequence.
92101
93102
* * *
103+
104+
## Running `ranked_match.py`
105+
106+
To run the ranked match program, first install `python3` and `~/bioscript` as
107+
described above. Then, from a Terminal, first navigate to the directory where
108+
your rankings CSV file is located.
109+
110+
```
111+
$ cd ~/Desktop
112+
$
113+
```
114+
115+
(_Here, the string "`$`" represents your entire prompt. You don't type it,
116+
only the text after the `$`._)
117+
118+
To see what files are located here, use the `ls` command. For example:
119+
120+
```
121+
$ ls
122+
Bio212_FA20_Topic_Rankings.csv Bio212_FA20_Topic_Rankings_OUTPUT.txt
123+
$
124+
```
125+
126+
To produce rankings:
127+
128+
```
129+
$ ~/bioscript/ranked_match.py Bio212_FA20_Topic_Rankings.csv
130+
Francesca Russo topic=36 (ranked=1)
131+
Jamie Costa topic=68 (ranked=1)
132+
Robin Ortega topic=37 (ranked=1)
133+
Kobe Davis topic=77 (ranked=1)
134+
Mia Beard topic=7 (ranked=1)
135+
Nathanael Rangel topic=76 (ranked=2)
136+
Gloria Conley topic=55 (ranked=1)
137+
Marvin Richmond topic=13 (ranked=1)
138+
Whitney Wang topic=6 (ranked=1)
139+
Cohen Aguilar topic=19 (ranked=1)
140+
Josie Rodriguez topic=72 (ranked=1)
141+
Henry Zimmerman topic=34 (ranked=1)
142+
Ariyah Valdez topic=10 (ranked=3)
143+
Kyler McDaniel topic=35 (ranked=2)
144+
Dahlia Taylor topic=20 (ranked=3)
145+
Jackson Avalos topic=38 (ranked=1)
146+
Paloma Williams topic=52 (ranked=2)
147+
Oliver Moon topic=57 (ranked=1)
148+
Naya Riley topic=63 (ranked=3)
149+
Amari Fischer topic=53 (ranked=2)
150+
Maci Fuentes topic=54 (ranked=5)
151+
Bowen Potter topic=24 (ranked=2)
152+
Rory Pollard topic=50 (ranked=3)
153+
Jad Warren topic=65 (ranked=3)
154+
Sloane Pham topic=27 (ranked=1)
155+
Russell Tapia topic=69 (ranked=2)
156+
Michaela Rodriguez topic=43 (ranked=1)
157+
Henry Archer topic=49 (ranked=1)
158+
Kadence Lyons topic=71 (ranked=1)
159+
Cyrus Ball topic=None (ranked=2)
160+
Abby Decker topic=None (ranked=2)
161+
```
162+
163+
Alternatively, to save the output to a text file:
164+
165+
```
166+
$ ~/bioscript/ranked_match.py Bio212_FA20_Topic_Rankings.csv > Bio212_FA20_Topic_Rankings_OUTPUT.txt
167+
```
168+
169+
* * *

ranked_match.py

+59-74
Original file line numberDiff line numberDiff line change
@@ -28,110 +28,95 @@
2828
27, 40, 17, 4
2929
1, 1, 3, 34
3030
3, 19, 7, 4
31-
32-
To Run:
33-
34-
Lindsays-MBP:~ Lindsay$ .../ranked_match.py PATH_TO_CSV_FILE
3531
'''
3632

3733

34+
import argparse
35+
import collections
3836
import csv
3937
import sys
4038
import os
4139

4240

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'])
5342

5443

5544
def parseInteger(s):
56-
s = s.replace('\xCA', '').strip()
45+
s = s.strip('\uFEFF').strip().strip('\xCA')
5746
try:
5847
return int(s)
5948
except ValueError:
60-
if len(s):
61-
raise
62-
return None
49+
return s if len(s) else None
6350

6451

6552
def parseCSVFile(infile):
6653
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 = []
7055
for line in reader:
71-
assert len(line) <= num_names
7256
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] != '':
10181
continue
102-
if not student.prefs:
103-
student.topic = None
82+
prefs = allPrefs[idx]
83+
if not prefs:
84+
topics[idx] = None
10485
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)
10889
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:
11293
# 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)]
11898

11999

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):
130101
name_length = max(len(s.name) for s in students)
131102
for student in students:
132-
sys.stdout.write('%-*s topic=%r (ranked=%d)\n' % (
103+
output.write('%-*s topic=%r (ranked=%d)\n' % (
133104
name_length, student.name, student.topic, student.choice))
134105

135106

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+
136121
if __name__ == '__main__':
137122
main()

test_pycodestyle.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,23 @@
2020
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
# SOFTWARE.
2222

23+
import logging
2324
import os
25+
import unittest
26+
2427
try:
2528
import pycodestyle
2629
except ImportError:
2730
print('Try `python3 -m pip install pycodestyle`')
2831
raise
2932

30-
pycodestyle.StyleGuide(paths=['--max-line-length=99']).check_files([os.path.dirname(__file__)])
33+
34+
class StyleTestCase(unittest.TestCase):
35+
def test_python_style(self):
36+
pycodestyle.StyleGuide(
37+
paths=['--max-line-length=99']).check_files([os.path.dirname(__file__)])
38+
39+
40+
if __name__ == '__main__':
41+
logging.basicConfig(format='%(levelname)s: %(message)s', level='WARNING')
42+
unittest.main()

0 commit comments

Comments
 (0)