Search code examples
pythondepth-first-search

Python3 Friend of a Friend


I'm working on this function to find a possible friendship between two people. The friendship system works in a transitive way, that is, if A is a friend of B and B is a friend of C, then A is a friend of C. The dictionary stores the initial relationships (like a graph) and the function parameters are the dictionary and the names of the two people you want to identify if they are friends or not.

def findfriendship(people, X, Y):
    if Y in people[X] or X in people[Y]:
        return True

    if len(people[X]) != 0:
        for friend in people[X]:
            return findfriendship(people, friend, Y)

    if len(people[Y]) != 0:
        for friend in people[Y]:
            return findfriendship(people, X, friend)

    return False

This is my code and I was successful in identifying a possible friendship between two people as long as one of them have a list of friends not empty, like this:

friendships = {'Jordan': ['Shaq'], 'Lebron': [], 'Kobe': [], 'Shaq': ['KD'], 'KD': []}
print(findfriendship(friendships, 'KD', 'Jordan')) -> return True

But I can't solve the problem where both have no direct friends, like this:

friendships = {'Jordan': ['Shaq'], 'Lebron': [], 'Kobe': ['KD', 'Lebron'], 'Shaq': ['KD'], 'KD': []}
print(findfriendship(friendships, 'Lebron', 'KD')) -> return False

it returns False, but Kobe is a friend of them both, so they should be friends.

Can you guys help me trying to solve this problem or do you know a similar question so I can understand this type of concept? Thanks for your attention.


Solution

  • Checking if two people are friends would be easy if we could get the full list of friends for either person. Let's write a helper for that:

    friendships = {
        'Jordan': ['Shaq'], 'Lebron': [],
        'Kobe': ['KD', 'Lebron'], 'Shaq': ['KD'], 'KD': [],
        "foo": ["bar", "baz"], "bar": [], "baz":[],
    }
    
    def buildFriendSet (x, people=None, fSet=None):
        people = people or friendships;       # default value
        fSet = fSet or set([x] + people[x]);  # Friend Set
        changeDetected = False;
        for kPerson, vPersonList in people.items():
            personList = [kPerson] + vPersonList;
            if fSet.issuperset(personList):
                pass;
            elif fSet.intersection(personList):
                fSet = fSet.union(personList);
                changeDetected = True;
        if not changeDetected:
            return fSet;
        return buildFriendSet(x, people, fSet);
    
    print(buildFriendSet('KD'));
    # Output: {'Shaq', 'Kobe', 'KD', 'Lebron', 'Jordan'}
    
    print(buildFriendSet('foo'))
    # Output: {'baz', 'bar', 'foo'}
    

    Once we can compute the set of friends for any person, checking if two people are friends is easy:

    def checkFriends (x, y, people=None):
        people = people or friendships; # default value
        return (x in people[y] or y in people[x] or
            x in buildFriendSet(y, people)
        );
    
    print(checkFriends('KD', 'Jordan')); # -> True
    print(checkFriends('Lebron', 'KD')); # -> True
    print(checkFriends('KD', 'foo'));    # -> False
    

    Logic behind buildFriendSet():

    We iterate over people and check if the friend-set fSet changes. If no change is detected, then we return fSet unchanged. Else, we keep looking for more friends.

    For a given group of close friends, such as any personList above, if everyone in the group is already in fSet, there's no need to change fSet. This corresponds to the .issuperset(.) check.

    Otherwise, if at least one person from personList is already in fSet, then by association, everyone else must be in it too! We use .intersection(.) and .union() to check and achieve this, respectively.

    Is everyone their own friend?

    The above implementation assumes that everyone is their own friend. If such behavior isn't desired, using a simple if-guard and/or fSet.remove(.) should suffice.