Skip to content

🎯 Added solution for Real Image Challenge 2016 #192

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 4 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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ 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.

---

## Solution Guide
For a detailed explanation of the solution, including usage instructions and enhancements, see [SOLUTION.md](./SOLUTION.md).
99 changes: 99 additions & 0 deletions SOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Solution for Real Image Challenge 2016

## Approach

This solution implements a hierarchical permission system for movie distributors. In this system:

1. **Data Loading**
- We use `cities.csv` to validate region existence (cities, states, countries).
- We use `distributors.csv` to dynamically build distributor permissions and hierarchy.

2. **Normalization**
- All inputs (distributor names, regions, etc.) are normalized to uppercase and stripped of spaces.
- This ensures consistent matching, avoiding case and spacing issues.

3. **Permission Checking**
- A distributor's permissions are a subset of its parent's.
- `EXCLUDE` rules override `INCLUDE` rules.
- If a parent distributor excludes a region, all child distributors inherit that exclusion.

4. **Authorization Logic**
- We recursively check the distributor's parent permissions.
- If the parent denies permission, the child is automatically denied.
- If the region is explicitly excluded, deny permission.
- If the region is included, allow permission.
- Otherwise, deny.

5. **Detailed Reasoning**
- The solution provides reasons for why a distributor is authorized or not.

---

## Files

- **`main.go`**: Main Go program that:
1. Loads CSV data
2. Accepts user input (distributor name + region)
3. Prints out authorization results

- **`cities.csv`**: Contains the canonical list of city/state/country data.

- **`distributors.csv`**: Lists each distributor's `INCLUDE`/`EXCLUDE` permissions and (optionally) a parent distributor.

- **`SOLUTION.md`**: This file, explaining the approach in detail.

---

## Running the Program

1. **Compile and Run**
```bash
go run main.go
```

2. **When prompted**, enter:
- **Distributor Name** (e.g., `DISTRIBUTOR1`)
- **Region** in `CITY-STATE-COUNTRY` format (e.g., `CHICAGO-ILLINOIS-UNITED STATES`)


## Expected Output

The program will respond with either:
```
DISTRIBUTOR1 is authorized to distribute in CHICAGO-ILLINOIS-UNITED STATES. Reason: Explicitly included by distributor: DISTRIBUTOR1
```
or
```
DISTRIBUTOR1 is NOT authorized to distribute in CHICAGO-ILLINOIS-UNITED STATES. Reason: Explicitly excluded by distributor: DISTRIBUTOR1
```

---

## Example
Assuming the CSV data includes entries for `CHICAGO-ILLINOIS-UNITED STATES`:

1. **Input**:
```
Enter distributor name: DISTRIBUTOR1
Enter region (format CITY-STATE-COUNTRY): CHICAGO-ILLINOIS-UNITED STATES
```
2. **Output**:
```
DISTRIBUTOR1 is authorized to distribute in CHICAGO-ILLINOIS-UNITED STATES. Reason: Explicitly included by distributor: DISTRIBUTOR1
```

---

## Enhancements and Additional Features
- **Detailed Reason** for authorization or denial.
- **Hierarchy**: The solution recursively checks parent distributors.
- **Dynamic Data**: No hardcoded distributor or region data.
- **Normalization**: Minimizes errors due to case or spacing differences.
- **Extended** to handle sub-distributors, ensuring they can't exceed their parent's permissions.

---

## Future Improvements
- **Caching**: Use caching for repeated region checks.
- **Advanced CLI**: Provide interactive commands to list distributors, visualize hierarchies, etc.
- **Web API**: Expose the authorization logic over HTTP for an external service integration.
5 changes: 5 additions & 0 deletions distributors.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DistributorName,IncludeRegions(separated by |),ExcludeRegions(separated by |),ParentDistributor(optional)
DISTRIBUTOR1,INDIA|UNITED STATES,KARNATAKA-INDIA|CHENNAI-TAMIL NADU-INDIA,
DISTRIBUTOR2,INDIA,TAMIL NADU-INDIA,DISTRIBUTOR1
DISTRIBUTOR3,HUBLI-KARNATAKA-INDIA,,DISTRIBUTOR2
DISTRIBUTOR4,UNITED STATES,ALABAMA-UNITED STATES,DISTRIBUTOR1
178 changes: 178 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package main

import (
"bufio"
"encoding/csv"
"fmt"
"os"
"strings"
)

type Distributor struct {
Name string
Include []string
Exclude []string
Parent *Distributor
}

type City struct {
City string
State string
Country string
}

var cityMap map[string]City
var distributors map[string]*Distributor

func normalize(s string) string {
return strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(s), " ", ""))
}

func loadCities(path string) {
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()

reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
panic(err)
}

cityMap = make(map[string]City)
for _, rec := range records[1:] {
cityKey := normalize(rec[3] + "-" + rec[4] + "-" + rec[5])
cityMap[cityKey] = City{
City: normalize(rec[3]),
State: normalize(rec[4]),
Country: normalize(rec[5]),
}
}
}

func loadDistributors(path string) {
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()

reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
panic(err)
}

distributors = make(map[string]*Distributor)
for _, rec := range records[1:] {
name := normalize(rec[0])
includes := strings.Split(rec[1], "|")
excludes := strings.Split(rec[2], "|")
parentName := normalize(rec[3])
var parent *Distributor
if parentName != "" {
parent = distributors[parentName]
}
for i := range includes {
includes[i] = normalize(includes[i])
}
for i := range excludes {
excludes[i] = normalize(excludes[i])
}
distributors[name] = &Distributor{
Name: name,
Include: includes,
Exclude: excludes,
Parent: parent,
}
}
}

func isAuthorized(dist *Distributor, region string) (bool, string) {
region = normalize(region)

if !regionExists(region) {
return false, "Region does not exist"
}

if dist.Parent != nil {
parentAuthorized, _ := isAuthorized(dist.Parent, region)
if !parentAuthorized {
return false, "Denied by parent distributor: " + dist.Parent.Name
}
}

for _, ex := range dist.Exclude {
if matchesRegion(region, ex) {
return false, "Explicitly excluded by distributor: " + dist.Name
}
}

for _, inc := range dist.Include {
if matchesRegion(region, inc) {
return true, "Explicitly included by distributor: " + dist.Name
}
}

return false, "No matching inclusion found"
}


func matchesRegion(query, perm string) bool {
qParts := strings.Split(query, "-")
pParts := strings.Split(perm, "-")

if len(qParts) < len(pParts) {
return false
}

for i := 1; i <= len(pParts); i++ {
if qParts[len(qParts)-i] != pParts[len(pParts)-i] {
return false
}
}
return true
}

func regionExists(region string) bool {
_, cityOk := cityMap[region]
if cityOk {
return true
}
for city := range cityMap {
if strings.HasSuffix(city, region) {
return true
}
}
return false
}

func main() {
loadCities("cities.csv")
loadDistributors("distributors.csv")

scanner := bufio.NewScanner(os.Stdin)

fmt.Print("Enter distributor name: ")
scanner.Scan()
distName := normalize(scanner.Text())

dist, exists := distributors[distName]
if !exists {
fmt.Printf("Distributor %s not found\n", distName)
return
}

fmt.Print("Enter region (format CITY-STATE-COUNTRY): ")
scanner.Scan()
region := normalize(scanner.Text())

authorized, reason := isAuthorized(dist, region)
if authorized {
fmt.Printf("%s is authorized to distribute in %s. Reason: %s\n", dist.Name, region, reason)
} else {
fmt.Printf("%s is NOT authorized to distribute in %s. Reason: %s\n", dist.Name, region, reason)
}
}