- Mon 18 November 2019
- analysis
- Christophe
- #Python
A very simple kindle draw¶
Revisiting the simple kindle draw. And getting utterly confused.
import random
def simple_secret_santa(names,printit=False):
n = len(names)
s = random.sample(names,k=n)
out = []
for i,name in enumerate(s):
if i == len(s)-1:
to = s[0]
else:
to = s[i+1]
out.append(name+'-'+to)
if printit:
print(str(i+1) + " "+name + ' gives to ' + to)
return out
names = ['Ro','Pa','Ao','HaM',
'Fi', 'Ci',
'Te','Ja','Ch','HaC',
'Co','An','It',
'To',
'Et',
'Ro', 'Li']
s = simple_secret_santa(names,printit=True)
1 Pa gives to Et 2 Et gives to An 3 An gives to Te 4 Te gives to Ro 5 Ro gives to It 6 It gives to Ja 7 Ja gives to HaM 8 HaM gives to Ro 9 Ro gives to Li 10 Li gives to Ao 11 Ao gives to Ch 12 Ch gives to Ci 13 Ci gives to To 14 To gives to HaC 15 HaC gives to Fi 16 Fi gives to Co 17 Co gives to Pa
How random is this ?¶
First lets import matplotlib package so we can do some vizualisation.
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
import itertools
def monte_carlo(names,n=1000):
comb = {name1+'-'+name2:0 for name1,name2 in itertools.permutations(names,2)}
for i in range(n):
for couple in simple_secret_santa(names):
comb[couple] += 1
fig1 = plt.figure()
plt.plot([comb[k] for k in comb.keys()])
plt.xlabel('Iterations')
plt.ylabel('Count each combo')
#plt.axhline(y=n, color='r', linestyle='-')
plt.title('Xmas Kindle')
plt.show()
return comb
c = monte_carlo(names,n=1000000)
That is very odd, I would have expected each combination to appear roughly the same number of times, it looks like some combination are twice as frequent ? ... Let's try with a smaller set.
names2 = ['Jolene','Niamh','Sophie','Lorraine','Paschal']
#only 5 people in the draw, which should make 20 possible donor combinations
comb2 = {name1+'-'+name2:0 for name1,name2 in itertools.permutations(names2,2)}
print(len(comb2))
20
c2 = monte_carlo(names2,n=50000)
This looks about right.Let's used a reduced inital set.
c_Reduced = monte_carlo(names[:14],n=1000000)
Again this looks more as expected, after inspecting the initial names selection, I actually realized the name were not uniques.
duplicates = [names.count(n)>=2 for n in names]
print(duplicates)
[True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False]
# Replicating the issue with duplicate names
names3 = ['Jolene','Niamh','Sophie','Lorraine','Jolene']
#only 5 people in the draw, which should make 20 possible donor combinations, with a duplicate
comb3 = {name1+'-'+name2:0 for name1,name2 in itertools.permutations(names3,2)}
print(len(comb2))
c3 = monte_carlo(names3,n=50000)
20
#This explain the disruption in randomness so we can easily fix that by adding a check for name uniqueness
def isThereDuplicates(names):
dups = [names.count(n)>=2 for n in names]
if any(dups):
print("Duplicate names")
print(names[dups.index(True)])
return True
else:
return False
isThereDuplicates(names)
Duplicate names Ro
True
# Fixing the duplicates
def simple_secret_santa(names,printit=False):
n = len(names)
s = random.sample(names,k=n)
out = []
if not isThereDuplicates(names):
for i,name in enumerate(s):
if i == len(s)-1:
to = s[0]
else:
to = s[i+1]
out.append(name+'-'+to)
if printit:
print(str(i+1) + " "+name + ' gives to ' + to)
return out
Family draws¶
However for a family draw, you may want to avoid gift between siblings ( my particular family case ) or between partners (not considered here), so only gift to cousin are allowed, to do this I am introducing a dictionary, with the name of the parent as value and the kid name as key.
def simple_secret_santa_avoid_siblings(names):
givers = names.keys()
recipients = []
s = random.sample(givers,k=len(givers))
out = []
if not isThereDuplicates(list(givers)):
for i,name in enumerate(s):
possible = [r for r in names.keys() if (names[r] != names[name] and r not in recipients)]
recipient = random.sample(possible,k=1)[0]
recipients.append(recipient)
print(str(i+1) + ': ' + name + ' gives to ' + recipient + ' ::: '+ str(len(recipients))+':'+str(len(possible)))
out.append(name+'-'+recipient)
#Check
print('Recipients: '+str(len(recipients))+':: names:'+str(len(names.keys())))
names_siblings = {'Child1':'Parent1','Child2':'Parent1','Child3':'Parent1','Child4':'Parent1',
'Child5':'Parent2', 'Child6':'Parent2',
'Child7':'Parent3','Child8':'Parent3','Child9':'Parent3','Child10':'Parent3',
'Child11':'Parent4','Child12':'Parent4','Child13':'Parent4',
'Child14':'Parent5',
'Child15':'Parent6',
'Child16':'Parent7', 'Child17':'Parent7'}
simple_secret_santa_avoid_siblings(names_siblings)
1: Child5 gives to Child13 ::: 1:15 2: Child11 gives to Child14 ::: 2:14 3: Child1 gives to Child6 ::: 3:11 4: Child7 gives to Child3 ::: 4:10 5: Child13 gives to Child7 ::: 5:11 6: Child9 gives to Child17 ::: 6:9 7: Child3 gives to Child12 ::: 7:8 8: Child4 gives to Child15 ::: 8:7 9: Child17 gives to Child5 ::: 9:8 10: Child8 gives to Child16 ::: 10:5 11: Child10 gives to Child11 ::: 11:4 12: Child6 gives to Child2 ::: 12:6 13: Child16 gives to Child4 ::: 13:5 14: Child2 gives to Child10 ::: 14:3 15: Child15 gives to Child8 ::: 15:3 16: Child14 gives to Child1 ::: 16:2 17: Child12 gives to Child9 ::: 17:1 Recipients: 17:: names:17