Created
July 28, 2019 13:36
-
-
Save emauton/1b51dd77bdcaada225fe2b1b05f0d008 to your computer and use it in GitHub Desktop.
Draw script for #coffee_friends on https://irishtechcommunity.com/
This file contains 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 python3 | |
# Draw script for #coffee_friends on https://irishtechcommunity.com/ | |
# Produces a random draw of pairs from commandline args, using accompanying | |
# historical data in `pairs.txt` to minimize the number of times the same | |
# #coffee_friends meet. | |
import argparse | |
import itertools | |
import random | |
import sys | |
def generate_candidate_pairs(friends): | |
'''Given a list of `friends`, generate all unique (x, y) pairs''' | |
return list(itertools.combinations(sorted(friends), 2)) | |
def generate_draws(pairs): | |
'''Given a list of unique pairs, return all possible draws | |
The pairs represent "draws from a hat"; we recursively generate all | |
further draws possible once we pick one out, etc. | |
Yes, this could get expensive. No, we don't care: #coffee_friends is smol! | |
Returns a list of draws `[[(a, b), (c, d)], ...]`''' | |
if len(pairs) == 1: # base case | |
return [pairs] | |
# A little ugly: we pick the first element `a` of the first pair & then | |
# recurse over each pair `(a, X)` | |
first = pairs[0][0] | |
tries = filter(lambda p: p[0] == first, pairs) | |
results = [] | |
for a, b in tries: | |
remainder = [p for p in pairs | |
if a not in p | |
and b not in p] | |
for draws in generate_draws(remainder): | |
results.append([(a, b)] + draws) | |
return results | |
def get_pairs_data(filename): | |
'''Given a file of comma-separated pairs, return a list of pair tuples''' | |
try: | |
with open(filename) as f: | |
pairs = [tuple(sorted(l.strip().split(','))) for l in f] | |
except Exception as e: | |
print(f'Failed to load former pairs data ({filename}): {e}') | |
pairs = [] | |
return pairs | |
def minimize_rematches(draws, former_pairs): | |
'''Get a sublist of draws such that "re-matching" is minimized | |
Given a list of draws + former pairs, return the sublist with minimal | |
number of friends being re-matched.''' | |
scored = {} | |
minimum = sys.maxsize | |
for d in draws: | |
score = 0 | |
for pair in d: | |
if pair in former_pairs: | |
score += 1 | |
scored[tuple(d)] = score | |
minimum = min(score, minimum) | |
return [list(d) for d in scored if scored[d] == minimum] | |
def get_args(argv): | |
desc = 'Draw script for #coffee_friends on https://irishtechcommunity.com/' | |
epilog = ('If you pass an odd number of friends, we add a "Dummy" partner ' | |
'to make up numbers; just add whomever is paired with "Dummy" ' | |
'to one of the other drawn pairs.') | |
parser = argparse.ArgumentParser(description=desc, epilog=epilog) | |
parser.add_argument('friends', metavar='@friend', nargs='+', | |
help='The friends to pair up, using Slack @names') | |
parser.add_argument('--pairs', default='pairs.txt', | |
help='Path to comma-separated pairs that met before') | |
parser.add_argument('--all', action='store_true', default=False, | |
help='Print all minimized draws vs. picking one') | |
return parser.parse_args(args=argv) | |
def main(): | |
args = get_args(sys.argv[1:]) | |
if len(args.friends) % 2 != 0: | |
args.friends.append('Dummy') # like in Bridge, you see | |
former_pairs = get_pairs_data(args.pairs) | |
candidate_pairs = generate_candidate_pairs(args.friends) | |
draws = generate_draws(candidate_pairs) | |
minimized = minimize_rematches(draws, former_pairs) | |
if args.all: | |
draws = minimized | |
else: | |
draws = [random.choice(minimized)] | |
print('Draw:') | |
print('=====') | |
for draw in draws: | |
for p, q in draw: | |
print(f'{p} and {q}') | |
print('-----') | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment