-
Notifications
You must be signed in to change notification settings - Fork 91
/
Copy pathsearch.go
169 lines (148 loc) · 4.22 KB
/
search.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package main
import (
"fmt"
"os"
"sync"
)
var (
// semaphores and concurrency safe Cache
sm = NewSyncManager()
Cache = sync.Map{}
queue = []QueueData{} // A Queue to track artist details for BFS traversal
parent = make(map[string]Path) // Parent Map to keep track of the Path
currentPerson = ""
)
func Separation(artistA Person, artistB string) {
go func() {
printResult(artistA.URL)
os.Exit(0)
}()
queue = append(queue, QueueData{
URL: artistA.URL,
Distance: 0,
})
for len(queue) > 0 {
// Pop a node from Queue
current := queue[0]
queue = queue[1:]
// Update the Path on each iteration
key := current.URL
if _, ok := parent[key]; !ok {
parent[key] = Path{
ParentURL: current.ParentURL,
Movie: current.Movie,
Role: current.Role,
ParentRole: current.ParentRole,
}
}
if current.URL == artistB {
currentPerson = current.URL
sm.dos <- current.Distance
}
person, ok := Cache.Load(current.URL)
if !ok {
/* person isn't present in the cache.
Fetch personDetails and update the cache.*/
personDetails, err := FetchEntityDetails[Person](current.URL)
if err != nil {
if err.Error()[:4] == "403" {
/* Storing the details when encountered 403 error
so that we do not make a call to the same url again */
Cache.Store(current.URL, Person{
Type: "Forbidden",
})
}
}
if personDetails != nil {
Cache.Store(current.URL, *personDetails)
person = *personDetails
}
}
if person != nil && person.(Person).Type != "Forbidden" {
// Iterate through the MovieList to find related Persons
for _, movie := range person.(Person).Movies {
sm.wg.Add(1)
go handleMovieData(movie, current, artistB)
}
sm.wg.Wait()
}
}
}
// Function to handle the movie Data. i.e. find the linked artists and push them on the queue
func handleMovieData(m Details, current QueueData, artistB string) {
defer sm.wg.Done()
movie, ok := Cache.Load(m.URL)
if !ok {
/* Movie isn't present in the cache.
Fetch movieDetails and update the cache */
movieDetails, err := FetchEntityDetails[Movie](m.URL)
if err != nil {
if err.Error()[:4] == "403" {
/* Storing the details when encountered 403 error
so that we do not make a call to the same url again */
Cache.Store(m.URL, Movie{
Type: "Forbidden",
})
}
}
if movieDetails != nil {
Cache.Store(m.URL, *movieDetails)
movie = *movieDetails
}
}
// Check if movie is Valid
if movie != nil && movie.(Movie).Type != "Forbidden" {
// Get the total list of related artists and append them to the queue with added distance
artists := append(movie.(Movie).Cast, movie.(Movie).Crew...)
sm.mu.Lock()
for _, a := range artists {
// Push artists on the queue
queue = append(queue, QueueData{
// Artist Details pushed on the queue
URL: a.URL,
Movie: m.URL,
Role: a.Role,
// Parent details pushed on the queue
ParentURL: current.URL,
ParentRole: m.Role,
// Increment the distance
Distance: current.Distance + 1,
})
if a.URL == artistB {
// If found, update the path and signal degrees of separation
key := a.URL
parent[key] = Path{
ParentURL: current.URL,
Movie: m.URL,
Role: a.Role,
ParentRole: current.Role,
}
currentPerson = a.URL
sm.dos <- current.Distance + 1
}
}
sm.mu.Unlock()
}
}
// Function to Print the results in the specified Format
func printResult(sourceArtistURL string) {
degrees := <-sm.dos
fmt.Println("Distance of Separation: ", degrees)
parentPerson := parent[currentPerson]
for {
// A defer function call helps tracing the path in a reverse order.
defer func(parentPerson Path, currentPerson string, count int) {
// Fetch the names of parent, person and movie
parentName, personName, movieName := GetNames(parentPerson.ParentURL, currentPerson, parentPerson.Movie)
fmt.Printf("\n%d. Movie: %s", count, movieName)
fmt.Printf("\n%s: %s", parentPerson.ParentRole, parentName)
fmt.Printf("\n%s: %s\n", parentPerson.Role, personName)
}(parentPerson, currentPerson, degrees)
currentPerson = parentPerson.ParentURL
if currentPerson == sourceArtistURL {
break
}
parentPerson = parent[currentPerson]
degrees--
}
}