diff --git a/README.md b/README.md index 5ea87fa..0ab92b0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ +# solution +Solved using golang:: + +Data models in /models, + +Solution functions in /solve, + +Utility functions in /utils + +you can insert new filenames in main.go + + # Qube Cinemas Challenge 2019 Qube delivers the movie content to theatres all around the world. There are multiple delivery partners to help us deliver the content. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..310bad3 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module challenge2019 + +go 1.19 diff --git a/main.go b/main.go new file mode 100644 index 0000000..7e5189a --- /dev/null +++ b/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "challenge2019/models" + "challenge2019/solve" + "log" +) + +var filenames = models.FileDetails{ + Partners: "partners.csv", + Capacities: "capacities.csv", + Input: "input.csv", + Solution1: "output1.csv", + Solution2: "output2.csv", +} + +func main() { + //..To "catch" panic and exit gracefully + defer func() { + if err := recover(); err != nil { + log.Fatal("panic occurred:", err) + } + }() + log.Println("Solving....") + + if err := solve.Solution(&filenames); err != nil { + log.Fatal(err) + } + +} diff --git a/models/capacityDetails.go b/models/capacityDetails.go new file mode 100644 index 0000000..75266d0 --- /dev/null +++ b/models/capacityDetails.go @@ -0,0 +1,3 @@ +package models + +type CapacityMap map[string]int diff --git a/models/filedetails.go b/models/filedetails.go new file mode 100644 index 0000000..df937a7 --- /dev/null +++ b/models/filedetails.go @@ -0,0 +1,10 @@ +package models + +// Filedetails stores names of i/o csv files +type FileDetails struct { + Partners string + Capacities string + Input string + Solution1 string + Solution2 string +} diff --git a/models/inputDetails.go b/models/inputDetails.go new file mode 100644 index 0000000..7573e3f --- /dev/null +++ b/models/inputDetails.go @@ -0,0 +1,7 @@ +package models + +type InputDetails struct { + DeliveryID string + Size int + TheatreID string +} diff --git a/models/outputDetails.go b/models/outputDetails.go new file mode 100644 index 0000000..6a6db7d --- /dev/null +++ b/models/outputDetails.go @@ -0,0 +1,14 @@ +package models + +type OutputDetails struct { + DeliveryID string + Feasibility bool + PartnerID string + Cost int +} + +type TotalDataPerPartner struct { + Data int + //indivisible data unit map, maps dataunit to output 1 + DataUnitsMap map[int]int +} diff --git a/models/partnerDetails.go b/models/partnerDetails.go new file mode 100644 index 0000000..7f99bd0 --- /dev/null +++ b/models/partnerDetails.go @@ -0,0 +1,16 @@ +package models + +type PartnerDetails struct { + TheatreID string + SizeSlab Slab + MinimumCost int + CostPerGB int + PartnerID string +} + +type Slab struct { + Min int + Max int +} +type PartnerMap map[string][]PartnerDetails +type TheatreMap map[string]PartnerMap diff --git a/solve/solution.go b/solve/solution.go new file mode 100644 index 0000000..79024c0 --- /dev/null +++ b/solve/solution.go @@ -0,0 +1,34 @@ +package solve + +import ( + "challenge2019/models" + "challenge2019/utils" + "fmt" +) + +func Solution(f *models.FileDetails) error { + //Getting input partners and capacities from csv files + input, err := utils.GetInput(f.Input) + if err != nil { + return fmt.Errorf("solve/Solution(): error reading input: \n %w", err) + } + partners, err := utils.GetPartners(f.Partners) + if err != nil { + return fmt.Errorf("solve/Solution(): error reading partners: \n %w", err) + } + capacityMap, err := utils.GetCapacities(f.Capacities) + if err != nil { + return fmt.Errorf("solve/Solution(): error reading capacities: \n %w", err) + } + + output1Map, totalDataPerPartner := solution1(input, partners) + if err := utils.GenerateOut(f.Solution1, output1Map); err != nil { + return fmt.Errorf("solve/Solution(): error generating output files:%s \n %w", f.Solution1, err) + } + + output2Map := solution2(input, capacityMap, totalDataPerPartner, output1Map, partners) + if err := utils.GenerateOut(f.Solution2, output2Map); err != nil { + return fmt.Errorf("solve/Solution(): error generating output files:%s \n %w", f.Solution2, err) + } + return nil +} diff --git a/solve/solution1.go b/solve/solution1.go new file mode 100644 index 0000000..edfe327 --- /dev/null +++ b/solve/solution1.go @@ -0,0 +1,58 @@ +package solve + +import "challenge2019/models" + +func solution1(input []models.InputDetails, partners models.TheatreMap) ([]models.OutputDetails, map[string]models.TotalDataPerPartner) { + //Solving Problem Statements + output1Map := make([]models.OutputDetails, len(input)) + totalDataPerPartner := make(map[string]models.TotalDataPerPartner) + for i, in := range input { + output1 := models.OutputDetails{ + DeliveryID: in.DeliveryID, + Feasibility: false, + PartnerID: "", + Cost: 0, + } + for _, theatreData := range partners[in.TheatreID] { + for _, FesibilityData := range theatreData { + //solving Problem Statement 1 + if in.Size >= FesibilityData.SizeSlab.Min && in.Size <= FesibilityData.SizeSlab.Max { + cost := FesibilityData.CostPerGB * in.Size + if cost < FesibilityData.MinimumCost { + cost = FesibilityData.MinimumCost + } + if output1.Cost != 0 { + if output1.Cost > cost { + output1.Cost = cost + output1.PartnerID = FesibilityData.PartnerID + } + } else { + output1.Cost = cost + output1.PartnerID = FesibilityData.PartnerID + output1.Feasibility = true + } + } + + } + } + output1Map[i] = output1 + if output1.PartnerID == "" { + continue + } + totaldata, ok := totalDataPerPartner[output1.PartnerID] + if ok { + totaldata.Data += in.Size + totaldata.DataUnitsMap[in.Size] = i + totalDataPerPartner[output1.PartnerID] = totaldata + } else { + newData := models.TotalDataPerPartner{} + newData.Data = in.Size + dataMap := make(map[int]int) + dataMap[in.Size] = i + newData.DataUnitsMap = dataMap + totalDataPerPartner[output1.PartnerID] = newData + } + + } + return output1Map, totalDataPerPartner +} diff --git a/solve/solution2.go b/solve/solution2.go new file mode 100644 index 0000000..f2239fc --- /dev/null +++ b/solve/solution2.go @@ -0,0 +1,144 @@ +package solve + +import ( + "challenge2019/models" + "sort" +) + +func solution2(input []models.InputDetails, capacityMap models.CapacityMap, totalDataPerPartner map[string]models.TotalDataPerPartner, output1Map []models.OutputDetails, partners models.TheatreMap) []models.OutputDetails { + for partner, dm := range totalDataPerPartner { + if capacityMap[partner] < dm.Data { + //geting first i.e smallest suitable element from dataunit map + //sorting the keys + keys := make([]int, 0, len(dm.DataUnitsMap)) + for k := range dm.DataUnitsMap { + keys = append(keys, k) + } + sort.Ints(keys) + for k, v := range dm.DataUnitsMap { + newKey, ok := checkIfAddPartner(partners, input[v], output1Map[v], capacityMap, totalDataPerPartner) + + if ok { + delete(dm.DataUnitsMap, k) + dm.Data -= k + totalDataPerPartner[partner] = dm + update, updateok := totalDataPerPartner[newKey] + if updateok { + update.Data += k + update.DataUnitsMap[k] = v + totalDataPerPartner[newKey] = update + } else { + newupdate := models.TotalDataPerPartner{} + newupdate.Data = k + newDataUnitmap := make(map[int]int) + newDataUnitmap[k] = v + newupdate.DataUnitsMap = newDataUnitmap + totalDataPerPartner[newKey] = newupdate + } + if capacityMap[partner] < dm.Data { + continue + } else { + break + } + } else { + output1Map[v].Feasibility = false + output1Map[v].Cost = 0 + output1Map[v].PartnerID = "" + } + } + + } + } + //mutating output + outputMap := updateTrueOutput(input, output1Map, totalDataPerPartner, partners) + return outputMap +} + +func checkIfAddPartner(partners models.TheatreMap, in models.InputDetails, output models.OutputDetails, capacityMap models.CapacityMap, totalDataPerPartner map[string]models.TotalDataPerPartner) (string, bool) { + Tid := in.TheatreID + Pid := output.PartnerID + replacement := "" + status := false + cost := 0 + for i, patnerDetailsPerTheatre := range partners[Tid] { + if i != Pid { + for _, Tdata := range patnerDetailsPerTheatre { + if Tdata.SizeSlab.Max >= in.Size && Tdata.SizeSlab.Min <= in.Size { + + if cost != 0 { + + Tempcost := Tdata.CostPerGB * in.Size + if Tempcost < Tdata.MinimumCost { + Tempcost = Tdata.MinimumCost + } + + if Tempcost < cost { + cost = Tdata.CostPerGB * in.Size + if cost < Tdata.MinimumCost { + cost = Tdata.MinimumCost + if capacityMap[i] >= (totalDataPerPartner[i].Data + in.Size) { + replacement = i + status = true + } + + } + } + + } else { + + cost = Tdata.CostPerGB * in.Size + if cost < Tdata.MinimumCost { + cost = Tdata.MinimumCost + } + + if capacityMap[i] >= (totalDataPerPartner[i].Data + in.Size) { + replacement = i + status = true + } + } + + } + } + } + + } + return replacement, status +} + +func updateTrueOutput(input []models.InputDetails, + output1Map []models.OutputDetails, + totalDataPerPartner map[string]models.TotalDataPerPartner, + partners models.TheatreMap) []models.OutputDetails { + for i, out := range output1Map { + if out.Feasibility { + for pid, v := range totalDataPerPartner { + + var cost int + //finding the cost for updated value + for _, value := range partners[input[i].TheatreID][pid] { + for key, value2 := range v.DataUnitsMap { + if value2 == i { + if key >= value.SizeSlab.Min && key <= value.SizeSlab.Max { + cost = key * value.CostPerGB + if cost < value.MinimumCost { + cost = value.MinimumCost + } + out.PartnerID = pid + out.Cost = cost + out.Feasibility = true + output1Map[i] = out + //can be replaced with multiple flag and break + goto label + } + } + + } + + } + + } + label: + } + } + return output1Map +} diff --git a/utils/generateOut.go b/utils/generateOut.go new file mode 100644 index 0000000..9e85053 --- /dev/null +++ b/utils/generateOut.go @@ -0,0 +1,31 @@ +package utils + +import ( + "challenge2019/models" + "fmt" + "os" + "strconv" +) + +func GenerateOut(f string, outArray []models.OutputDetails) error { + file, err := os.Create(f) + if err != nil { + return err + } + + defer file.Close() + for _, out := range outArray { + line := out.DeliveryID + if out.Feasibility { + line = fmt.Sprintf("%s,%s,%s,%s\n", line, "true ", out.PartnerID, strconv.Itoa(out.Cost)) + } else { + line = fmt.Sprintf("%s,%s,%s,%s\n", line, "false", "\"\"", "\"\"") + } + _, err = file.WriteString(line) + + if err != nil { + return err + } + } + return nil +} diff --git a/utils/getCapacities.go b/utils/getCapacities.go new file mode 100644 index 0000000..760ef17 --- /dev/null +++ b/utils/getCapacities.go @@ -0,0 +1,43 @@ +package utils + +import ( + "bufio" + "challenge2019/models" + "fmt" + "os" + "strconv" + "strings" +) + +// getCapacities gets capacity details +func GetCapacities(filename string) (models.CapacityMap, error) { + capacityMap := make(models.CapacityMap) + file, err := os.Open(filename) + + if err != nil { + return models.CapacityMap{}, fmt.Errorf("utils/getCapacities() failed opening file:\n %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + scanner.Scan() + for scanner.Scan() { + + data := strings.Split(scanner.Text(), ",") + var capacity int + + if len(data) < 2 { + break + } + capacity, err = strconv.Atoi(strings.Trim(data[1], " ")) + if err != nil { + return models.CapacityMap{}, fmt.Errorf("utils/getCapacities() error reading size:\n %w", err) + } + + capacityMap[strings.Trim(data[0], " ")] = capacity + + } + + return capacityMap, nil +} diff --git a/utils/getInput.go b/utils/getInput.go new file mode 100644 index 0000000..7d78913 --- /dev/null +++ b/utils/getInput.go @@ -0,0 +1,45 @@ +package utils + +import ( + "bufio" + "challenge2019/models" + "fmt" + "os" + "strconv" + "strings" +) + +// getInput gets input details +func GetInput(filename string) ([]models.InputDetails, error) { + var inputList []models.InputDetails + + file, err := os.Open(filename) + + if err != nil { + return []models.InputDetails{}, fmt.Errorf("utils/getInput() failed opening file:\n %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + + for scanner.Scan() { + data := strings.Split(scanner.Text(), ",") + //stop at newline + if len(data) < 3 { + break + } + var size int + size, err = strconv.Atoi(strings.Trim(data[1], " ")) + if err != nil { + return []models.InputDetails{}, fmt.Errorf("utils/getInput() error reading size:\n %w", err) + } + inputList = append(inputList, models.InputDetails{ + DeliveryID: strings.Trim(data[0], " "), + Size: size, + TheatreID: strings.Trim(data[2], " "), + }) + } + + return inputList, nil +} diff --git a/utils/getPartners.go b/utils/getPartners.go new file mode 100644 index 0000000..2fd2ef6 --- /dev/null +++ b/utils/getPartners.go @@ -0,0 +1,81 @@ +package utils + +import ( + "bufio" + "challenge2019/models" + "fmt" + "os" + "strconv" + "strings" +) + +// getPartners gets partner details +func GetPartners(filename string) (models.TheatreMap, error) { + theatreMap := make(models.TheatreMap) + + file, err := os.Open(filename) + + if err != nil { + return theatreMap, fmt.Errorf("utils/getPartners() failed opening file:\n %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + scanner.Scan() + for scanner.Scan() { + data := strings.Split(scanner.Text(), ",") + //stop at newline + if len(data) < 5 { + break + } + var minimumCost int + minimumCost, err = strconv.Atoi(strings.Trim(data[2], " ")) + if err != nil { + return models.TheatreMap{}, fmt.Errorf("utils/getPartners() error reading minimum cost:\n %w", err) + } + + var costPerGB int + costPerGB, err = strconv.Atoi(strings.Trim(data[3], " ")) + if err != nil { + return models.TheatreMap{}, fmt.Errorf("utils/getPartners() error reading cost per gb:\n %w", err) + } + minmax := strings.Split(data[1], "-") + + var min int + min, err = strconv.Atoi(strings.Trim(minmax[0], " ")) + if err != nil { + return models.TheatreMap{}, fmt.Errorf("utils/getPartners() error reading minimum slab:\n %w", err) + } + var max int + max, err = strconv.Atoi(strings.Trim(minmax[1], " ")) + if err != nil { + return models.TheatreMap{}, fmt.Errorf("utils/getPartners() error reading maximum slab:\n %w", err) + } + partner := models.PartnerDetails{ + TheatreID: strings.Trim(data[0], " "), + SizeSlab: models.Slab{ + Min: min, + Max: max, + }, + MinimumCost: minimumCost, + CostPerGB: costPerGB, + PartnerID: strings.Trim(data[4], " "), + } + _, theatreOk := theatreMap[partner.TheatreID] + if theatreOk { + _, partnerOK := theatreMap[partner.TheatreID][partner.PartnerID] + if partnerOK { + theatreMap[partner.TheatreID][partner.PartnerID] = append(theatreMap[partner.TheatreID][partner.PartnerID], partner) + } else { + theatreMap[partner.TheatreID][partner.PartnerID] = []models.PartnerDetails{} + theatreMap[partner.TheatreID][partner.PartnerID] = append(theatreMap[partner.TheatreID][partner.PartnerID], partner) + } + + } else { + theatreMap[partner.TheatreID] = models.PartnerMap{} + theatreMap[partner.TheatreID][partner.PartnerID] = append([]models.PartnerDetails{}, partner) + } + } + return theatreMap, nil +}