Skip to content

[Qube Cinema] Code Challenge - Manav Sinha #51

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 1 commit 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
14 changes: 14 additions & 0 deletions dtos/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dtos

import "github.com/sinhamanav030/challange2015/models"

type MovieAPIResponse struct {
Name string `json:"name"`
Cast []models.Actor `json:"cast"`
Crew []models.Actor `json:"crew"`
}

type ActorAPIResponse struct {
Name string `json:"name"`
Movies []models.Movie `json:"movies"`
}
10 changes: 10 additions & 0 deletions dtos/seperation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dtos

import (
"github.com/sinhamanav030/challange2015/models"
)

type SeperationDegreeResponse struct {
SeperationDegree int
Path []models.RelationNode
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/sinhamanav030/challange2015

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

import (
"log"
"os"
"strings"

"github.com/sinhamanav030/challange2015/models"
service "github.com/sinhamanav030/challange2015/service"
)

func main() {

args := os.Args[1:]

if len(args) < 2 || strings.Compare(args[0], args[1]) == 0 {
log.Fatal("Incorrect: require unique source actor and dest actor url")
}

svc := service.NewSeperationDegreeService()

svc.Find(models.Actor{URL: args[0]}, models.Actor{URL: args[1]})

}
7 changes: 7 additions & 0 deletions models/actor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package models

type Actor struct {
Name string `json:"name"`
URL string `json:"url"`
Role string `json:"role"`
}
7 changes: 7 additions & 0 deletions models/movies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package models

type Movie struct {
Name string `json:"name"`
URL string `json:"url"`
Role string `json:"role"`
}
21 changes: 21 additions & 0 deletions models/queue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package models

type QueueNode struct {
Person Actor
Role string
Path []RelationNode
IsDest bool
}

type RelationNode struct {
Degree int
Movie string
FrstPerson Actor
SecondPerson Actor
}

type QueueReader struct {
Queue []QueueNode
DestFound bool
ResultNode QueueNode
}
8 changes: 8 additions & 0 deletions out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
2024/10/26 10:07:25 [amitabh-bachchan robert-de-niro]
2024/10/26 10:07:25 degree: 1
2024/10/26 10:07:55 degree: 2
2024/10/26 10:15:04 error while fetching actor details
2024/10/26 10:17:11 error while fetching actor details
2024/10/26 10:24:11 error while fetching actor details
2024/10/26 10:24:35 degree: 3
signal: interrupt
188 changes: 188 additions & 0 deletions service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package service

import (
"context"
"fmt"
"log"
"strings"
"sync"

"github.com/sinhamanav030/challange2015/models"
moviebuff "github.com/sinhamanav030/challange2015/utils/movieBuff"
mutexMap "github.com/sinhamanav030/challange2015/utils/mutexMap"
)

type SeperationDegree interface {
Find(source, dest models.Actor)
}

func NewSeperationDegreeService() SeperationDegree {
return &seperationDegreeService{
client: moviebuff.NewClient(),
logger: *log.Default(),
}
}

type seperationDegreeService struct {
client moviebuff.Client
logger log.Logger
}

func (s *seperationDegreeService) Find(source, dest models.Actor) {
queue := []models.QueueNode{}
visited := mutexMap.NewCallVisitedMutex()
sourceNode := models.QueueNode{
Person: source,
}
visited.Set(source.URL)
queue = append(queue, sourceNode)

degree := 1

for len(queue) > 0 {
ctx, cancelCtx := context.WithCancel(context.Background())

queueWg := &sync.WaitGroup{}
queueChan := make(chan models.QueueReader, 1)

nodeJobChan := make(chan models.QueueNode, len(queue))
nodeResChan := make(chan models.QueueNode)

queueWg.Add(1)
go func() {
defer queueWg.Done()
nxtLvlQueue := []models.QueueNode{}
for node := range nodeResChan {
if node.IsDest {
queueChan <- models.QueueReader{DestFound: true, ResultNode: node}
cancelCtx()
return
}
nxtLvlQueue = append(nxtLvlQueue, node)
}
queueChan <- models.QueueReader{Queue: nxtLvlQueue}
}()

queueWg.Add(1)
go func() {
defer queueWg.Done()
wg := &sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
s.traverse(ctx, nodeResChan, nodeJobChan, source, dest, visited)
}()
}
for _, node := range queue {
nodeJobChan <- node
}
close(nodeJobChan)

wg.Wait()

close(nodeResChan)
}()

queueWg.Wait()
close(queueChan)

res := <-queueChan

if res.DestFound {
s.printRelation(res.ResultNode, degree)
return
}

queue = res.Queue
degree += 1
}

