Lipovlan
Lipovlan

Reputation: 33

Recursion happens too many times and list is not iterable

I'm trying to make a secret santa programm. The input is in form of the list of names of people g. ["John", "Bob", "Alice"] and the list of emials ["[email protected]", "[email protected]", "[email protected]"]. I need to generate pairs of email adress and a random name which doesn't belong to the said email adress. For this I have written the function compare.

def compare(list_of_names, list_of_emails):
    zipped_lists = zip(list_of_emails, list_of_names)
    random.shuffle(list_of_emails)
    zipped_shuffled_lists = zip(list_of_emails, list_of_names)
    for pair in zipped_lists:
        for shuffle_pair in zipped_shuffled_lists:
            if shuffle_pair == pair:
                return compare(list_of_names, list_of_emails)
    return zipped_shuffled_lists

But instead of shuffling like it should it just creates a recursion. i still can't find out why. After a finite amount of time it should create two different lists that work. Also the shuffled_list_of_emails is not iterable, why?

EDIT:changed the code with shuffle because it works in place

Upvotes: 1

Views: 68

Answers (2)

Mikhail Stepanov
Mikhail Stepanov

Reputation: 3790

There's correct answer from @ForceBru already. But a will contribute a little.
You should avoid zip's lazy evaluation and unfold zips with, for example, list:

def compare(list_of_names, list_of_emails):
    zipped_lists = list(zip(list_of_emails, list_of_names))  # eager evaluation instead of lazy
    random.shuffle(list_of_emails)  # shuffle lists
    zipped_shuffled_lists = list(zip(list_of_emails, list_of_names))  # eager again
    for pair in zipped_lists:
        for shuffle_pair in zipped_shuffled_lists:
            if shuffle_pair == pair:
                return compare(list_of_names, list_of_emails)
    return zipped_shuffled_lists

But I guess you need no recursion and can achieve your task easier:

def compare(list_of_names, list_of_emails):
    zipped_lists = list(zip(list_of_emails, list_of_names))
    random.shuffle(zipped_lists)  # shuffle list of emails and names
    result = []
    shuffled_emails = [i[0] for i in zipped_lists]
    for i, _ in enumerate(shuffled_emails):
        result.append(zipped_lists[i-1][1])  # shift email relatively one position to the right
    return list(zip(result, shuffled_emails))

This code links an name with an email of a previous name, which is randomly selected, and it guaranteed does not match. There's no recursion, works fine for lists with two or more elements.

Upvotes: 0

ForceBru
ForceBru

Reputation: 44878

zip is lazy!

I'm not sure why, but I'm too excited about this right now, so the answer might be a bit messy. Feel free to ask for clarification)


Let's step through your code:

def compare(list_of_names, list_of_emails):
    # the `zip` object doesn't actually iterate over any of its arguments until you attempt to iterate over `zipped_lists`
    zipped_lists = zip(list_of_emails, list_of_names)

    # modify this IN-PLACE; but the `zip` object above has a pointer to this SAME list
    random.shuffle(list_of_emails)

    # since the very first `zip` object has `list_of_emails` as its argument, AND SO DOES THE ONE BELOW, they both point to the very same, SHUFFLED (!) list
    zipped_shuffled_lists = zip(list_of_emails, list_of_names)

    # now you're iterating over identical `zip` objects
    for pair in zipped_lists:
        for shuffle_pair in zipped_shuffled_lists:

            # obviously, this is always true
            if shuffle_pair == pair:

                # say "hello" to infinite recursion, then!
                return compare(list_of_names, list_of_emails)
    return zipped_shuffled_lists

Let's recreate this in the Python interpreter!

>>> List = list(range(5))
>>> List
[0, 1, 2, 3, 4]
>>> zipped_1 = zip(List, range(5))
>>> import random
>>> random.shuffle(List)
>>> zipped_2 = zip(List, range(5))
>>> print(List)
[4, 2, 3, 0, 1]
>>> zipped_1, zipped_2 = list(zipped_1), list(zipped_2)
>>> zipped_1 == zipped_2
True

You see, two different zip objects applied to the same list at different times (before and after that list is modified in-place) produce the exact same result! Because zip doesn't do the zipping once you do zip(a, b), it will produce the zipped... uh, stuff... on-the-fly, while you're iterating over it!

So, to fix the issue, do not shuffle the original list, shuffle its copy:

list_of_emails_copy = list_of_emails.copy()
random.shuffle(list_of_emails_copy)
zipped_shuffled_lists = zip(list_of_emails_copy, list_of_names)

Upvotes: 1

Related Questions