/// <summary> /// Inserts the given customer at the best location if possible. /// </summary> /// <param name="tour">The tour to insert into.</param> /// <param name="weights">The directed weights.</param> /// <param name="turnPenalties">The turn pentalties.</param> /// <param name="customer">The customer to insert.</param> /// <param name="metric">The metric to use, time, distance or custom.</param> /// <param name="max">The maximum allowed cost of the insertion.</param> public static Weight InsertCheapestDirected(this Tour tour, Weight[][] weights, Weight[] turnPenalties, ProfileMetric metric, int customer, Weight max) { if (tour.Count == 1) { // there is only one customer, the first one in the route. if (tour.First == tour.Last) { // there is one customer but it's both the start and the end. var firstId = DirectedHelper.ExtractId(tour.First); var bestCost = new Weight() { Distance = float.MaxValue, Time = float.MaxValue, Value = float.MaxValue }; var bestDirected1Id = int.MaxValue; var bestDirected2Id = int.MaxValue; for (var turn1 = 0; turn1 < 4; turn1++) { var firstDirectedId = DirectedHelper.BuildDirectedId(firstId, turn1); var firstArrivalId = DirectedHelper.ExtractArrivalId(firstDirectedId); var firstDepartureId = DirectedHelper.ExtractDepartureId(firstDirectedId); for (var turn2 = 0; turn2 < 4; turn2++) { var customerDirectedId = DirectedHelper.BuildDirectedId(customer, turn2); var customerArrivalId = DirectedHelper.ExtractArrivalId(customerDirectedId); var customerDepartureId = DirectedHelper.ExtractDepartureId(customerDirectedId); var weight = turnPenalties[turn1]; weight += turnPenalties[turn2]; weight += weights[firstDepartureId][customerArrivalId]; weight += weights[customerDepartureId][firstArrivalId]; if (bestCost.GetForMetric(metric) > weight.GetForMetric(metric)) { bestDirected1Id = firstDirectedId; bestDirected2Id = customerDirectedId; bestCost = weight; } } } if (bestCost.GetForMetric(metric) <= max.GetForMetric(metric)) { tour.Replace(tour.First, bestDirected1Id); tour.InsertAfter(tour.First, bestDirected2Id); return bestCost; } } else { // there is one customer, the last one is not set. var firstId = DirectedHelper.ExtractId(tour.First); int departureOffset1, arrivalOffset2; var cost = DirectedHelper.CheapestInsert(weights, turnPenalties, firstId, customer, metric, out departureOffset1, out arrivalOffset2); if (cost.GetForMetric(metric) <= max.GetForMetric(metric)) { var newFirst = DirectedHelper.UpdateDepartureOffset( DirectedHelper.BuildDirectedId(firstId, 0), departureOffset1); var customerDirectedId = DirectedHelper.UpdateArrivalOffset( DirectedHelper.BuildDirectedId(customer, 0), arrivalOffset2); tour.Replace(tour.First, newFirst); tour.InsertAfter(tour.First, customerDirectedId); return cost; } } } else { // at least 2 customers already exist, insert a new one in between. var cost = Weight.MaxValue; var departureOffsetFrom = Constants.NOT_SET; var arrivalOffsetTo = Constants.NOT_SET; var turn = Constants.NOT_SET; var location = new Pair(int.MaxValue, int.MaxValue); foreach (var pair in tour.Pairs()) { int departureOffset1, arrivalOffset3, turn2; var fromIsFirst = tour.IsFirst(pair.From); var toIsLast = tour.IsLast(pair.To); var localCost = CheapestInsertionDirectedHelper.CalculateCheapestInsert(weights, turnPenalties, metric, pair.From, customer, pair.To, !fromIsFirst, !toIsLast, out departureOffset1, out arrivalOffset3, out turn2); if (localCost.GetForMetric(metric) < cost.GetForMetric(metric)) { cost = localCost; location = pair; departureOffsetFrom = departureOffset1; arrivalOffsetTo = arrivalOffset3; turn = turn2; } } if (cost.GetForMetric(metric) <= max.GetForMetric(metric)) { var directedId = DirectedHelper.BuildDirectedId(customer, turn); tour.InsertAfter(location.From, directedId); // update departure offset at from. var newFromId = DirectedHelper.UpdateDepartureOffset(location.From, departureOffsetFrom); if (location.From != newFromId) { tour.Replace(location.From, newFromId); } var newToId = DirectedHelper.UpdateArrivalOffset(location.To, arrivalOffsetTo); if (location.To != newToId) { tour.Replace(location.To, newToId); } return cost; } } return Weight.Zero; // insert failed, probably costs are too high (above given max parameter). }