Created
November 2, 2020 15:15
-
-
Save ucyo/6d3e557243248c3bac640aefdc1a4045 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| # coding: utf-8 | |
| """Application for distribution of topics to authors based on preference""" | |
| import random | |
| import math | |
| from collections import Counter | |
| MINIMUM = 0 | |
| MAXIMUM = 100 | |
| class Author: | |
| """Author which needs to be assigned to a topic""" | |
| def __init__(self, name: str, preference: str, number: int): | |
| self.name = name | |
| self._preference_original = preference | |
| self.preference = preference | |
| self.number = number | |
| @property | |
| def preference(self): | |
| return self._preference | |
| @preference.setter | |
| def preference(self, preference: str): | |
| candidates = [Topic(int((x))) for x in preference.split(",") if x] | |
| assert len(set(candidates)) == len(candidates), "Repeated elements in preference list" | |
| self._preference = candidates | |
| def __repr__(self): | |
| return f"Author(name={self.name}, preference={self.preference}, number={self.number})" | |
| def extend(self, topics: int): | |
| """Extend the preference""" | |
| assert len(self.preference) <= topics, "Authors preference list is longer than the number of topics" | |
| self._preference += [math.nan] * (topics - len(self._preference)) | |
| return self | |
| def erase_topic(self, topic: int): | |
| self._preference = [x if isinstance(x, Topic) and | |
| x.id != topic else math.nan | |
| for x in self._preference] | |
| def erase_all(self): | |
| self._preference = [math.nan for x in self._preference] | |
| def preferences_good(pref, num_topics): | |
| candidates = [int(x) for x in pref.split(",") if x] | |
| if len(set(candidates)) != len(candidates): | |
| print(f"Repeated elements in preference list {candidates}") | |
| return False | |
| if any([x > num_topics for x in candidates]): | |
| print(f"Maximum preference is {num_topics}") | |
| return False | |
| return True | |
| def main(): | |
| topics = int(input("Number of topics: ")) | |
| td = TopicDistributor(topics) | |
| while True: | |
| i = input("Please enter '<name> <preferences> <lucky number>': ") | |
| if not i: | |
| break | |
| li = i.split(" ") | |
| if len(li) != 3: | |
| print(f"Input too short {li}") | |
| continue | |
| name = li[0] | |
| pref = li[1] | |
| if not preferences_good(pref, topics): | |
| continue | |
| num = int(li[2]) | |
| td.add_author(Author(name, pref, num)) | |
| print("=================") | |
| print(td) | |
| print("=================") | |
| print("Distributing...") | |
| td.distribute() | |
| print("=================") | |
| print(td) | |
| print("=================") | |
| class TopicDistributor: | |
| def __init__(self, number_of_topics: int, minimum=MINIMUM, maximum=MAXIMUM): | |
| self.number_of_topics = number_of_topics | |
| self.authors = {} | |
| self.topic_numbers = {i: random.randint(minimum, maximum) | |
| for i in range(1, number_of_topics+1)} | |
| self.assigned_topics = {i: math.nan | |
| for i in range(1, number_of_topics+1)} | |
| def __repr__(self): | |
| result = f"TopicDistributor(n={self.number_of_topics})" | |
| result += "\n\n Authors:" | |
| for (x, author) in self.authors.items(): | |
| result += f"\n {x}: {author}" | |
| result += "\n\n Assigned Topics:" | |
| for topic, author in self.assigned_topics.items(): | |
| result += f"\n {topic}: {self.topic_numbers[topic]}:\t{author}" | |
| return result | |
| def add_author(self, author: Author): | |
| author.extend(self.number_of_topics) | |
| ix = len(self.authors.keys()) + 1 | |
| self.authors[ix] = author | |
| def assign_topic_to_author(self, topic: int, author: int): | |
| assert math.isnan(self.assigned_topics[topic]), "Topic already assigned" | |
| assert len(self.authors) >= author, "Author does not exist" | |
| assert self.number_of_topics >= topic, "Topic does not exist" | |
| num_unassigned_topics = sum([not isinstance(x, Author) and math.isnan(x) | |
| for x in self.assigned_topics.values()]) | |
| num_authors = len(self.authors) | |
| if self.number_of_topics - num_authors == num_unassigned_topics: | |
| # print("No more authors to be assigned") | |
| return True | |
| self.assigned_topics[topic] = self.authors[author] | |
| self.authors[author].erase_all() | |
| for a in self.authors.values(): | |
| a.erase_topic(topic) | |
| return False | |
| def distribute_by_topic(self, t): | |
| """ | |
| """ | |
| topic = Topic(t) | |
| preference_level = 0 | |
| while preference_level < self.number_of_topics: | |
| candidate_ix = [k for k, v in self.authors.items() | |
| if isinstance(v.preference[preference_level], Topic) | |
| and v.preference[preference_level] == topic] | |
| if len(candidate_ix) == 0: | |
| preference_level += 1 | |
| continue | |
| if len(candidate_ix) == 1: | |
| self.assign_topic_to_author(t, candidate_ix[0]) | |
| break | |
| else: | |
| # print("Too much choice") | |
| break | |
| def distribute_by_preference(self, p): | |
| """ | |
| """ | |
| topics = {k: x.preference[p] for k, x in self.authors.items()} | |
| counts = Counter(topics.values()) | |
| while len(counts.items()) > 0: | |
| t, count = counts.popitem() | |
| if not isinstance(t, Topic): | |
| continue | |
| if count == 1: | |
| a = None | |
| for k, v in topics.items(): | |
| if isinstance(v, Topic) and v == t: | |
| a = k | |
| self.assign_topic_to_author(t.id, a) | |
| else: | |
| authors = [k for k, v in topics.items() | |
| if isinstance(v, Topic) and v == t] | |
| self.collision(t.id, authors) | |
| def distribute(self): | |
| for i in range(self.number_of_topics): | |
| self.distribute_by_preference(i) | |
| def collision(self, topic_id, author_ids): | |
| numbers = {x: | |
| abs(self.authors[x].number - self.topic_numbers[topic_id]) | |
| for x in author_ids} | |
| sorted_numbers = sorted(numbers.items(), key=lambda x: x[1], | |
| reverse=False) | |
| lowest = sorted_numbers[0][1] | |
| sec_lowest = sorted_numbers[1][1] | |
| while lowest == sec_lowest: | |
| i = 0 | |
| # print(f"Have to be redone: {i}") | |
| new_topic_num = random.randint(MINIMUM, MAXIMUM) | |
| numbers = {x: abs(self.authors[x].number - new_topic_num) | |
| for x in author_ids} | |
| # print(f"Num: {numbers}") | |
| sorted_numbers = sorted(numbers.items(), key=lambda x: x[1], | |
| reverse=False) | |
| lowest = sorted_numbers[0][1] | |
| sec_lowest = sorted_numbers[1][1] | |
| i += 1 | |
| else: | |
| # print(f"Collision: {sorted_numbers}") | |
| self.assign_topic_to_author(topic_id, sorted_numbers[0][0]) | |
| @property | |
| def no_preferences(self): | |
| for a in self.authors.values(): | |
| for p in a.preference: | |
| if isinstance(p, Topic): | |
| return False | |
| class Topic: | |
| def __init__(self, topic: int): | |
| self.id = topic | |
| def __eq__(self, other): | |
| return self.id == other.id | |
| def __le__(self, other): | |
| return self.id < other.id | |
| def __hash__(self): | |
| return hash(self.id) | |
| def __repr__(self): | |
| return f"Topic({self.id})" | |
| if __name__ == '__main__': | |
| main() |
Author
Nice ๐ Did not know that. Will definitely check it out ๐
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You could export the google forms as CSV and just read that.
Should be pretty quick and easier than importing all of them by hand