From 4f8d20c17b928f9d0183fb91749afe86e5a496e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 2 May 2024 18:28:29 +0200 Subject: [PATCH] Reducing the cyclomatic complexity of the order fulfillment app --- .golangci.yml | 2 +- order-fulfillment-gosdk/main.go | 530 +++++++++++++++++++++----------- 2 files changed, 351 insertions(+), 181 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index af1f52e..a17923a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,7 +14,7 @@ linters-settings: modules: # Allow increased cyclomatic complexity for examples. gocyclo: - min-complexity: 45 + min-complexity: 12 # Set correct go version. gosimple: go: "1.21" diff --git a/order-fulfillment-gosdk/main.go b/order-fulfillment-gosdk/main.go index 9ceec9b..49160b2 100644 --- a/order-fulfillment-gosdk/main.go +++ b/order-fulfillment-gosdk/main.go @@ -121,35 +121,10 @@ func solver(_ context.Context, i input, opts options) (schema.Output, error) { assignments := computeAssignments(i) // create some helping data structures - distributionCenterCarrierCombinations := []carrier{} - for _, dc := range i.DistributionCenters { - for c := range i.CarrierCapacities[dc.DistributionCenterID] { - newCarrier := carrier{ - DistributionCenter: dc, - Carrier: c, - } - distributionCenterCarrierCombinations = append(distributionCenterCarrierCombinations, newCarrier) - } - } + distributionCenterCarrierCombinations := createDistributionCenterCarrierCombinations(i) - itemToAssignments := make(map[string][]assignment, len(i.Items)) - distributionCenterToCarrierToAssignments := make(map[string]map[string][]assignment, len(i.DistributionCenters)) - for _, as := range assignments { - itemID := as.Item.ItemID - _, ok := itemToAssignments[itemID] - if !ok { - itemToAssignments[itemID] = []assignment{} - } - itemToAssignments[itemID] = append(itemToAssignments[itemID], as) - if _, ok = distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID]; !ok { - distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID] = make(map[string][]assignment) - } - if _, ok = distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier]; !ok { - distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier] = []assignment{} - } - distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier] = - append(distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier], as) - } + itemToAssignments := createItemToAssignmentsMap(assignments, i) + distributionCenterToCarrierToAssignments := createDistributionCenterToCarrierAssignmentsMap(assignments, i) // x is a multimap representing a set of variables. It is initialized with a // create function and, in this case one set of elements. The elements can @@ -223,151 +198,140 @@ func solver(_ context.Context, i input, opts options) (schema.Output, error) { // We want to minimize the costs for fulfilling the order. m.Objective().SetMinimize() - // Fulfilment constraint -> ensure all items are assigned. - for _, item := range i.Items { - fulfillment := m.NewConstraint( - mip.Equal, - item.Quantity, - ) - for _, a := range itemToAssignments[item.ItemID] { - fulfillment.NewTerm(float64(a.Quantity), x.Get(a)) - } - } + // Fulfillment constraint -> ensure all items are assigned. + createFulfillmentConstraints(i, m, x, itemToAssignments) // Carrier capacity constraint -> consider the carrier capacities in the // solution; carrier capacity is considered in volume. - for dcID, dc := range distributionCenterToCarrierToAssignments { - for cID, list := range dc { - carrier := m.NewConstraint( - mip.LessThanOrEqual, - i.CarrierCapacities[dcID][cID], - ) - for _, as := range list { - carrier.NewTerm(as.Item.UnitVolume*as.Item.Quantity, x.Get(as)) - } - } - } + createCarrierCapacityConstraints(i, m, x, distributionCenterToCarrierToAssignments) - /* Inventory constraint -> Consider the inventory of each item at the - distribution centers. */ - for _, item := range i.Items { - for _, dc := range i.DistributionCenters { - inventory := m.NewConstraint( - mip.LessThanOrEqual, - float64(dc.Inventory[item.ItemID]), - ) - for _, a := range itemToAssignments[item.ItemID] { - if a.DistributionCenter.DistributionCenterID == dc.DistributionCenterID { - inventory.NewTerm(float64(a.Quantity), x.Get(a)) - } - } - } - } + // Inventory constraint -> Consider the inventory of each item at the + // distribution centers. + createInventoryConstraints(i, m, x, itemToAssignments) - /* carton computation -> look at every distribution center and accumulate - the volume of all the assigned items, use the carton volume from the input to - compute the number of cartons that are necessary. - volume computation -> compute the volume for each distribution center - - carrier combination. - weight computation -> compute the weight for each - distribution center - carrier combination. */ - for _, dc := range distributionCenterCarrierCombinations { - cartonConstr := m.NewConstraint( - mip.Equal, - 0.0, - ) - cartonConstr.NewTerm(-1, cartons.Get(dc)) + // carton computation -> look at every distribution center and accumulate + // the volume of all the assigned items, use the carton volume from the input to + // compute the number of cartons that are necessary. + createCartonComputationConstraints(i, m, x, cartons, assignments, distributionCenterCarrierCombinations) - volumeConstr := m.NewConstraint( - mip.Equal, - 0.0, - ) - volumeConstr.NewTerm(-1, volumes.Get(dc)) + // volume computation -> compute the volume for each distribution center - + // carrier combination. + createVolumeComputationConstraints(m, x, volumes, assignments, distributionCenterCarrierCombinations) - weightConstr := m.NewConstraint( - mip.Equal, - 0.0, - ) - weightConstr.NewTerm(-1, weights.Get(dc)) + // weight computation -> compute the weight for each distribution center - + // carrier combination. + createWeightComputationConstraints(m, x, weights, assignments, distributionCenterCarrierCombinations) - for _, a := range assignments { - if a.DistributionCenter.DistributionCenterID == dc.DistributionCenter.DistributionCenterID && - a.Carrier == dc.Carrier { - cartonConstr.NewTerm(a.Item.UnitVolume*float64(a.Quantity)*1/i.CartonVolume, x.Get(a)) - volumeConstr.NewTerm(a.Item.UnitVolume*float64(a.Quantity), x.Get(a)) - weightConstr.NewTerm(a.Item.UnitWeight*float64(a.Quantity), x.Get(a)) - } - } + /* + */ + + // dimensional weight computation -> by using the carrier specific + // dimensional weight factor, the dimensional weight of each shipnode carrier + // combination is determined. + createDimensionalWeightComputationConstraints(i, m, dimensionalWeights, volumes, distributionCenterCarrierCombinations) + + // billable weight computation -> computes the billable weight for each + // distribution center carrier combination, which is the max between the actual + // weight and the dimensional weight of that combination. + createBillableWeightsComputationConstraints(m, billableWeights, weights, + dimensionalWeights, distributionCenterCarrierCombinations) + + // Only one weight tier -> for each carrier, only a single weight tier can + // be selected. + createSingleWeightTierConstraints(i, m, weightTierVariables) + + // Weight tier upper limit -> used to determine actual weight tier of a + // distribution center carrier combination + createWeightTierUpperLimitConstraints(i, m, billableWeights, weightTierVariables, + distributionCenterCarrierCombinations, totalWeight) + + // weight tier lower limit -> used to determine actual weight tier of a + // distribution center carrier combination + createWeightTierLowerLimitConstraints(i, m, billableWeights, weightTierVariables, + distributionCenterCarrierCombinations) + + // delivery costs constraint -> compute the delivery costs based on the + // selected weight tier for each distribution center and carrier combination + createDeliveryCostsConstraints(i, m, deliveryCosts, weightTierVariables, distributionCenterCarrierCombinations) + + /* objective function = handling costs + delivery costs */ + /* handling costs: cost is based on number of cartons that need to be + handled at a distribution center */ + /* delivery costs: cost is based on number of cartons that need to be + transported */ + for _, combination := range distributionCenterCarrierCombinations { + m.Objective().NewTerm(1.0, deliveryCosts.Get(combination)) + m.Objective().NewTerm(combination.DistributionCenter.HandlingCost, cartons.Get(combination)) // handling costs } - /* dimensional weight computation -> by using the carrier specific - dimensional weight factor, the dimensional weight of each shipnode carrier - combination is determined. - billable weight computation -> computes the billable weight for each - distribution center carrier combination, which is the max between the actual - weight and the dimensional weight of that combination. */ - for _, combi := range distributionCenterCarrierCombinations { - dimWeightConstr := m.NewConstraint( - mip.Equal, - 0.0, - ) - dimWeightConstr.NewTerm(1.0, dimensionalWeights.Get(combi)) - dimWeightConstr.NewTerm(-i.CarrierDimensionalWeightFactors[combi.Carrier], volumes.Get(combi)) + // We create a solver using the 'highs' provider. + solver := highs.NewSolver(m) - /* Due to the fact that the billable weight will be used in the - objective function and we're trying to minimize cost, the billable weight will - be either set to the actual weight or to the dimensional weight. */ - billableWeightConstr1 := m.NewConstraint( - mip.GreaterThanOrEqual, - 0.0, - ) - billableWeightConstr1.NewTerm(1.0, billableWeights.Get(combi)) - billableWeightConstr1.NewTerm(-1.0, weights.Get(combi)) + // We create the solve options we will use. + solveOptions := mip.SolveOptions{} - billableWeightConstr2 := m.NewConstraint( - mip.GreaterThanOrEqual, - 0.0, - ) - billableWeightConstr2.NewTerm(1.0, billableWeights.Get(combi)) - billableWeightConstr2.NewTerm(-1.0, dimensionalWeights.Get(combi)) + // Limit the solve to a maximum duration. + solveOptions.Duration = opts.Solve.Duration + // Set the relative gap to 0% (highs' default is 5%). + solveOptions.MIP.Gap.Relative = 0.0 + + // Set verbose level to see a more detailed output. + solveOptions.Verbosity = mip.Off + + solution, err := solver.Solve(solveOptions) + if err != nil { + return schema.Output{}, err } - /* Only one weight tier -> for each carrier, only a single weight tier can - be selected. */ - for _, dc := range i.DistributionCenters { - for c := range i.CarrierDimensionalWeightFactors { - tiersConstraint := m.NewConstraint(mip.Equal, 1.0) - weightTiersLength := len(i.CarrierDeliveryCosts[dc.DistributionCenterID][c]["weight_tiers"]) - for k := 0; k < weightTiersLength+1; k++ { - tiersConstraint.NewTerm(1.0, weightTierVariables[dc.DistributionCenterID][c][k]) - } - } + output, err := format(solution, opts, x, assignments, + distributionCenterCarrierCombinations, cartons, volumes, + dimensionalWeights, weights, billableWeights, + weightTierVariables, deliveryCosts, + ) + if err != nil { + return schema.Output{}, err } - /* Weight tier upper limit -> used to determine actual weight tier of a - distribution center carrier combination*/ + return output, nil +} + +// delivery costs constraint -> compute the delivery costs based on the +// selected weight tier for each distribution center and carrier combination. +func createDeliveryCostsConstraints( + i input, + m mip.Model, + deliveryCosts model.MultiMap[mip.Float, carrier], + weightTierVariables map[string]map[string]map[int]mip.Bool, + distributionCenterCarrierCombinations []carrier) { for _, combi := range distributionCenterCarrierCombinations { - upperConstraint := m.NewConstraint(mip.LessThanOrEqual, 0.0) - upperConstraint.NewTerm(1, billableWeights.Get(combi)) + costsConstraint := m.NewConstraint(mip.Equal, 0.0) + costsConstraint.NewTerm(1, deliveryCosts.Get(combi)) weightTiersLength := len(i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_tiers"]) for k := 0; k < weightTiersLength+1; k++ { if k == weightTiersLength { - upperConstraint.NewTerm( - -totalWeight, + costsConstraint.NewTerm( + -i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_rates"][k-1], weightTierVariables[combi.DistributionCenter.DistributionCenterID][combi.Carrier][k], ) } else { - upperConstraint.NewTerm( - -i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_tiers"][k], + costsConstraint.NewTerm( + -i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_rates"][k], weightTierVariables[combi.DistributionCenter.DistributionCenterID][combi.Carrier][k], ) } } } +} - /* weight tier lower limit -> used to determine actual weight tier of a - distribution center carrier combination */ +// weight tier lower limit -> used to determine actual weight tier of a +// distribution center carrier combination. +func createWeightTierLowerLimitConstraints( + i input, + m mip.Model, + billableWeights model.MultiMap[mip.Float, carrier], + weightTierVariables map[string]map[string]map[int]mip.Bool, + distributionCenterCarrierCombinations []carrier) { for _, combi := range distributionCenterCarrierCombinations { lowerConstraint := m.NewConstraint(mip.LessThanOrEqual, 0.0) weightTiersLength := @@ -384,68 +348,274 @@ func solver(_ context.Context, i input, opts options) (schema.Output, error) { } lowerConstraint.NewTerm(-1, billableWeights.Get(combi)) } +} - /* delivery costs constraint -> compute the delivery costs based on the - selected weight tier for each distribution center and carrier combination */ +// Weight tier upper limit -> used to determine actual weight tier of a +// distribution center carrier combination. +func createWeightTierUpperLimitConstraints( + i input, + m mip.Model, + billableWeights model.MultiMap[mip.Float, carrier], + weightTierVariables map[string]map[string]map[int]mip.Bool, + distributionCenterCarrierCombinations []carrier, + totalWeight float64) { for _, combi := range distributionCenterCarrierCombinations { - costsConstraint := m.NewConstraint(mip.Equal, 0.0) - costsConstraint.NewTerm(1, deliveryCosts.Get(combi)) + upperConstraint := m.NewConstraint(mip.LessThanOrEqual, 0.0) + upperConstraint.NewTerm(1, billableWeights.Get(combi)) weightTiersLength := len(i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_tiers"]) for k := 0; k < weightTiersLength+1; k++ { if k == weightTiersLength { - costsConstraint.NewTerm( - -i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_rates"][k-1], + upperConstraint.NewTerm( + -totalWeight, weightTierVariables[combi.DistributionCenter.DistributionCenterID][combi.Carrier][k], ) } else { - costsConstraint.NewTerm( - -i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_rates"][k], + upperConstraint.NewTerm( + -i.CarrierDeliveryCosts[combi.DistributionCenter.DistributionCenterID][combi.Carrier]["weight_tiers"][k], weightTierVariables[combi.DistributionCenter.DistributionCenterID][combi.Carrier][k], ) } } } +} - /* objective function = handling costs + delivery costs */ - /* handling costs: cost is based on number of cartons that need to be - handled at a distribution center */ - /* delivery costs: cost is based on number of cartons that need to be - transported */ - for _, combination := range distributionCenterCarrierCombinations { - m.Objective().NewTerm(1.0, deliveryCosts.Get(combination)) - m.Objective().NewTerm(combination.DistributionCenter.HandlingCost, cartons.Get(combination)) // handling costs +// Only one weight tier -> for each carrier, only a single weight tier can +// be selected. +func createSingleWeightTierConstraints( + i input, + m mip.Model, + weightTierVariables map[string]map[string]map[int]mip.Bool) { + for _, dc := range i.DistributionCenters { + for c := range i.CarrierDimensionalWeightFactors { + tiersConstraint := m.NewConstraint(mip.Equal, 1.0) + weightTiersLength := len(i.CarrierDeliveryCosts[dc.DistributionCenterID][c]["weight_tiers"]) + for k := 0; k < weightTiersLength+1; k++ { + tiersConstraint.NewTerm(1.0, weightTierVariables[dc.DistributionCenterID][c][k]) + } + } } +} - // We create a solver using the 'highs' provider. - solver := highs.NewSolver(m) +// billable weight computation -> computes the billable weight for each +// distribution center carrier combination, which is the max between the actual +// weight and the dimensional weight of that combination. +func createBillableWeightsComputationConstraints( + m mip.Model, + billableWeights model.MultiMap[mip.Float, carrier], + weights model.MultiMap[mip.Float, carrier], + dimensionalWeights model.MultiMap[mip.Float, carrier], + distributionCenterCarrierCombinations []carrier) { + for _, combi := range distributionCenterCarrierCombinations { + /* Due to the fact that the billable weight will be used in the + objective function and we're trying to minimize cost, the billable weight will + be either set to the actual weight or to the dimensional weight. */ + billableWeightConstr1 := m.NewConstraint( + mip.GreaterThanOrEqual, + 0.0, + ) + billableWeightConstr1.NewTerm(1.0, billableWeights.Get(combi)) + billableWeightConstr1.NewTerm(-1.0, weights.Get(combi)) - // We create the solve options we will use. - solveOptions := mip.SolveOptions{} + billableWeightConstr2 := m.NewConstraint( + mip.GreaterThanOrEqual, + 0.0, + ) + billableWeightConstr2.NewTerm(1.0, billableWeights.Get(combi)) + billableWeightConstr2.NewTerm(-1.0, dimensionalWeights.Get(combi)) + } +} - // Limit the solve to a maximum duration. - solveOptions.Duration = opts.Solve.Duration - // Set the relative gap to 0% (highs' default is 5%). - solveOptions.MIP.Gap.Relative = 0.0 +// dimensional weight computation -> by using the carrier specific +// dimensional weight factor, the dimensional weight of each shipnode carrier +// combination is determined. +func createDimensionalWeightComputationConstraints( + i input, + m mip.Model, + dimensionalWeights model.MultiMap[mip.Float, carrier], + volumes model.MultiMap[mip.Float, carrier], + distributionCenterCarrierCombinations []carrier) { + for _, combi := range distributionCenterCarrierCombinations { + dimWeightConstr := m.NewConstraint( + mip.Equal, + 0.0, + ) + dimWeightConstr.NewTerm(1.0, dimensionalWeights.Get(combi)) + dimWeightConstr.NewTerm(-i.CarrierDimensionalWeightFactors[combi.Carrier], volumes.Get(combi)) + } +} - // Set verbose level to see a more detailed output. - solveOptions.Verbosity = mip.Off +func createWeightComputationConstraints( + m mip.Model, + x model.MultiMap[mip.Bool, assignment], + weights model.MultiMap[mip.Float, carrier], + assignments []assignment, + distributionCenterCarrierCombinations []carrier) { + for _, dc := range distributionCenterCarrierCombinations { + weightConstr := m.NewConstraint( + mip.Equal, + 0.0, + ) + weightConstr.NewTerm(-1, weights.Get(dc)) - solution, err := solver.Solve(solveOptions) - if err != nil { - return schema.Output{}, err + for _, a := range assignments { + if a.DistributionCenter.DistributionCenterID == dc.DistributionCenter.DistributionCenterID && + a.Carrier == dc.Carrier { + weightConstr.NewTerm(a.Item.UnitWeight*float64(a.Quantity), x.Get(a)) + } + } } +} - output, err := format(solution, opts, x, assignments, - distributionCenterCarrierCombinations, cartons, volumes, - dimensionalWeights, weights, billableWeights, - weightTierVariables, deliveryCosts, - ) - if err != nil { - return schema.Output{}, err +// volume computation -> compute the volume for each distribution center - +// carrier combination. +func createVolumeComputationConstraints( + m mip.Model, + x model.MultiMap[mip.Bool, assignment], + volumes model.MultiMap[mip.Float, carrier], + assignments []assignment, + distributionCenterCarrierCombinations []carrier) { + for _, dc := range distributionCenterCarrierCombinations { + volumeConstr := m.NewConstraint( + mip.Equal, + 0.0, + ) + volumeConstr.NewTerm(-1, volumes.Get(dc)) + + for _, a := range assignments { + if a.DistributionCenter.DistributionCenterID == dc.DistributionCenter.DistributionCenterID && + a.Carrier == dc.Carrier { + volumeConstr.NewTerm(a.Item.UnitVolume*float64(a.Quantity), x.Get(a)) + } + } } +} - return output, nil +// carton computation -> look at every distribution center and accumulate +// the volume of all the assigned items, use the carton volume from the input to +// compute the number of cartons that are necessary. +func createCartonComputationConstraints( + i input, + m mip.Model, + x model.MultiMap[mip.Bool, assignment], + cartons model.MultiMap[mip.Float, carrier], + assignments []assignment, + distributionCenterCarrierCombinations []carrier) { + for _, dc := range distributionCenterCarrierCombinations { + cartonConstr := m.NewConstraint( + mip.Equal, + 0.0, + ) + cartonConstr.NewTerm(-1, cartons.Get(dc)) + for _, a := range assignments { + if a.DistributionCenter.DistributionCenterID == dc.DistributionCenter.DistributionCenterID && + a.Carrier == dc.Carrier { + cartonConstr.NewTerm(a.Item.UnitVolume*float64(a.Quantity)*1/i.CartonVolume, x.Get(a)) + } + } + } +} + +// Inventory constraint -> Consider the inventory of each item at the +// distribution centers. +func createInventoryConstraints( + i input, + m mip.Model, + x model.MultiMap[mip.Bool, assignment], + itemToAssignments map[string][]assignment) { + for _, item := range i.Items { + for _, dc := range i.DistributionCenters { + inventory := m.NewConstraint( + mip.LessThanOrEqual, + float64(dc.Inventory[item.ItemID]), + ) + for _, a := range itemToAssignments[item.ItemID] { + if a.DistributionCenter.DistributionCenterID == dc.DistributionCenterID { + inventory.NewTerm(float64(a.Quantity), x.Get(a)) + } + } + } + } +} + +// Carrier capacity constraint -> consider the carrier capacities in the +// solution; carrier capacity is considered in volume. +func createCarrierCapacityConstraints( + i input, + m mip.Model, + x model.MultiMap[mip.Bool, assignment], + distributionCenterToCarrierToAssignments map[string]map[string][]assignment) { + for dcID, dc := range distributionCenterToCarrierToAssignments { + for cID, list := range dc { + carrier := m.NewConstraint( + mip.LessThanOrEqual, + i.CarrierCapacities[dcID][cID], + ) + for _, as := range list { + carrier.NewTerm(as.Item.UnitVolume*as.Item.Quantity, x.Get(as)) + } + } + } +} + +// Fulfillment constraint -> ensure all items are assigned. +func createFulfillmentConstraints( + i input, m mip.Model, + x model.MultiMap[mip.Bool, assignment], + itemToAssignments map[string][]assignment) { + for _, item := range i.Items { + fulfillment := m.NewConstraint( + mip.Equal, + item.Quantity, + ) + for _, a := range itemToAssignments[item.ItemID] { + fulfillment.NewTerm(float64(a.Quantity), x.Get(a)) + } + } +} + +func createDistributionCenterToCarrierAssignmentsMap( + assignments []assignment, + i input) map[string]map[string][]assignment { + distributionCenterToCarrierToAssignments := make(map[string]map[string][]assignment, len(i.DistributionCenters)) + for _, as := range assignments { + if _, ok := distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID]; !ok { + distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID] = make(map[string][]assignment) + } + if _, ok := distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier]; !ok { + distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier] = []assignment{} + } + distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier] = + append(distributionCenterToCarrierToAssignments[as.DistributionCenter.DistributionCenterID][as.Carrier], as) + } + return distributionCenterToCarrierToAssignments +} + +func createItemToAssignmentsMap(assignments []assignment, i input) map[string][]assignment { + itemToAssignments := make(map[string][]assignment, len(i.Items)) + for _, as := range assignments { + itemID := as.Item.ItemID + _, ok := itemToAssignments[itemID] + if !ok { + itemToAssignments[itemID] = []assignment{} + } + itemToAssignments[itemID] = append(itemToAssignments[itemID], as) + } + return itemToAssignments +} + +func createDistributionCenterCarrierCombinations(i input) []carrier { + distributionCenterCarrierCombinations := []carrier{} + for _, dc := range i.DistributionCenters { + for c := range i.CarrierCapacities[dc.DistributionCenterID] { + newCarrier := carrier{ + DistributionCenter: dc, + Carrier: c, + } + distributionCenterCarrierCombinations = append(distributionCenterCarrierCombinations, newCarrier) + } + } + return distributionCenterCarrierCombinations } type oflSolution struct {