3

I am teaching myself Python and have written a small script for exchanging Christmas gifts (this is not homework). My family likes for each person to give one gift to one person of the same gender. The following script works most of the time but sometimes fails through infinite recursion. I am not sure why, because I think the base case will eventually be met.

import random

family = {'Joe': 'm', 'Jane': 'f', 'John': 'm', 'Jill': 'f', 'James': 'm', 'Jade': 'f'}
receivers = family.copy()
givers = family.copy()

def match(giver):
    index = random.randrange(len(receivers))
    giverGender = givers[giver]
    receiver = receivers.keys()[index]
    receiverGender = receivers.values()[index]

    if giver != receiver and giverGender == receiverGender:
        del receivers[receiver]
        return giver + ' to ' + receiver
    else:
        return match(giver)

# main program
for i in givers:
    print match(i)

This is the error (EDIT to add full error):

Traceback (most recent call last):
  File "C:\...\christmasGifts.py", line 22, in <module>
    print match(i)
  File "C:\...\christmasGifts.py", line 18, in match
    return match(giver)

  ...

  File "C:\...\christmasGifts.py", line 18, in match
    return match(giver)
  File "C:\...\christmasGifts.py", line 9, in match
  index = random.randrange(len(receivers))
  File "C:\Python27\lib\random.py", line 184, in randrange
istart = int(start)
RuntimeError: maximum recursion depth exceeded while calling a Python object

Thank you for any help.

4
  • 2
    There appears to be some of that error message missing Commented Nov 16, 2012 at 17:44
  • This isn't an answer, more of a suggestion: look up Derangement, as it describes your problem fairly well, minus the gender requirement. Commented Nov 16, 2012 at 17:58
  • May be Google sense of humor can help.. Commented Nov 16, 2012 at 18:16
  • @Kevin, thanks. This is interesting and definitely a partial answer, as that link also has a (Python!) algorithm for derangement. Commented Nov 16, 2012 at 18:39

5 Answers 5

6

First lets just consider the women. If your program runs and matches Jane to Jill, then matches Jill to Jane, Jane is the only female left and because she cannot match herself, your program runs indefinitely without a match.

Let me propose an alternate method to solve your problem. Randomly shuffle the order of your givers/recieves and have each person give a gift the the following person in the list, and have the last person in the list gift the first person. That would look something like this:

from random import shuffle
women = ['Jane', 'Jill', 'Jade']
shuffle(women)
print women[-1] + ' to ' + women[0]
for i in range(len(women) - 1):
    print women[i] + ' to ' + women[i+1]
Sign up to request clarification or add additional context in comments.

5 Comments

Your shuffle method is a cool idea, although it doesn't generate all possible gift-giving possibilities if you have 4+ participants. For example, A -> B, B -> A, C -> D, D -> C can't be generated in this way. Not that it matters much for Secret Santa, of course. Uncle Joe won't care whether the gift-giving graph is always fully connected or not.
Ya you're right, I figured it would be good enough for Secret Santa purposes. I don't know of an algorithm to do derangement, though I'm sure it exists.
@Kevin -- What gives you the right to say what Uncle Joe does or does not care about? ;-)
Thank you! Both an explanation and a nice solution.
Yay! for non-recursive solution. Unless the language depends on recursion (e.g. lisp) I'd suggest staying away from recursion while learning the language (unless of course you're a functional language guru out on a jaunt).
2

Jane to Jill and Jill to Jane

Who does Jade give to?

1 Comment

Perfect. This explains why it works fine for four people (two males, two females).
0

Here is a simple iterative variant of your script. It works pretty well.

As an assumption I think that, based on the probability theory, sometimes you just call the match function too many times, so it cause the program to reach the recursion limit

import random

family = {'Joe': 'm', 'Jane': 'f', 'John': 'm', 'Jill': 'f', 'James': 'm', 'Jade': 'f'}
receivers = family.copy()
givers = family.copy()

def match(giver):
    giverGender = givers[giver]

    Found = False
    while not Found:
        index = random.randrange(len(receivers))
        receiver = receivers.keys()[index]
        receiverGender = receivers.values()[index]
        if giver != receiver and giverGender == receiverGender:
            Found = True
            del receivers[receiver]
    return giver + ' to ' + receiver


# main program
if __name__=='__main__':
    for i in givers:
        print match(i)

1 Comment

Does this address the problem raised by the other answers? That is, if Joe gives John a present, and John gives Joe a present, will this loop forever when trying to find a recipient for James' present?
0

There's a slightly easier to understand way of doing this:

from itertools import izip_longest
from random import shuffle

family = {
    'Joe': 'm',
    'Jane': 'f',
    'John': 'm',
    'Jill': 'f',
    'James': 'm',
    'Jade': 'f'
}

females = [k for k, v in family.iteritems() if v == 'f']
for num in range(5):
    shuffle(females) # muck up the order a bit
    print list(izip_longest(females, females[1:], fillvalue=females[0]))

[('Jade', 'Jill'), ('Jill', 'Jane'), ('Jane', 'Jade')]
[('Jane', 'Jill'), ('Jill', 'Jade'), ('Jade', 'Jane')]
[('Jade', 'Jane'), ('Jane', 'Jill'), ('Jill', 'Jade')]
[('Jade', 'Jane'), ('Jane', 'Jill'), ('Jill', 'Jade')]
[('Jill', 'Jane'), ('Jane', 'Jade'), ('Jade', 'Jill')]

Comments

0

The version below actually allows for more possibilities of choices.

from random import randint

giver = ['Jane', 'Jill', 'Jade', 'Nicole', 'Charlotte']

receiver = [i for i in giver]

def match(giver, receiver):
    for i in giver:

        #get random receiver
        rec = receiver[randint(0,len(receiver)-1)]

        #check to see if only two remain, so person doesn't pick self in loop.
        if len(receiver) == 2:
            rec = receiver[1]

        #make sure person doesn't select themself.
        while i == rec:
            rec = receiver[randint(0,len(receiver)-1)]

        print i + " gives to " + rec
        #reduce receiver pool
        receiver.remove(rec)

Edit: I somehow think this ruins the fun of drawing from a hat at Thanksgiving.... :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.