s.printRelation(models.QueueNode{}, 0)
}

func (s *seperationDegreeService) traverse(ctx context.Context, nodeResChn chan<- models.QueueNode, nodeJobChn <-chan models.QueueNode, source models.Actor, dest models.Actor, visited *mutexMap.CallVisitedMutex) {
for curNode := range nodeJobChn {
if len(curNode.Person.URL) == 0 {
s.logger.Print("Skipping Invalid Node")
continue
}

resp, err := s.client.FetchActor(curNode.Person.URL)
if err != nil {
s.logger.Print("error while fetching actor details")
fmt.Println("here :", curNode.Person.URL)
continue
}

for _, movie := range resp.Movies {
if visited.Get(movie.URL) {
continue
}
visited.Set(movie.URL)

movieDetails, err := s.client.FetchMovie(movie.URL)
if err != nil || movieDetails == nil {
s.logger.Print("error while fetching movie details")
continue
}

movieDetails.Cast = append(movieDetails.Cast, movieDetails.Crew...)
for _, actor := range movieDetails.Cast {
if strings.Compare(source.URL, curNode.Person.URL) == 0 && strings.Compare(curNode.Person.URL, actor.URL) == 0 {
curNode.Person.Name = actor.Name
curNode.Person.Role = actor.Role
}

if visited.Get(actor.URL) {
continue
}

relationNode := models.RelationNode{
FrstPerson: curNode.Person,
SecondPerson: actor,
Movie: movie.Name,
}

node := models.QueueNode{
Person: actor,
}

if strings.Compare(dest.URL, actor.URL) == 0 {
node.IsDest = true
}

node.Path = append(node.Path, curNode.Path...)
node.Path = append(node.Path, relationNode)

visited.Set(actor.URL)

select {
case nodeResChn <- node:
case <-ctx.Done():
return
}

if node.IsDest {
return
}

}
}
}
}

func (s *seperationDegreeService) printRelation(node models.QueueNode, degree int) {
if degree == 0 {
fmt.Println("\nNo Relation found.")
return
}

fmt.Println("\nDegree of Seperation: ", degree)
for i, relation := range node.Path {
fmt.Printf("%d. Movie: %s\n", i+1, relation.Movie)
fmt.Printf("%s: %s\n", relation.FrstPerson.Role, relation.FrstPerson.Name)
fmt.Printf("%s: %s\n", relation.SecondPerson.Role, relation.SecondPerson.Name)
fmt.Println()
}
}
70 changes: 70 additions & 0 deletions utils/movieBuff/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package moviebuff

import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"

"github.com/sinhamanav030/challange2015/dtos"
)

type Client interface {
FetchActor(actor string) (*dtos.ActorAPIResponse, error)
FetchMovie(movie string) (*dtos.MovieAPIResponse, error)
}

type client struct {
httpClient *http.Client
logger log.Logger
}

func NewClient() Client {
client := &client{
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
logger: *log.Default(),
}
return client
}

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

func (c *client) FetchActor(actor string) (*dtos.ActorAPIResponse, error) {
res, err := c.makeHttpReq(actor)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data dtos.ActorAPIResponse
if res.StatusCode != 200 {
return &data, nil
}

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

func (c *client) FetchMovie(movie string) (*dtos.MovieAPIResponse, error) {
res, err := c.makeHttpReq(movie)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data dtos.MovieAPIResponse
if res.StatusCode != 200 {
return &data, nil
}
err = json.NewDecoder(res.Body).Decode(&data)
return &data, err
}
30 changes: 30 additions & 0 deletions utils/mutexMap/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package mutexmap

import "sync"

type CallVisitedMutex struct {
sync.Mutex
visited map[string]bool
}

func NewCallVisitedMutex() *CallVisitedMutex {
c := CallVisitedMutex{
visited: make(map[string]bool),
}

return &c
}

func (c *CallVisitedMutex) Set(name string) {
c.Lock()
c.visited[name] = true
c.Unlock()
}

func (c *CallVisitedMutex) Get(name string) bool {
c.Lock()
set := c.visited[name]
c.Unlock()

return set
}