Skip to content

Instantly share code, notes, and snippets.

@ucyo
Created November 2, 2020 15:15
Show Gist options
  • Select an option

  • Save ucyo/6d3e557243248c3bac640aefdc1a4045 to your computer and use it in GitHub Desktop.

Select an option

Save ucyo/6d3e557243248c3bac640aefdc1a4045 to your computer and use it in GitHub Desktop.
#!/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()
@sebimarkgraf
Copy link

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

@ucyo
Copy link
Author

ucyo commented Nov 2, 2020

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