Skip to content

Solution to Array/Celebrity Problem #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ By default, the time complexity indicates the worst time and the space complexit
#### [⬆](#toc) <a name='array'>Array:</a>

* [Calculate the rotation distance for a sorted rotated array](problem/Array/Rotatearraydistance.cpp)
* Celebrity problem
* [Celebrity problem](problem/Array/CelebrityProblem.py)
* [Detect cycle in an array](problem/Array/Detect%20cycle%20in%20an%20array.cpp)
* [Detect the longest cycle in an array](problem/Array/Detect%20the%20longest%20cycle%20in%20an%20array.cpp)
* [Diagonal elements sum of a spiral matrix](problem/Array/Diagonal%20elements%20sum%20of%20spiral%20matrix.cpp)
Expand Down
163 changes: 163 additions & 0 deletions problem/Array/CelebrityProblem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# /usr/bin/env python2.7
# vim: set fileencoding=utf-8
"""
In a party of N people, only one person (the celebrity) is known to everyone.
Such a person may be present in the party. If yes, they don't know anyone else
at the party.

We can only ask whether "does person-A know person-B":

HasAcquaintance(A, B) returns True if A knows B, False otherwise.

Find the celebrity at the party (if they exist) in as few calls to
HasAcquaintance as possible.
"""


class _BaseSolution:
def __init__(self, edges=None):
self.edges = edges or {}
self.has_acquaintance_counter = 0
self.answer = None
self._called = False

def HasAcquaintance(self, A, B):
"""Returns True if A knows B."""
self.has_acquaintance_counter += 1
return B in self.edges.get(A, ())

def Solve(self, people=None):
if people is None:
people = self.edges.keys()
self.answer = self.findTheCelebrity(people)
return self

def findTheCelebrity(self, people):
"""Return the name of the celebrity if they are at the party.

Return None if there is no celebrity at the party.
"""
return None


class _TestData:
"""Struct for containing test data and expected celebrity value."""
def __init__(self, name, expected, edges):
self.name = name
self.expected = expected
self.edges = edges


class NSquaredSolution(_BaseSolution):
"""Assume everyone is the celebrity and disqualify them if they can't be
the celebrity:

(1) If they know anyone else in the party, they can't be the celebrity.

(2) If they are not known by everyone else in the party, they also cannot be
the celebrity.

If there is an entry remaining in the list of potential celebrities, then
that is the celebrity. It's an error if there is more than one potential
celebrity.
"""
def findTheCelebrity(self, people):
import collections
# each key knows everyone in their matching value
social_graph = collections.defaultdict(set)

possible_celebrities = set(people)
for A in people:
for B in [p for p in people if p != A]:
# (1) celebrities don't know anyone at the party
if self.HasAcquaintance(A, B):
social_graph[A].add(B)
possible_celebrities.discard(A)
if self.HasAcquaintance(B, A):
social_graph[B].add(A)
possible_celebrities.discard(B)

# (2) celebrities are known by everyone at the party
celebrities = set(possible_celebrities)
for celeb in possible_celebrities:
for edges in social_graph.values():
if celeb not in edges:
celebrities.discard(celeb)
break

if celebrities:
# we assume there's only one celebrity
return celebrities.pop()

return None


class TwoPointersSolution(_BaseSolution):
def findTheCelebrity(self, people):
a_idx = 0
b_idx = len(people) - 1
while a_idx < b_idx:
if self.HasAcquaintance(people[a_idx], people[b_idx]):
# if A knows B, then A can't be the celebrity
a_idx += 1
else:
# if A doesn't know B, then B can't be the celebrity
b_idx -= 1

# double check that A meets the celebrity requirements for everyone.
# we may have missed a disqualification when we aggressively walked
# people in the last step
A = people[a_idx]
for person in [p for p in people if p != A]:
if self.HasAcquaintance(A, person):
# if A knows someone at the party, they can't be the celebrity
return None
if not self.HasAcquaintance(person, A):
# if someone doesn't know A, then A can't be the celebrity
return None
return A


def main():
test_data = [
_TestData('D_IS_THE_CELEBRITY',
'd',
{'a': ('b', 'c', 'd'),
'b': ('c', 'd'),
'c': ('a', 'd'),
'd': (),
'e': ('b', 'd')}),
_TestData('NO_CELEBRITY',
None,
{'a': ('b', 'c', 'd'),
'b': ('c', 'd'),
'c': ('a', 'd'),
'd': ('e'),
'e': ('b', 'd')}),
_TestData('ALMOST_A_CELEBRITY',
None,
{'a': ('b', 'c', 'd'),
'b': ('c', 'd'),
'c': ('a', 'd'),
'd': (),
'e': ('b')}), # e has never heard of d
]
solutions = [NSquaredSolution, TwoPointersSolution]

for test_case in test_data:
print
print test_case.name, test_case.edges
for solution in solutions:
s = solution(test_case.edges).Solve()
s_repr = '%s(%d)' % (s.__class__.__name__,
s.has_acquaintance_counter)
if s.answer == test_case.expected:
print 'PASS %s: %s' % (s_repr, s.answer)
else:
print 'FAIL %s: (expected %s, got %s)' % (s_repr,
test_case.expected,
s.answer)


if __name__ == '__main__':
main()