Skip to content

Oleksandr Petrykin P1 and P2 solution #127

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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
11 changes: 11 additions & 0 deletions cmd/problem1/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import "github.com/challenge2019/delivery"

func main() {
d := delivery.NewDeliveryService(&delivery.Repository{})
_, err := d.FindMinCostPartners("./data/input.csv")
if err != nil {
panic(err)
}
}
11 changes: 11 additions & 0 deletions cmd/problem2/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import "github.com/challenge2019/delivery"

func main() {
d := delivery.NewDeliveryService(&delivery.Repository{})
_, err := d.Assign("./data/input.csv")
if err != nil {
panic(err)
}
}
2 changes: 1 addition & 1 deletion capacities.csv → data/capacities.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"Partner ID","Capacity (in GB)"
partner_id, capacity
P1 ,350
P2 ,500
P3 ,1500
1 change: 1 addition & 0 deletions input.csv → data/input.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
delivery_id,amount,theatre_id
D1,150,T1
D2,325,T2
D3,510,T1
Expand Down
5 changes: 5 additions & 0 deletions data/output_problem1.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
delivery_id,is_possible,partner_id,cost
D2,true,P3,3900
D1,true,P3,3750
D3,true,P3,15300
D4,false,,0
5 changes: 5 additions & 0 deletions data/output_problem2.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
delivery_id,is_possible,partner_id,cost
D1,true,P1,2000
D2,true,P2,3500
D3,true,P3,15300
D4,false,,0
2 changes: 1 addition & 1 deletion partners.csv → data/partners.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Theatre,Size Slab (in GB),Minimum cost,Cost Per GB,Partner ID
theatre_id,slab,min_cost,cost_gb,partner_id
T1 ,0-100 ,1500 ,20 ,P1
T1 ,100-200 ,2000 ,13 ,P1
T1 ,200-300 ,2500 ,12 ,P1
Expand Down
52 changes: 52 additions & 0 deletions delivery/entity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package delivery

import (
"strconv"
"strings"
)

type Input struct {
DeliveryID TrimString `csv:"delivery_id"`
Amount int `csv:"amount"`
TheatreID TrimString `csv:"theatre_id"`
}

type Output struct {
DeliveryID TrimString `csv:"delivery_id"`
IsPossible bool `csv:"is_possible"`
PartnerID TrimString `csv:"partner_id"`
Cost int `csv:"cost"`
}

type Partner struct {
TheatreID TrimString `csv:"theatre_id"`
Slab Slab `csv:"slab"`
MinCost int `csv:"min_cost"`
CostPerGB int `csv:"cost_gb"`
PartnerID TrimString `csv:"partner_id"`
}

type Slab struct {
MinSlab int
MaxSlab int
}

type TrimString string

func (s *TrimString) UnmarshalCSV(csv string) (err error) {
*s = TrimString(strings.TrimSpace(csv))
return
}

func (s *Slab) UnmarshalCSV(csv string) (err error) {
ss := strings.Split(strings.TrimSpace(csv), "-")
s.MinSlab, err = strconv.Atoi(ss[0])
if err != nil {
return err
}
s.MaxSlab, err = strconv.Atoi(ss[1])
if err != nil {
return err
}
return nil
}
25 changes: 25 additions & 0 deletions delivery/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package delivery

import "github.com/challenge2019/file"

type Repository struct{}

func (r *Repository) FetchDeliveries(fileName string, deliveries *[]Input) chan error {
return file.ReadAsync(fileName, deliveries)
}

func (r *Repository) FetchPartners(fileName string, partners *[]Partner) chan error {
return file.ReadAsync(fileName, partners)
}

func (r *Repository) FetchCapacities(fileName string, capacities *map[TrimString]int) chan error {
return file.ReadToMapAsync(fileName, capacities)
}

func (r *Repository) SaveDeliveriesOutput(fileName string, out *[]Output) error {
err := file.Write(fileName, out)
if err != nil {
return err
}
return nil
}
213 changes: 213 additions & 0 deletions delivery/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package delivery

import (
"github.com/challenge2019/util"
)

const (
partnerFileName = "./data/partners.csv"
capacityFileName = "./data/capacities.csv"
)

type Service struct {
Repo *Repository
}

func NewDeliveryService(r *Repository) *Service {
return &Service{Repo: r}
}

func (s *Service) checkPartner(in <-chan Partner, d Input) chan Output {
out := make(chan Output)
min := Output{DeliveryID: d.DeliveryID, IsPossible: false}
go func() {
for p := range in {
if d.TheatreID == p.TheatreID && d.Amount >= p.Slab.MinSlab && d.Amount <= p.Slab.MaxSlab {
cost := d.Amount * p.CostPerGB
if cost < p.MinCost {
cost = p.MinCost
}
min = Output{DeliveryID: d.DeliveryID, IsPossible: true, Cost: cost, PartnerID: p.PartnerID}
}
}
out <- min
close(out)
}()

return out
}

