// Moves an item in the list. That is, if we go from position 1 to 5, the items // that were previously 2, 3, 4 and 5 become 1, 2, 3 and 4. public static void MoveLocations(Location[] locations, int fromIndex, int toIndex) { if (locations == null) throw new ArgumentNullException("locations"); if (fromIndex < 0 || fromIndex >= locations.Length) throw new ArgumentOutOfRangeException("fromIndex"); if (toIndex < 0 || toIndex >= locations.Length) throw new ArgumentOutOfRangeException("toIndex"); var temp = locations[fromIndex]; if (fromIndex < toIndex) { for(int i=fromIndex+1; i<=toIndex; i++) locations[i-1] = locations[i]; } else { for(int i=fromIndex; i>toIndex; i--) locations[i] = locations[i-1]; } locations[toIndex] = temp; }
public static void MutateRandomLocations(Location[] locations) { if (locations == null) throw new ArgumentNullException("locations"); if (locations.Length < 2) throw new ArgumentException("The locations array must have at least two items.", "locations"); // I opted to give up to 10% of the chromosome size in number of mutations. // Maybe I should find a better number of make this configurable. int mutationCount = GetRandomValue(locations.Length/10) + 1; for(int mutationIndex=0; mutationIndex<mutationCount; mutationIndex++) { int index1 = GetRandomValue(locations.Length); int index2 = GetRandomValue(locations.Length-1); if (index2 >= index1) index2++; switch(GetRandomValue(3)) { case 0: Location.SwapLocations(locations, index1, index2); break; case 1: Location.MoveLocations(locations, index1, index2); break; case 2: Location.ReverseRange(locations, index1, index2); break; default: throw new InvalidOperationException(); } } }
public static double GetTotalDistance(Location startLocation, Location[] locations) { if (startLocation == null) throw new ArgumentNullException("startLocation"); if (locations == null) throw new ArgumentNullException("locations"); if (locations.Length == 0) throw new ArgumentException("The locations array must have at least one element.", "locations"); foreach(var location in locations) if (location == null) throw new ArgumentException("The locations array can't contain null values."); double result = startLocation.GetDistance(locations[0]); int countLess1 = locations.Length-1; for(int i=0; i<countLess1; i++) { var actual = locations[i]; var next = locations[i+1]; var distance = actual.GetDistance(next); result += distance; } result += locations[locations.Length-1].GetDistance(startLocation); return result; }
public static Location[] GetRandomDestinations(int count) { if (count < 2) throw new ArgumentOutOfRangeException("count"); Location[] result = new Location[count]; for(int i=0; i<count; i++) { int x = GetRandomValue(700) + 50; int y = GetRandomValue(500) + 50; result[i] = new Location(x, y); } return result; }
public TravellingSalesmanAlgorithm(Location startLocation, Location[] destinations, int populationCount) { if (startLocation == null) throw new ArgumentNullException("startLocation"); if (destinations == null) throw new ArgumentNullException("destinations"); if (populationCount < 2) throw new ArgumentOutOfRangeException("populationCount"); if (populationCount % 2 != 0) throw new ArgumentException("The populationCount parameter must be an even value.", "populationCount"); _startLocation = startLocation; destinations = (Location[])destinations.Clone(); foreach(var destination in destinations) if (destination == null) throw new ArgumentException("The destinations array can't contain null values.", "destinations"); // This commented method uses a search of the kind "look for the nearest non visited location". // This is rarely the shortest path, yet it is already a "somewhat good" path. destinations = _GetFakeShortest(destinations); _populationWithDistances = new KeyValuePair<Location[], double>[populationCount]; // Create initial population. for(int solutionIndex=0; solutionIndex<populationCount; solutionIndex++) { var newPossibleDestinations = (Location[])destinations.Clone(); // Try commenting the next 2 lines of code while keeping the _GetFakeShortest active. // If you avoid the algorithm from running and press reset, you will see that it always // start with a path that seems "good" but is not the best. //for(int randomIndex=0; randomIndex<newPossibleDestinations.Length; randomIndex++) //RandomProvider.FullyRandomizeLocations(newPossibleDestinations); var distance = Location.GetTotalDistance(startLocation, newPossibleDestinations); var pair = new KeyValuePair<Location[], double>(newPossibleDestinations, distance); _populationWithDistances[solutionIndex] = pair; } Array.Sort(_populationWithDistances, _sortDelegate); }
public static void FullyRandomizeLocations(Location[] locations) { if (locations == null) throw new ArgumentNullException("locations"); // This code does a full randomization of the destination locations without creating a new array. // If we have 3 items, for example, it will first determine which one of the 3 will be in the last // place, swapping items if needed. Then, it will chose which one of the first 2 items is put at the // second place. And, as everything works by swaps, the item in the first position is obviously the // only one that remains, that's why the i>0 is used instead of i>=0. int count = locations.Length; for(int i=count-1; i>0; i--) { int value = GetRandomValue(i+1); if (value != i) Location.SwapLocations(locations, i, value); } }
public static void ReverseRange(Location[] locations, int startIndex, int endIndex) { if (locations == null) throw new ArgumentNullException("locations"); if (startIndex < 0 || startIndex >= locations.Length) throw new ArgumentOutOfRangeException("startIndex"); if (endIndex < 0 || endIndex >= locations.Length) throw new ArgumentOutOfRangeException("endIndex"); if (endIndex < startIndex) { int temp = endIndex; endIndex = startIndex; startIndex = temp; } while(startIndex<endIndex) { Location temp = locations[endIndex]; locations[endIndex] = locations[startIndex]; locations[startIndex] = temp; startIndex++; endIndex--; } }
public double GetDistance(Location other) { int diffX = X - other.X; int diffY = Y - other.Y; return Math.Sqrt(diffX*diffX + diffY*diffY); }
public static void SwapLocations(Location[] locations, int index1, int index2) { if (locations == null) throw new ArgumentNullException("locations"); if (index1 < 0 || index1 >= locations.Length) throw new ArgumentOutOfRangeException("index1"); if (index2 < 0 || index2 >= locations.Length) throw new ArgumentOutOfRangeException("index2"); var location1 = locations[index1]; var location2 = locations[index2]; locations[index1] = location2; locations[index2] = location1; }
private IEnumerable<Location> _AddEndLocation(Location[] middleLocations) { foreach (var location in middleLocations) yield return location; yield return _startLocation; }
internal static void _CrossOver(Location[] locations1, Location[] locations2, bool mutateFailedCrossovers) { // I am not validating parameters because this method is internal. // If you want to make it public, you should validate the parameters. var availableLocations = new HashSet<Location>(locations1); int startPosition = GetRandomValue(locations1.Length); int crossOverCount = GetRandomValue(locations1.Length - startPosition); if (mutateFailedCrossovers) { bool useMutation = true; int pastEndPosition = startPosition + crossOverCount; for (int i=startPosition; i<pastEndPosition; i++) { if (locations1[i] != locations2[i]) { useMutation = false; break; } } // if the crossover is not going to give any change, we // force a mutation. if (useMutation) { MutateRandomLocations(locations1); return; } } Array.Copy(locations2, startPosition, locations1, startPosition, crossOverCount); List<int> toReplaceIndexes = null; // Now we will remove the used locations from the available locations. // If we can't remove one, this means it was used in duplicate. At this // moment we only register those indexes that have duplicate locations. int index = 0; foreach(var value in locations1) { if (!availableLocations.Remove(value)) { if (toReplaceIndexes == null) toReplaceIndexes = new List<int>(); toReplaceIndexes.Add(index); } index++; } // Finally we will replace duplicated items by those that are still available. // This is how we avoid having chromosomes that contain duplicated places to go. if (toReplaceIndexes != null) { // To do this, we enumerate two objects in parallel. // If we could use foreach(var indexToReplace, location from toReplaceIndexex, location1) it would be great. using(var enumeratorIndex = toReplaceIndexes.GetEnumerator()) { using(var enumeratorLocation = availableLocations.GetEnumerator()) { while(true) { if (!enumeratorIndex.MoveNext()) { Debug.Assert(!enumeratorLocation.MoveNext()); break; } if (!enumeratorLocation.MoveNext()) throw new InvalidOperationException("Something wrong happened."); locations1[enumeratorIndex.Current] = enumeratorLocation.Current; } } } } }
private Location[] _Reproduce(Location[] parent) { var result = (Location[])parent.Clone(); if (!MustDoCrossovers) { // When we are not using cross-overs, we always apply mutations. RandomProvider.MutateRandomLocations(result); return result; } // if you want, you can ignore the next three lines of code and the next // if, keeping the call to RandomProvider.MutateRandomLocations(result); always // invoked and without crossovers. Doing that you will not promove evolution through // "sexual reproduction", yet the good result will probably be found. int otherIndex = RandomProvider.GetRandomValue(_populationWithDistances.Length/2); var other = _populationWithDistances[otherIndex].Key; RandomProvider._CrossOver(result, other, MustMutateFailedCrossovers); if (!MustMutateFailedCrossovers) if (RandomProvider.GetRandomValue(10) == 0) RandomProvider.MutateRandomLocations(result); return result; }
private Location[] _GetFakeShortest(Location[] destinations) { Location[] result = new Location[destinations.Length]; var currentLocation = _startLocation; for(int fillingIndex=0; fillingIndex<destinations.Length; fillingIndex++) { int bestIndex = -1; double bestDistance = double.MaxValue; for(int evaluatingIndex=0; evaluatingIndex<destinations.Length; evaluatingIndex++) { var evaluatingItem = destinations[evaluatingIndex]; if (evaluatingItem == null) continue; double distance = currentLocation.GetDistance(evaluatingItem); if (distance < bestDistance) { bestDistance = distance; bestIndex = evaluatingIndex; } } result[fillingIndex] = destinations[bestIndex]; currentLocation = destinations[bestIndex]; destinations[bestIndex] = null; } return result; }