Skip to content

challenge2015 solution | @c-harish #55

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 5 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
41 changes: 41 additions & 0 deletions INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Challenge 2015

This solution finds the shortest degree of connection between two actors based on their movie collaborations using single source shortest path breadth-first algorithm.

## Prerequisites

- Go 1.23.5 or later

## Installation

1. Clone the repository:
```sh
git clone https://github.com/c-harish/challenge2015.git
cd challenge2015
```

2. Install dependencies:
```sh
go mod tidy
```

## Usage

1. Run the program:
```sh
go run main.go
```

2. Enter the source actor's moviebuff URL when prompted:
```
source actor : <source_actor_url>
```

3. Enter the target actor's moviebuff URL when prompted:
```
target actor : <target_actor_url>
```

Refer to https://www.moviebuff.com/ for valid actor_url

The program will output the degrees of separation and the list movies connecting the two actors.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module challenge2015

go 1.23.5
202 changes: 202 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sync"
)

type Person struct {
Name string `json:"name"`
URL string `json:"url"`
Movies []PersonMovie `json:"movies"`
}

type PersonMovie struct {
Name string `json:"name"`
URL string `json:"url"`
Role string `json:"role"`
}

type Movie struct {
Name string `json:"name"`
URL string `json:"url"`
Cast []MoviePerson `json:"cast"`
Crew []MoviePerson `json:"crew"`
}

type MoviePerson struct {
Name string `json:"name"`
URL string `json:"url"`
Role string `json:"role"`
}

var cache = make(map[string]interface{})
var cacheLock = &sync.Mutex{}

func fetchData(url string) ([]byte, error) {
resp, err := http.Get("https://data.moviebuff.com/" + url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}

type Path struct {
Depth []Step
}

type Step struct {
Movie string
Role string
Person string
}

func findConnection(start, end string) (*Path, error) {
// Find Shortest Path from single source using BFS with Queue iteratively
visited := make(map[string]bool)
queue := make([]Path, 0)

queue = append(queue, Path{Depth: []Step{}})
visited[start] = true

for len(queue) > 0 {
currentPath := queue[0]
queue = queue[1:]

currentPerson := start
if len(currentPath.Depth) > 0 {
currentPerson = currentPath.Depth[len(currentPath.Depth)-1].Person
}

// Check if data exists in cache before making API call
var person Person
data, exists := cache[currentPerson]
if !exists {
rawData, err := fetchDataAsync(currentPerson)
if err != nil {
continue
}
if err := json.Unmarshal(rawData, &person); err != nil {
continue
}
// Store the response in Cache for future use
// Using Lock to make map thread-safe
cacheLock.Lock()
cache[currentPerson] = person
cacheLock.Unlock()
} else {
person = data.(Person)
}

var wg sync.WaitGroup
movieChan := make(chan Movie, len(person.Movies))

for _, movie := range person.Movies {
wg.Add(1)
// Fetching movie data asynchronously by spawning goroutine
go func(movie PersonMovie) {
defer wg.Done()
var movieData Movie
data, exists := cache[movie.URL]
if !exists {
rawData, err := fetchDataAsync(movie.URL)
if err != nil {
return
}
if err := json.Unmarshal(rawData, &movieData); err != nil {
return
}
cacheLock.Lock()
cache[movie.URL] = movieData
cacheLock.Unlock()
} else {
movieData = data.(Movie)
}
movieChan <- movieData
}(movie)
}

go func() {
wg.Wait()
close(movieChan)
}()

for movieData := range movieChan {
// Check in both cast and crew of the movie for connections
for _, p := range append(movieData.Cast, movieData.Crew...) {
if visited[p.URL] {
continue
}

visited[p.URL] = true

newPath := Path{
Depth: append(currentPath.Depth, Step{
Movie: movieData.Name,
Role: p.Role,
Person: p.URL,
}),
}

if p.URL == end {
return &newPath, nil
}

queue = append(queue, newPath)
}
}
}

return nil, fmt.Errorf("connection error")
}

func fetchDataAsync(url string) ([]byte, error) {
dataChan := make(chan []byte, 1)
errChan := make(chan error, 1)

go func() {
data, err := fetchData(url)
if err != nil {
errChan <- err
return
}
dataChan <- data
}()

select {
case data := <-dataChan:
return data, nil
case err := <-errChan:
return nil, err
}
}

func main() {
var person1, person2 string

fmt.Print("source actor : ")
fmt.Scanf("%s", &person1)
fmt.Print("target actor : ")
fmt.Scanf("%s", &person2)
path, err := findConnection(person1, person2)
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}

fmt.Printf("\nDegrees of Separation: %d\n\n", len(path.Depth))
for i, step := range path.Depth {
fmt.Printf("%d. Movie: %s\n", i+1, step.Movie)
if i == 0 {
fmt.Printf(" %s: %s\n", step.Role, person1)
} else {
fmt.Printf(" %s: %s\n", path.Depth[i-1].Role, path.Depth[i-1].Person)
}
fmt.Printf(" %s: %s\n", step.Role, step.Person)
}
}