diff --git a/README.md b/README.md index f1c342f65..1ba32774b 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/SOLUTION.md b/SOLUTION.md new file mode 100644 index 000000000..6abf59d0f --- /dev/null +++ b/SOLUTION.md @@ -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. \ No newline at end of file diff --git a/distributors.csv b/distributors.csv new file mode 100644 index 000000000..4b64f549d --- /dev/null +++ b/distributors.csv @@ -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 \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 000000000..766878d22 --- /dev/null +++ b/main.go @@ -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) + } +} \ No newline at end of file