Skip to content

solution to calculate degree of separation #44

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 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 16 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
#Degrees of Separation
# Steps to run the program

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.
## Navigate to the degrees folder
```
$ cd degrees
```

## Compile the program
```
$ go build -o degrees ./main.go

Write a Go program that behaves the following way:
```

## Run the executable by providing person urls as input
```
$ ./degrees amitabh-bachchan robert-de-niro
```
$ degrees amitabh-bachchan robert-de-niro

## Example of output
```
Degrees of Separation: 3

1. Movie: The Great Gatsby
Expand All @@ -20,33 +31,5 @@ Director: Martin Scorsese
3. Movie: Taxi Driver
Director: Martin Scorsese
Actor: Robert De Niro
```

Your solution should use the Moviebuff data available to figure out the smallest degree of separation between the two people.
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`.

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}`

To solve the example above, your solution would fetch at least the following:

http://data.moviebuff.com/amitabh-bachchan

http://data.moviebuff.com/the-great-gatsby

http://data.moviebuff.com/leonardo-dicaprio

http://data.moviebuff.com/the-wolf-of-wall-street

http://data.moviebuff.com/martin-scorsese

http://data.moviebuff.com/taxi-driver

##Notes
* 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 :)
* 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.

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.

To submit a solution, fork this repo and send a Pull Request on Github.

