diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a74d569 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +.PHONY: build + +build: + go build + ./degrees amitabh-bachchan bunty-behl \ No newline at end of file diff --git a/degrees b/degrees new file mode 100755 index 0000000..49222d3 Binary files /dev/null and b/degrees differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..afdbe65 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module challenge2015 + +go 1.23.3 diff --git a/main.go b/main.go new file mode 100644 index 0000000..62ddd7e --- /dev/null +++ b/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + + data := os.Args + if len(data) != 3 { + fmt.Println("Kindly provide correct arguments") + return + } + + DegreesofSeperation(data[1], data[2]) +} diff --git a/movieBuff.go b/movieBuff.go new file mode 100644 index 0000000..2bf6dfb --- /dev/null +++ b/movieBuff.go @@ -0,0 +1,192 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "sync" +) + +// Node structure +type Node struct { + ID string + Type string + Parent *Node + Role string +} + +func DegreesofSeperation(name1, name2 string) { + fmt.Println("Searching for path between", name1, "and", name2) + + start := &Node{ID: name1, Type: "person", Parent: nil} + queue := []*Node{start} + visited := sync.Map{} // concurrent-safe map + + jobs := make(chan *Node, 20) + results := make(chan []*Node, 20) + + // Start a fixed pool of workers + const workerCount = 10 + var wg sync.WaitGroup + for i := 0; i < workerCount; i++ { + wg.Add(1) + go bfsWorker(jobs, results, &visited, name2, &wg) + } + + for len(queue) > 0 { + current := queue[0] + queue = queue[1:] + + if _, ok := visited.Load(current.ID); ok { + continue + } + visited.Store(current.ID, true) + + jobs <- current + newNodes := <-results + + for _, node := range newNodes { + if node.ID == name2 { + PrintPath(name1, node) + close(jobs) + wg.Wait() + return + } + queue = append(queue, node) + } + } + + close(jobs) + wg.Wait() + fmt.Println("No connection found.") +} + +func bfsWorker(jobs <-chan *Node, results chan<- []*Node, visited *sync.Map, target string, wg *sync.WaitGroup) { + defer wg.Done() + + for node := range jobs { + var nextNodes []*Node + + if node.Type == "person" { + personData, err := GetPersonDetails(node.ID) + if err != nil { + results <- nextNodes + continue + } + if movies, ok := personData["movies"].([]interface{}); ok { + for _, m := range movies { + movie := m.(map[string]interface{}) + movieID := movie["url"].(string) + + if _, ok := visited.Load(movieID); !ok { + nextNodes = append(nextNodes, &Node{ + ID: movieID, + Type: "movie", + Parent: node, + Role: getRole(movie), + }) + } + } + } + } else if node.Type == "movie" { + movieData, err := GetMovieDetails(node.ID) + if err != nil { + results <- nextNodes + continue + } + for _, roleType := range []string{"cast", "crew"} { + if arr, ok := movieData[roleType].([]interface{}); ok { + for _, p := range arr { + person := p.(map[string]interface{}) + personID := person["url"].(string) + + if personID == target { + results <- []*Node{&Node{ + ID: personID, + Type: "person", + Parent: node, + Role: getRole(person), + }} + return + } + + if _, ok := visited.Load(personID); !ok { + nextNodes = append(nextNodes, &Node{ + ID: personID, + Type: "person", + Parent: node, + Role: getRole(person), + }) + } + } + } + } + } + results <- nextNodes + } +} + +// Helper to safely extract role +func getRole(data map[string]interface{}) string { + if role, ok := data["role"].(string); ok { + return role + } + return "" +} + +// Trace the path from target to source +func PrintPath(name1 string, end *Node) { + var path []*Node + var role string + for current := end; current != nil; current = current.Parent { + path = append([]*Node{current}, path...) + } + + degree := (len(path) - 1) / 2 + fmt.Printf("Degrees of Separation: %d\n\n", degree) + + step := 1 + for i := 0; i < len(path)-2; i += 2 { + movie := path[i+1] + personA := path[i] + personB := path[i+2] + if name1 == personA.ID { + role = end.Parent.Role + } else { + role = personA.Role + } + + fmt.Printf("%d. Movie: %s\n", step, movie.ID) + fmt.Printf(" %s: %s\n", role, personA.ID) + fmt.Printf(" %s: %s\n\n", personB.Role, personB.ID) + + step++ + } +} + +// Fetch person info from MovieBuff +func GetPersonDetails(person string) (map[string]interface{}, error) { + url := "https://data.moviebuff.com/" + person + resp, err := http.Get(url) + if err != nil { + return nil, errors.New("unable to fetch " + person) + } + defer resp.Body.Close() + var personResponse map[string]interface{} + json.NewDecoder(resp.Body).Decode(&personResponse) + return personResponse, nil +} + +// Fetch movie info from MovieBuff +func GetMovieDetails(movie string) (map[string]interface{}, error) { + url := "https://data.moviebuff.com/" + movie + resp, err := http.Get(url) + if err != nil { + return nil, errors.New("unable to fetch " + movie) + } + defer resp.Body.Close() + var movieResponse map[string]interface{} + json.NewDecoder(resp.Body).Decode(&movieResponse) + return movieResponse, nil +}