func (s *Service) FindMinCostPartners(input string) ([]Output, error) {
var (
deliveries []Input
partners []Partner
)

errors := util.Merge(
s.Repo.FetchDeliveries(input, &deliveries),
s.Repo.FetchPartners(partnerFileName, &partners),
)

for err := range errors {
if err != nil {
return nil, err
}
}

size := len(deliveries)
out := make([]chan Output, 0, size)
result := make([]Output, 0, size)
deliveryChannels := make([]chan Partner, size, size)

for i := range deliveries {
c := make(chan Partner)
deliveryChannels[i] = c
out = append(out, s.checkPartner(c, deliveries[i]))
}

util.FanOut(partners, deliveryChannels...)

for o := range util.Merge(out...) {
result = append(result, o)
}

err := s.Repo.SaveDeliveriesOutput("./data/output_problem1.csv", &result)
if err != nil {
return nil, err
}

return result, nil
}

func toMap(pp []Partner) map[TrimString][]Partner {
m := make(map[TrimString][]Partner)

for _, p := range pp {
m[p.TheatreID] = append(m[p.TheatreID], p)
}

return m
}

type container struct {
value int
okCount int
deliveries []Output
capacityLeft map[TrimString]int
}

func (c *container) checkOptimal(opt container) bool {
return c.okCount > opt.okCount || (c.okCount == opt.okCount && c.value <= opt.value && len(c.deliveries) >= len(opt.deliveries))
}

func (c *container) copyAndAdd(d Input, p Partner) container {
cl := copyMap(c.capacityLeft)
out := Output{DeliveryID: d.DeliveryID, IsPossible: false}
ok := c.okCount
value := c.value

if cl[p.PartnerID] >= d.Amount && d.Amount >= p.Slab.MinSlab && d.Amount <= p.Slab.MaxSlab {
ok++
cost := d.Amount * p.CostPerGB
if cost < p.MinCost {
cost = p.MinCost
}
value += cost
cl[p.PartnerID] = cl[p.PartnerID] - d.Amount
out = Output{DeliveryID: d.DeliveryID, IsPossible: true, Cost: cost, PartnerID: p.PartnerID}
}

dd := make([]Output, len(c.deliveries), len(c.deliveries)+1)
copy(dd, c.deliveries)

return container{
value: value,
capacityLeft: cl,
okCount: ok,
deliveries: append(dd, out),
}
}

func copyMap(m map[TrimString]int) map[TrimString]int {
cm := make(map[TrimString]int)
for k, v := range m {
cm[k] = v
}
return cm
}

func newContainer(d Input, p Partner, cc map[TrimString]int) container {
var value int
cl := copyMap(cc)
out := Output{DeliveryID: d.DeliveryID, IsPossible: false}
ok := 0

if cl[p.PartnerID] >= d.Amount && d.Amount >= p.Slab.MinSlab && d.Amount <= p.Slab.MaxSlab {
ok++
value = d.Amount * p.CostPerGB
if value < p.MinCost {
value = p.MinCost
}
cl[p.PartnerID] = cl[p.PartnerID] - d.Amount
out = Output{DeliveryID: d.DeliveryID, IsPossible: true, Cost: value, PartnerID: p.PartnerID}
}

return container{
value: value,
capacityLeft: cl,
okCount: ok,
deliveries: []Output{out},
}
}

func (s *Service) Assign(input string) ([]Output, error) {
var (
deliveries []Input
partners []Partner
capacities map[TrimString]int
)

errors := util.Merge(
s.Repo.FetchDeliveries(input, &deliveries),
s.Repo.FetchPartners(partnerFileName, &partners),
s.Repo.FetchCapacities(capacityFileName, &capacities),
)

for err := range errors {
if err != nil {
return nil, err
}
}

partnersMap := toMap(partners)

var (
containerSet []container
opt container
)

for _, d := range deliveries {
pp := partnersMap[d.TheatreID]
var cc []container
for _, p := range pp {
cc = append(cc, newContainer(d, p, capacities))

for i := 0; i < len(containerSet); i++ {
newC := containerSet[i].copyAndAdd(d, p)
cc = append(cc, newC)

if newC.checkOptimal(opt) {
opt = newC
}
}
}
containerSet = append(containerSet, cc...)
}

err := s.Repo.SaveDeliveriesOutput("./data/output_problem2.csv", &opt.deliveries)
if err != nil {
return nil, err
}

return opt.deliveries, nil
}
Loading