For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can.
```
25 changes: 25 additions & 0 deletions degrees/datastructs/structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package datastructs

type General struct {
Url string
Name string
}

type Entity struct {
General
Type string
}
type Info struct {
General
Role string
}

type Movie struct {
Entity
Cast []Info
}

type Person struct {
Entity
Movies []Info
}
197 changes: 197 additions & 0 deletions degrees/degreecalculator/calculate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package degreecalculator

import (
"degrees/moviebuffclient"
"fmt"
"sync"
"sync/atomic"
)

type void struct{}

type node struct {
parent *node
parentRole string
person
}

type person struct {
name string
url string
role string
movie string
}

type Result struct {
Level int
Node *node
Err error
}

var pregL, mregL sync.Mutex

func Calculate(p1, p2 string) (int, *node, error) {
ch := make(chan Result)
// maintains the list of visited urls in the search process
registry := make(map[string]void)
registry[p1] = void{}
movieRegistry := make(map[string]void)
parentNode := &node{person: person{url: p1}}
list := []*node{parentNode}
go traverse(ch, 1, p2, list, registry, movieRegistry)
res := <-ch
return res.Level, res.Node, res.Err
}

func traverse(ch chan Result, level int, destinationUrl string, list []*node, registry map[string]void, movieRegistry map[string]void) {

if len(list) == 0 {
ch <- Result{-1, nil, nil}
return
}

terminate := new(atomic.Bool)
terminate.Store(false)

var nextLevelList []*node

wg := sync.WaitGroup{}
maxGoroutines := make(chan struct{}, 150)
defer close(maxGoroutines)

// fetch direct assocaited persons of all the person in the current level
for _, p := range list {
if terminate.Load() {
return
}
wg.Add(1)
maxGoroutines <- struct{}{}

go func(p *node) {
defer wg.Done()
defer func() {
<-maxGoroutines
}()

// fetch person info
personInfo, err := moviebuffclient.GetPersonInfo(p.url)
if err != nil {
ch <- Result{-2, nil, err}
return
}

if p.parent == nil {
// update person info in the node
p.name = personInfo.Name
}

for _, m := range personInfo.Movies {
// check if the movie is already visited
mregL.Lock()
if _, ok := movieRegistry[m.Url]; ok {
mregL.Unlock()
continue
}
// add the movie to the registry
movieRegistry[m.Url] = void{}
mregL.Unlock()

// fetch movie info
movieInfo, err := moviebuffclient.GetMovieInfo(m.Url)
if err != nil {
ch <- Result{-2, nil, err}
return
}

parentRole := m.Role

for _, c := range movieInfo.Cast {
// generate a new node
newNode := &node{
p,
parentRole,
person{
name: c.Name,
url: c.Url,
role: c.Role,
movie: movieInfo.Name,
},
}

// check if the destination url is reached
if c.Url == destinationUrl {
if terminate.Load() {
return
}
ch <- Result{level, newNode, nil} // complete the function
terminate.Store(true)
return
}
// check if the person is already visited
pregL.Lock()
if _, ok := registry[c.Url]; !ok {
// add the person to the registry
registry[c.Url] = void{}
// add the person to the next level
nextLevelList = append(nextLevelList, newNode)
pregL.Unlock()
continue
}
pregL.Unlock()

}
}
}(p)
}
wg.Wait()

traverse(ch, level+1, destinationUrl, nextLevelList, registry, movieRegistry)
}

func PrintRespose(level int, n *node, err error) {
if checkForNonTrivialBehavior(level, err) {
return
}
var entries []string
fmt.Println("Degrees of seperation: ", level)
count := level
cur := n
for cur.parent != nil {
entries = append(entries, fmt.Sprintf(`%d. Movie: %s
%s: %s
%s: %s%s`, count, cur.movie, cur.parentRole, cur.parent.name, cur.role, cur.name, "\n"))
cur = cur.parent
count--
}
for i := len(entries) - 1; i >= 0; i-- {
fmt.Println(entries[i])
}
}

func PrintResponseInReverse(level int, n *node, err error) {
if checkForNonTrivialBehavior(level, err) {
return
}
fmt.Println("Degrees of seperation: ", level)
count := 1
cur := n
for cur.parent != nil {
fmt.Printf(`%d. Movie: %s
%s: %s
%s: %s%s`, count, cur.movie, cur.role, cur.name, cur.parentRole, cur.parent.name, "\n")
cur = cur.parent
count++
}
}

func checkForNonTrivialBehavior(level int, err error) bool {
if err != nil {
fmt.Println("Error in calculating degree of seperation:", err)
return true
}
if level == -1 {
fmt.Println("No degree of seperation found")
return true
}
return false
}
3 changes: 3 additions & 0 deletions degrees/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module degrees

go 1.19
54 changes: 54 additions & 0 deletions degrees/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"degrees/degreecalculator"
"degrees/moviebuffclient"
"fmt"
"os"
)

func main() {
// take input as commnad line arguments
input := os.Args
if len(input) < 3 {
fmt.Println("Please provide two person urls as command line arguments")
return
}
person1Url := input[1]
person2Url := input[2]

// validate the input
dir, err := validateInputAndProvideFlowDirection(person1Url, person2Url)
if err != nil {
fmt.Println(fmt.Errorf("error while validating input: %s", err))
return
}

// calculate the degree of separation
if dir {
separation, chainInfo, err := degreecalculator.Calculate(person1Url, person2Url)
degreecalculator.PrintRespose(separation, chainInfo, err)
return
}
separation, chainInfo, err := degreecalculator.Calculate(person2Url, person1Url)
degreecalculator.PrintResponseInReverse(separation, chainInfo, err)

}

func validateInputAndProvideFlowDirection(person1Url string, person2Url string) (bool, error) {
if person1Url == person2Url {
return false, fmt.Errorf("given urls are same. Please provide two different urls")
}
p1Info, err := moviebuffclient.GetPersonInfo(person1Url)
if err != nil {
return false, err
}
p2Info, err := moviebuffclient.GetPersonInfo(person2Url)
if err != nil {
return false, err
}
if len(p1Info.Movies) < len(p2Info.Movies) {
return true, nil
}
return false, nil
}
52 changes: 52 additions & 0 deletions degrees/moviebuffclient/clients.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package moviebuffclient

import (
"degrees/datastructs"
"encoding/json"
"fmt"
"net/http"
"time"
)

var c = &http.Client{
Timeout: 30 * time.Second,
}

func MakeHttpReq(suffix string) (*http.Response, error) {
httpUrl := fmt.Sprintf("http://data.moviebuff.com/%s", suffix)
req, err := http.NewRequest("GET", httpUrl, nil)
if err != nil {
return nil, err
}
res, err := c.Do(req)
return res, err
}

func GetPersonInfo(personUrl string) (*datastructs.Person, error) {
res, err := MakeHttpReq(personUrl)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data datastructs.Person
if res.StatusCode != 200 {
return &data, nil
}

err = json.NewDecoder(res.Body).Decode(&data)
return &data, err
}

func GetMovieInfo(movieUrl string) (*datastructs.Movie, error) {
res, err := MakeHttpReq(movieUrl)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data datastructs.Movie
if res.StatusCode != 200 {
return &data, nil
}
err = json.NewDecoder(res.Body).Decode(&data)
return &data, err
}