Skip to content

Commit aa962be

Browse files
committed
s
0 parents  commit aa962be

25 files changed

+2579898
-0
lines changed

0-search/0a-degrees/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Running
2+
3+
```python
4+
# Using the small database
5+
python degrees.py small
6+
7+
# Using the large database
8+
python degrees.py large
9+
```

0-search/0a-degrees/degrees.py

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import csv
2+
import sys
3+
from util import Node, QueueFrontier
4+
5+
# Maps names to a set of corresponding person_ids
6+
names = {}
7+
8+
# Maps person_ids to a dictionary of: name, birth, movies (a set of movie_ids)
9+
people = {}
10+
11+
# Maps movie_ids to a dictionary of: title, year, stars (a set of person_ids)
12+
movies = {}
13+
14+
15+
def load_data(directory):
16+
"""
17+
Load data from CSV files into memory.
18+
"""
19+
# Load people
20+
with open(f"{directory}/people.csv", encoding="utf-8") as f:
21+
reader = csv.DictReader(f)
22+
for row in reader:
23+
people[row["id"]] = {
24+
"name": row["name"],
25+
"birth": row["birth"],
26+
"movies": set()
27+
}
28+
# Add to names
29+
if row["name"].lower() not in names:
30+
names[row["name"].lower()] = { row["id"] }
31+
else:
32+
names[row["name"].lower()].add(row["id"])
33+
34+
# Load movies
35+
with open(f"{directory}/movies.csv", encoding="utf-8") as f:
36+
reader = csv.DictReader(f)
37+
for row in reader:
38+
movies[row["id"]] = {
39+
"title": row["title"],
40+
"year": row["year"],
41+
"stars": set()
42+
}
43+
44+
# Load stars
45+
with open(f"{directory}/stars.csv", encoding="utf-8") as f:
46+
reader = csv.DictReader(f)
47+
for row in reader:
48+
try:
49+
people[row["person_id"]]["movies"].add(row["movie_id"])
50+
movies[row["movie_id"]]["stars"].add(row["person_id"])
51+
except KeyError:
52+
pass
53+
54+
55+
def main():
56+
if len(sys.argv) > 2: sys.exit("Usage: python degrees.py [directory]")
57+
directory = sys.argv[1] if len(sys.argv) == 2 else "large"
58+
59+
# Load data from files into memory
60+
print("Loading data...")
61+
load_data(directory)
62+
print("Data loaded.")
63+
64+
source = person_id_for_name(input("Name: "))
65+
if source is None:
66+
sys.exit("Person not found.")
67+
target = person_id_for_name(input("Name: "))
68+
if target is None:
69+
sys.exit("Person not found.")
70+
71+
path = shortest_path(source, target)
72+
73+
if path is None:
74+
print("Not connected.")
75+
else:
76+
degrees = len(path)
77+
print(f"{degrees} degrees of separation.")
78+
path = [(None, source)] + path
79+
for i in range(degrees):
80+
person1 = people[path[i][1]]["name"]
81+
person2 = people[path[i+1][1]]["name"]
82+
movie = movies[path[i+1][0]]["title"]
83+
print(f"{i + 1}: {person1} and {person2} starred in {movie}")
84+
85+
86+
def shortest_path(source: int, target: int):
87+
"""
88+
Returns the shortest list of (movie_id, person_id) pairs that connect the person with id source to the person with id target.
89+
90+
If no possible path, returns None.
91+
"""
92+
93+
node = Node(state=source) # state: person_id, action: movie_id
94+
explored = set() # set of person_id
95+
96+
frontier = QueueFrontier()
97+
frontier.add(node)
98+
99+
while (not frontier.empty()):
100+
node = frontier.remove()
101+
explored.add(node.state)
102+
103+
for action, state in neighbors_for_person(node.state):
104+
if not frontier.contains_state(state) and state not in explored:
105+
child = Node(state, node, action)
106+
frontier.add(child)
107+
108+
if (child.state == target):
109+
return recover(child)
110+
111+
return None
112+
113+
def recover(node: Node):
114+
"""
115+
Returns the nodes as a list of (action, state)
116+
"""
117+
solution = []
118+
while (node.parent is not None):
119+
solution.append((node.action, node.state))
120+
node = node.parent
121+
solution.reverse()
122+
return solution
123+
124+
125+
def person_id_for_name(name):
126+
"""
127+
Returns the IMDB id for a person's name, resolving ambiguities as needed.
128+
"""
129+
person_ids = list(names.get(name.lower(), set()))
130+
if len(person_ids) == 0:
131+
return None
132+
elif len(person_ids) > 1:
133+
print(f"Which '{name}'?")
134+
for person_id in person_ids:
135+
person = people[person_id]
136+
name = person["name"]
137+
birth = person["birth"]
138+
print(f"ID: {person_id}, Name: {name}, Birth: {birth}")
139+
try:
140+
person_id = input("Intended Person ID: ")
141+
if person_id in person_ids:
142+
return person_id
143+
except ValueError:
144+
pass
145+
return None
146+
else:
147+
return person_ids[0]
148+
149+
150+
def neighbors_for_person(person_id):
151+
"""
152+
Returns a set of (movie_id, person_id) pairs for people who starred with a given person_id.
153+
"""
154+
movie_ids = people[person_id]["movies"]
155+
neighbors = set()
156+
for movie_id in movie_ids:
157+
for person_id in movies[movie_id]["stars"]:
158+
neighbors.add((movie_id, person_id))
159+
return neighbors
160+
161+
162+
if __name__ == "__main__":
163+
main()

0 commit comments

Comments
 (0)