Skip to content

Commit e9b9918

Browse files
committed
solution to find the degree of seperaion
1 parent 9939adc commit e9b9918

File tree

6 files changed

+341
-32
lines changed

6 files changed

+341
-32
lines changed

README.md

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
#Degrees of Separation
1+
# Steps to run the program
22

3-
With cinema going global these days, every one of the [A-Z]ollywoods are now connected. Use the wealth of data available at [Moviebuff](http://www.moviebuff.com) to see how.
3+
## Compile the program
4+
```
5+
$ go build -o degrees ./main.go
46
5-
Write a Go program that behaves the following way:
7+
```
68

9+
## Run the executable by providing input
710
```
811
$ degrees amitabh-bachchan robert-de-niro
12+
```
913

14+
## Example of output
15+
```
1016
Degrees of Separation: 3
1117
1218
1. Movie: The Great Gatsby
@@ -20,33 +26,5 @@ Director: Martin Scorsese
2026
3. Movie: Taxi Driver
2127
Director: Martin Scorsese
2228
Actor: Robert De Niro
23-
```
24-
25-
Your solution should use the Moviebuff data available to figure out the smallest degree of separation between the two people.
26-
All the inputs should be Moviebuff URLs for their respective people: For Amitabh Bachchan, his page is on http://www.moviebuff.com/amitabh-bachchan and his Moviebuff URL is `amitabh-bachchan`.
27-
28-
Please do not attempt to scrape the Moviebuff website - All the data is available on an S3 bucket in an easy to parse JSON format here: `https://data.moviebuff.com/{moviebuff_url}`
29-
30-
To solve the example above, your solution would fetch at least the following:
31-
32-
http://data.moviebuff.com/amitabh-bachchan
33-
34-
http://data.moviebuff.com/the-great-gatsby
35-
36-
http://data.moviebuff.com/leonardo-dicaprio
37-
38-
http://data.moviebuff.com/the-wolf-of-wall-street
39-
40-
http://data.moviebuff.com/martin-scorsese
41-
42-
http://data.moviebuff.com/taxi-driver
43-
44-
##Notes
45-
* If you receive HTTP errors when trying to fetch the data, that might be the CDN throttling you. Luckily, Go has some very elegant idioms for rate limiting :)
46-
* There may be a discrepancy in some cases where a movie appears on an actor's list but not vice versa. This usually happens when we edit data while exporting it, so feel free to either ignore these mismatches or handle them in some way.
47-
48-
Write a program in any language you want (If you're here from Gophercon, use Go :D) that does this. Feel free to make your own input and output format / command line tool / GUI / Webservice / whatever you want. Feel free to hold the dataset in whatever structure you want, but try not to use external databases - as far as possible stick to your langauage without bringing in MySQL/Postgres/MongoDB/Redis/Etc.
49-
50-
To submit a solution, fork this repo and send a Pull Request on Github.
5129
52-
For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can.
30+
```

degrees/datastructs/structs.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package datastructs
2+
3+
type General struct {
4+
Url string
5+
Name string
6+
}
7+
8+
type Entity struct {
9+
General
10+
Type string
11+
}
12+
type Info struct {
13+
General
14+
Role string
15+
}
16+
17+
type Movie struct {
18+
Entity
19+
Cast []Info
20+
}
21+
22+
type Person struct {
23+
Entity
24+
Movies []Info
25+
}

degrees/degreecalculator/calculate.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package degreecalculator
2+
3+
import (
4+
"degrees/moviebuffclient"
5+
"fmt"
6+
"sync"
7+
"sync/atomic"
8+
)
9+
10+
type void struct{}
11+
12+
type node struct {
13+
parent *node
14+
parentRole string
15+
person
16+
}
17+
18+
type person struct {
19+
name string
20+
url string
21+
role string
22+
movie string
23+
}
24+
25+
type Result struct {
26+
Level int
27+
Node *node
28+
Err error
29+
}
30+
31+
var pregL, mregL sync.Mutex
32+
33+
func Calculate(p1, p2 string) (int, *node, error) {
34+
ch := make(chan Result)
35+
// maintains the list of visited urls in the search process
36+
registry := make(map[string]void)
37+
registry[p1] = void{}
38+
movieRegistry := make(map[string]void)
39+
parentNode := &node{person: person{url: p1}}
40+
list := []*node{parentNode}
41+
go traverse(ch, 1, p2, list, registry, movieRegistry)
42+
res := <-ch
43+
return res.Level, res.Node, res.Err
44+
}
45+
46+
func traverse(ch chan Result, level int, destinationUrl string, list []*node, registry map[string]void, movieRegistry map[string]void) {
47+
48+
if len(list) == 0 {
49+
ch <- Result{-1, nil, nil}
50+
return
51+
}
52+
53+
terminate := new(atomic.Bool)
54+
terminate.Store(false)
55+
56+
var nextLevelList []*node
57+
58+
wg := sync.WaitGroup{}
59+
maxGoroutines := make(chan struct{}, 150)
60+
defer close(maxGoroutines)
61+
62+
// fetch direct assocaited persons of all the person in the current level
63+
for _, p := range list {
64+
if terminate.Load() {
65+
return
66+
}
67+
wg.Add(1)
68+
maxGoroutines <- struct{}{}
69+
70+
go func(p *node) {
71+
defer wg.Done()
72+
defer func() {
73+
<-maxGoroutines
74+
}()
75+
76+
// fetch person info
77+
personInfo, err := moviebuffclient.GetPersonInfo(p.url)
78+
if err != nil {
79+
ch <- Result{-2, nil, err}
80+
return
81+
}
82+
83+
if p.parent == nil {
84+
// update person info in the node
85+
p.name = personInfo.Name
86+
}
87+
88+
for _, m := range personInfo.Movies {
89+
// check if the movie is already visited
90+
mregL.Lock()
91+
if _, ok := movieRegistry[m.Url]; ok {
92+
mregL.Unlock()
93+
continue
94+
}
95+
// add the movie to the registry
96+
movieRegistry[m.Url] = void{}
97+
mregL.Unlock()
98+
99+
// fetch movie info
100+
movieInfo, err := moviebuffclient.GetMovieInfo(m.Url)
101+
if err != nil {
102+
ch <- Result{-2, nil, err}
103+
return
104+
}
105+
106+
parentRole := m.Role
107+
108+
for _, c := range movieInfo.Cast {
109+
// generate a new node
110+
newNode := &node{
111+
p,
112+
parentRole,
113+
person{
114+
name: c.Name,
115+
url: c.Url,
116+
role: c.Role,
117+
movie: movieInfo.Name,
118+
},
119+
}
120+
121+
// check if the destination url is reached
122+
if c.Url == destinationUrl {
123+
if terminate.Load() {
124+
return
125+
}
126+
ch <- Result{level, newNode, nil} // complete the function
127+
terminate.Store(true)
128+
return
129+
}
130+
// check if the person is already visited
131+
pregL.Lock()
132+
if _, ok := registry[c.Url]; !ok {
133+
// add the person to the registry
134+
registry[c.Url] = void{}
135+
// add the person to the next level
136+
nextLevelList = append(nextLevelList, newNode)
137+
pregL.Unlock()
138+
continue
139+
}
140+
pregL.Unlock()
141+
142+
}
143+
}
144+
}(p)
145+
}
146+
wg.Wait()
147+
148+
traverse(ch, level+1, destinationUrl, nextLevelList, registry, movieRegistry)
149+
}
150+
151+
func PrintRespose(level int, n *node, err error) {
152+
if checkForNonTrivialBehavior(level, err) {
153+
return
154+
}
155+
var entries []string
156+
fmt.Println("Degrees of seperation: ", level)
157+
count := level
158+
cur := n
159+
for cur.parent != nil {
160+
entries = append(entries, fmt.Sprintf(`%d. Movie: %s
161+
%s: %s
162+
%s: %s%s`, count, cur.movie, cur.parentRole, cur.parent.name, cur.role, cur.name, "\n"))
163+
cur = cur.parent
164+
count--
165+
}
166+
for i := len(entries) - 1; i >= 0; i-- {
167+
fmt.Println(entries[i])
168+
}
169+
}
170+
171+
func PrintResponseInReverse(level int, n *node, err error) {
172+
if checkForNonTrivialBehavior(level, err) {
173+
return
174+
}
175+
fmt.Println("Degrees of seperation: ", level)
176+
count := 1
177+
cur := n
178+
for cur.parent != nil {
179+
fmt.Printf(`%d. Movie: %s
180+
%s: %s
181+
%s: %s%s`, count, cur.movie, cur.role, cur.name, cur.parentRole, cur.parent.name, "\n")
182+
cur = cur.parent
183+
count++
184+
}
185+
}
186+
187+
func checkForNonTrivialBehavior(level int, err error) bool {
188+
if err != nil {
189+
fmt.Println("Error in calculating degree of seperation:", err)
190+
return true
191+
}
192+
if level == -1 {
193+
fmt.Println("No degree of seperation found")
194+
return true
195+
}
196+
return false
197+
}

degrees/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module degrees
2+
3+
go 1.19

degrees/main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package main
2+
3+
import (
4+
"degrees/degreecalculator"
5+
"degrees/moviebuffclient"
6+
"fmt"
7+
"os"
8+
)
9+
10+
func main() {
11+
// take input as commnad line arguments
12+
input := os.Args
13+
if len(input) < 3 {
14+
fmt.Println("Please provide two person urls as command line arguments")
15+
return
16+
}
17+
person1Url := input[1]
18+
person2Url := input[2]
19+
20+
// validate the input
21+
dir, err := validateInputAndProvideFlowDirection(person1Url, person2Url)
22+
if err != nil {
23+
fmt.Println(fmt.Errorf("error while validating input: %s", err))
24+
return
25+
}
26+
27+
// calculate the degree of separation
28+
if dir {
29+
separation, chainInfo, err := degreecalculator.Calculate(person1Url, person2Url)
30+
degreecalculator.PrintRespose(separation, chainInfo, err)
31+
return
32+
}
33+
separation, chainInfo, err := degreecalculator.Calculate(person2Url, person1Url)
34+
degreecalculator.PrintResponseInReverse(separation, chainInfo, err)
35+
36+
}
37+
38+
func validateInputAndProvideFlowDirection(person1Url string, person2Url string) (bool, error) {
39+
if person1Url == person2Url {
40+
return false, fmt.Errorf("given urls are same. Please provide two different urls")
41+
}
42+
p1Info, err := moviebuffclient.GetPersonInfo(person1Url)
43+
if err != nil {
44+
return false, err
45+
}
46+
p2Info, err := moviebuffclient.GetPersonInfo(person2Url)
47+
if err != nil {
48+
return false, err
49+
}
50+
if len(p1Info.Movies) < len(p2Info.Movies) {
51+
return true, nil
52+
}
53+
return false, nil
54+
}

degrees/moviebuffclient/clients.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package moviebuffclient
2+
3+
import (
4+
"degrees/datastructs"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
)
10+
11+
var c = &http.Client{
12+
Timeout: 30 * time.Second,
13+
}
14+
15+
func MakeHttpReq(suffix string) (*http.Response, error) {
16+
httpUrl := fmt.Sprintf("http://data.moviebuff.com/%s", suffix)
17+
req, err := http.NewRequest("GET", httpUrl, nil)
18+
if err != nil {
19+
return nil, err
20+
}
21+
res, err := c.Do(req)
22+
return res, err
23+
}
24+
25+
func GetPersonInfo(personUrl string) (*datastructs.Person, error) {
26+
res, err := MakeHttpReq(personUrl)
27+
if err != nil {
28+
return nil, err
29+
}
30+
defer res.Body.Close()
31+
var data datastructs.Person
32+
if res.StatusCode != 200 {
33+
return &data, nil
34+
}
35+
36+
err = json.NewDecoder(res.Body).Decode(&data)
37+
return &data, err
38+
}
39+
40+
func GetMovieInfo(movieUrl string) (*datastructs.Movie, error) {
41+
res, err := MakeHttpReq(movieUrl)
42+
if err != nil {
43+
return nil, err
44+
}
45+
defer res.Body.Close()
46+
var data datastructs.Movie
47+
if res.StatusCode != 200 {
48+
return &data, nil
49+
}
50+
err = json.NewDecoder(res.Body).Decode(&data)
51+
return &data, err
52+
}

0 commit comments

Comments
 (0)