Skip to content
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

Param - RealImage Challenge 2015 #37

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
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
Binary file added degrees.exe
Binary file not shown.
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/challenge2015

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

import (
"fmt"
"os"
"github.com/challenge2015/util"
"github.com/challenge2015/service"
)

func main() {

if len(os.Args) < 3 {
fmt.Println("Error: Invalid number of arguments. Please provide two actor names.")
fmt.Println("Usage: degrees <actor-name> <actor-name>")
return
}

firstActorName := os.Args[1]
secondActorName := os.Args[2]

if !util.IsValidURL(firstActorName) || !util.IsValidURL(secondActorName) {
fmt.Println("Error: Invalid URL format. URLs should be in the format 'actor-name' or 'director-name' separated by a hyphen.")
return
}

fmt.Println("First actor name: ", firstActorName)
fmt.Println("Second actor name: ", secondActorName)

service.DisplayResult(firstActorName, secondActorName)
}
194 changes: 194 additions & 0 deletions service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package service

import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

// "os"
"runtime"
"sync"
)

type queue struct {
URL string
degree int
path []string
}

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

type BufferMetaData struct {
URL string `json:"url"`
Type string `json:"type"`
Name string `json:"name"`
}

type Person struct {
BufferMetaData
Movies []BufferData `json:"movies"`
}

type Movie struct {
BufferMetaData
Cast []BufferData `json:"cast"`
}

type FetchResponse interface {
urlType() string
}

func (person Person) urlType() string {
return person.Type
}

func (movie Movie) urlType() string {
return movie.Type
}

const (
CategoryPerson = iota
CategoryMovie
)

func fetchAPIData(url string, URLCategiry int) (FetchResponse, error) {
// fmt.Println("Fetching data for URL: ", "https://data.moviebuff.com/" + url)
resp, err := http.Get("https://data.moviebuff.com/" + url)
if err != nil {
return nil, err
}
defer resp.Body.Close()

responseData, err := io.ReadAll(resp.Body)
// fmt.Println("Response data: ", string(responseData))
if err != nil {
return nil, err
}

if URLCategiry == CategoryPerson {
actor := Person{}
if err := json.Unmarshal(responseData, &actor); err != nil {
return nil, err
}
return actor, nil
}

movie := Movie{}
if err := json.Unmarshal(responseData, &movie); err != nil {
return nil, err
}
return movie, nil

}

func FindDegreesOfSeparation(firstActorName string, secondActorName string) (*queue, error) {

// used to keep track of URLs we've already visited, so we don't visit them again
visitedURLs := make(map[string]bool)
startQueue := queue{URL: firstActorName, degree: 0, path: []string{firstActorName}}
BFSQueue := make([]*queue, 0, 1)
BFSQueue = append(BFSQueue, &startQueue)
var mu sync.Mutex
var wg sync.WaitGroup

URLCategory := CategoryPerson

totalWorkers := runtime.NumCPU()
fmt.Println("Total workers: ", totalWorkers)
workerPool := make(chan struct{}, totalWorkers)

for len(BFSQueue) > 0 {
length := len(BFSQueue)
for i := 0; i < length; i++ {
URL := BFSQueue[i].URL
degree := BFSQueue[i].degree
path := BFSQueue[i].path

if URLCategory == CategoryPerson {degree = degree + 1}

if URL == secondActorName {
// fmt.Println("Found the path!")
BFSQueue[i].degree = degree
return BFSQueue[i], nil
}

workerPool <- struct{}{} // acquire a worker pool to control the number of concurrent workers
wg.Add(1)

go func(url string) {
defer func() {
<-workerPool // release the worker pool
wg.Done()
}()
if response, err := fetchAPIData(url, URLCategory); err == nil {
// fmt.Println("Response: ", response)
// os.Exit(1)
mu.Lock()
visitedURLs[URL] = true
mu.Unlock()
urlCategory := response.urlType()
// fmt.Println("URL category: ", urlCategory)
if urlCategory == "Person" {
resp := response.(Person)
for _, person := range resp.Movies {
path := make([]string, len(path))
copy(path, path)
mu.Lock()
if !visitedURLs[person.URL] {
URL = person.URL
path = append(path, person.URL)
q := queue{
URL: URL,
degree: degree,
path: path,
}
BFSQueue = append(BFSQueue, &q)
}
mu.Unlock()
}
} else if urlCategory == "Movie" {
resp := response.(Movie)
for _, movie := range resp.Cast {
path := make([]string, len(path))
copy(path, path)
mu.Lock()
if !visitedURLs[movie.URL] {
URL = movie.URL
path = append(path, movie.URL)
q := queue{
URL: URL,
degree: degree,
path: path,
}
BFSQueue = append(BFSQueue, &q)
}
mu.Unlock()
}
}
}
} (URL)
}
wg.Wait()
URLCategory = 1 - URLCategory
BFSQueue = BFSQueue[length:]
}
return nil, fmt.Errorf("No path found")
}

func DisplayResult(firstActorName string, secondActorName string) {
// fmt.Println("Displaying result")
result, err := FindDegreesOfSeparation(firstActorName, secondActorName)
if err != nil {
fmt.Println("Error:", err)
return
}

fmt.Println("Degrees of separation: ", result.degree)
fmt.Println("Path: ", strings.Join(result.path, " -> "))
}
9 changes: 9 additions & 0 deletions util/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package util

import "regexp"

var validURLPattern = regexp.MustCompile(`^[a-z]+(?:-[a-z]+)*$`)

func IsValidURL(url string) bool {
return validURLPattern.MatchString(url)
}