diff --git a/Evolve.podspec b/Evolve.podspec index e25499d..7d8ed68 100644 --- a/Evolve.podspec +++ b/Evolve.podspec @@ -1,14 +1,14 @@ Pod::Spec.new do |s| s.name = "Evolve" - s.version = "0.3" + s.version = "0.4" s.summary = "An Objective-C evolution simulation engine." s.homepage = "https://github.com/mamaral/Evolve" s.license = "MIT" s.author = { "Mike Amaral" => "mike.amaral36@gmail.com" } s.social_media_url = "http://twitter.com/MikeAmaral" s.platform = :ios - s.source = { :git => "https://github.com/mamaral/Evolve.git", :tag => "v0.3" } + s.source = { :git => "https://github.com/mamaral/Evolve.git", :tag => "v0.4" } s.source_files = "Source/EvolutionManager.{h,m}", "Source/Population.{h,m}", "Source/Organism.{h,m}", "Source/Genome.{h,m}", "Source/Random.{h,m}" s.requires_arc = true diff --git a/EvolveTests/EvolutionManagerTests.m b/EvolveTests/EvolutionManagerTests.m index aaaaed3..23ad13e 100644 --- a/EvolveTests/EvolutionManagerTests.m +++ b/EvolveTests/EvolutionManagerTests.m @@ -64,7 +64,7 @@ - (void)tearDown { - (void)testInitWithPopulation { for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { NSInteger randomSize = [Random randomIntegerFromMin:2 toMax:100]; - NSInteger randomLength = [Random randomIntegerFromMin:1 toMax:10]; + NSInteger randomLength = [Random randomIntegerFromMin:4 toMax:10]; NSString *domain = @"abcd"; Population *population = [[Population alloc] initRandomPopulationWithSize:randomSize geneSequenceLength:randomLength genomeDomain:domain]; EvolutionManager *manager = [[EvolutionManager alloc] initWithPopulation:population]; diff --git a/EvolveTests/GenomeTests.m b/EvolveTests/GenomeTests.m index f7f9f39..1326355 100644 --- a/EvolveTests/GenomeTests.m +++ b/EvolveTests/GenomeTests.m @@ -23,7 +23,7 @@ - (void)testInitWithGeneSequenceAndDomain { NSString *entireDomain = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"; for (NSInteger i = 0; i < kGenomeTestIterations; i++) { - NSInteger randomSequenceLength = [Random randomIntegerFromMin:1 toMax:50]; + NSInteger randomSequenceLength = [Random randomIntegerFromMin:4 toMax:50]; NSInteger randomDomainLength = [Random randomIntegerFromMin:1 toMax:25]; NSString *randomDomain = [Random randomGeneSequenceWithLength:randomDomainLength domain:entireDomain]; NSString *randomGeneSequence = [Random randomGeneSequenceWithLength:randomSequenceLength domain:randomDomain]; @@ -40,7 +40,7 @@ - (void)testInitRandomWithLengthAndDomain { NSString *entireDomain = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"; for (NSInteger i = 0; i < kGenomeTestIterations; i++) { - NSInteger randomSequenceLength = [Random randomIntegerFromMin:1 toMax:50]; + NSInteger randomSequenceLength = [Random randomIntegerFromMin:4 toMax:50]; NSInteger randomDomainLength = [Random randomIntegerFromMin:1 toMax:25]; NSString *randomDomain = [Random randomGeneSequenceWithLength:randomDomainLength domain:entireDomain]; @@ -108,11 +108,10 @@ - (void)testInitRandomWithInvalidDomain { XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); } -- (void)testHandleMutation { - // With a 100% mutation rate (the only thing we can really effectively test) - // a gene should never be the same after mutation. +- (void)testHandleMutationGuarenteed { + // With a 100% mutation rate a gene should never be the same after mutation. NSString *testDomain = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - Genome *testGenome = [[Genome alloc] initRandomGenomeWithLength:1 domain:testDomain]; + Genome *testGenome = [[Genome alloc] initRandomGenomeWithLength:4 domain:testDomain]; for (NSInteger i = 0; i < kGenomeTestIterations; i++) { NSString *originalGeneSequence = testGenome.sequence; @@ -125,4 +124,20 @@ - (void)testHandleMutation { } } +- (void)testHandleMutationShouldNotHappen { + // With a 0% mutation rate a gene should be the same after mutation. + NSString *testDomain = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + Genome *testGenome = [[Genome alloc] initRandomGenomeWithLength:4 domain:testDomain]; + + for (NSInteger i = 0; i < kGenomeTestIterations; i++) { + NSString *originalGeneSequence = testGenome.sequence; + + [testGenome handleMutationWithRate:0.0]; + + NSString *newGeneSequence = testGenome.sequence; + + XCTAssertTrue([newGeneSequence isEqualToString:originalGeneSequence]); + } +} + @end diff --git a/EvolveTests/OrganismTests.m b/EvolveTests/OrganismTests.m index 65afef8..13a74c2 100644 --- a/EvolveTests/OrganismTests.m +++ b/EvolveTests/OrganismTests.m @@ -13,6 +13,12 @@ static NSInteger const kOrganismTestIterations = 10000; +@interface Organism (Testing) + ++ (NSRange)generateRangeForTwoPointCrossoverFromLength:(NSUInteger)geneSequenceLength; + +@end + @interface OrganismTests : XCTestCase @end @@ -36,7 +42,7 @@ - (void)testInitRandom { NSString *testDomain = @"abcdefg 123456"; for (NSInteger i = 0; i < kOrganismTestIterations; i++) { - NSInteger randomLength = [Random randomIntegerFromMin:1 toMax:25]; + NSInteger randomLength = [Random randomIntegerFromMin:4 toMax:25]; Organism *organism = [[Organism alloc] initRandomWithGeneSequenceLength:randomLength domain:testDomain]; XCTAssertNotNil(organism); @@ -97,7 +103,7 @@ - (void)testGenerateOffspring { for (NSInteger i = 0; i < kOrganismTestIterations; i++) { Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:testGeneSequenceLength domain:testDomain]; Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:testGeneSequenceLength domain:testDomain]; - Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:0.0]; + Organism *offspring = [parent1 mateWithOrganism:parent2 crossoverMethod:CrossoverMethodOnePoint mutationRate:0.0]; XCTAssertNotNil(offspring); XCTAssertNotNil(offspring.genome); @@ -122,17 +128,9 @@ - (void)testGenerateOffspring { } } -- (void)testGenerateOffspringWithInvalidParent1 { +- (void)testGenerateOffspringWithInvalidMate { void (^expressionBlock)() = ^{ - __unused Organism *offspring = [Organism offspringFromParent1:nil parent2:[Organism new] mutationRate:0.0]; - }; - - XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); -} - -- (void)testGenerateOffspringWithInvalidParent2 { - void (^expressionBlock)() = ^{ - __unused Organism *offspring = [Organism offspringFromParent1:[Organism new] parent2:nil mutationRate:0.0]; + __unused Organism *offspring = [[Organism new] mateWithOrganism:nil crossoverMethod:CrossoverMethodOnePoint mutationRate:0.0]; }; XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); @@ -144,7 +142,7 @@ - (void)testGenerateOffspringWithInvalidMutationRate { NSInteger testLength = 4; Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:testDomain]; Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:testDomain]; - __unused Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:1.5]; + __unused Organism *offspring = [parent1 mateWithOrganism:parent2 crossoverMethod:CrossoverMethodOnePoint mutationRate:1.5]; }; XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); @@ -155,7 +153,7 @@ - (void)testGenerateOffspringWithMismatchingParentLength { NSString *testDomain = @"abcd"; Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:3 domain:testDomain]; Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:4 domain:testDomain]; - __unused Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:0.0]; + __unused Organism *offspring = [parent1 mateWithOrganism:parent2 crossoverMethod:CrossoverMethodOnePoint mutationRate:0.0]; }; XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); @@ -166,7 +164,37 @@ - (void)testGenerateOffspringWithMismatchingParentDomain { NSInteger testLength = 4; Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:@"abc"]; Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:@"123"]; - __unused Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:0.0]; + __unused Organism *offspring = [parent1 mateWithOrganism:parent2 crossoverMethod:CrossoverMethodOnePoint mutationRate:0.0]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testGenerateRangeForTwoPointCrossoverWithMinimumLength { + for (NSInteger i = 0; i < kOrganismTestIterations; i++) { + NSRange range = [Organism generateRangeForTwoPointCrossoverFromLength:kMinimumGeneSequenceLength]; + + XCTAssertEqual(range.location, 1); + XCTAssertEqual(range.length, 1); + } +} + +- (void)testGenerateRangeForTwoPointCrossoverWithRandomLength { + for (NSInteger i = 0; i < kOrganismTestIterations; i++) { + NSUInteger randomLength = [Random randomIntegerFromMin:4 toMax:100]; + NSRange range = [Organism generateRangeForTwoPointCrossoverFromLength:randomLength]; + NSUInteger lastIndexFromRange = range.location + range.length + 1; + + XCTAssertGreaterThanOrEqual(range.location, 1); + XCTAssertGreaterThanOrEqual(range.length, 1); + XCTAssertLessThan(lastIndexFromRange, randomLength); + } +} + +- (void)testGenerateRangeForTwoPointCrossoverWithInvalidLength { + void (^expressionBlock)() = ^{ + NSInteger randomInvalidLength = [Random randomIntegerFromMin:0 toMax:kMinimumGeneSequenceLength - 1]; + [Organism generateRangeForTwoPointCrossoverFromLength:randomInvalidLength]; }; XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); diff --git a/EvolveTests/PopulationTests.m b/EvolveTests/PopulationTests.m index c7c0e4b..46cd742 100644 --- a/EvolveTests/PopulationTests.m +++ b/EvolveTests/PopulationTests.m @@ -25,7 +25,7 @@ - (void)testInitWithOrganisms { NSMutableArray *organisms = [NSMutableArray arrayWithCapacity:numberOfOrganisms]; for (NSInteger i = 0; i < numberOfOrganisms; i++) { - [organisms addObject:[[Organism alloc] initRandomWithGeneSequenceLength:1 domain:@"abcd"]]; + [organisms addObject:[[Organism alloc] initRandomWithGeneSequenceLength:4 domain:@"abcd"]]; } Population *population = [[Population alloc] initWithOrganisms:organisms]; @@ -40,7 +40,7 @@ - (void)testInitWithOrganisms { - (void)testInitRandom { for (NSInteger i = 0; i < kPopulationTestIterations; i++) { NSInteger randomSize = [Random randomIntegerFromMin:2 toMax:20]; - NSInteger randomGeneSequenceLength = [Random randomIntegerFromMin:2 toMax:30]; + NSInteger randomGeneSequenceLength = [Random randomIntegerFromMin:4 toMax:30]; NSString *domain = @"abcdefg1234567"; Population *population = [[Population alloc] initRandomPopulationWithSize:randomSize geneSequenceLength:randomGeneSequenceLength genomeDomain:domain]; diff --git a/README.md b/README.md index f5c15d2..6a58b9d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Evolve is a customizable [***evolutionary algorithm***](http://en.wikipedia.org/ - [x] **Customizable genomes**. You may want your genome to represent a color value, so you choose to define the domain of the genome to be all hex characters `0123456789ABCDEF` and the length to be `6`, so you can easily translate a gene sequence like `FF0000` to the color red. You might want your domain to be a binary string, `01` with the length `6`, like `110100`, where the first two digits represent the speed of your organism, the next two represent the strength of your organism, and the last two represent how well they're camouflaged. You define what the genome is and how it manifests itself in the wild, the possibilities are endless. Be creative! - [x] **Constant population size** from generation to generation. - [x] **Tournament style selection.** -- [x] **Single-point crossover.** +- [x] **One-point or Two-point crossover.** - [x] **Uniform mutation.** - [x] ***Tweakable*** **simulation parameters.** Play around with some of the settable variables to see how they effect the simulation. *Tweak the dials*, make predictions and test your hypotheses in real-time! - [x] **Flexible simulation mechanics.** The engine doesn't continue with the next generation whenever it wants, it waits for you to tell it to continue. Because of this, you can run the simulation in a for-loop running through thousands of generations almost instantly, or you can take each organism in your population and put them in a real-time interactive simulation using something like SpriteKit, making each generation take minutes, or even hours, if that's what interests you. @@ -123,9 +123,11 @@ Separate from the parent selection process, the population is sorted based on ea # *Crossover and Mutation* -This algorithm simulates sexual reproduction, as opposed to asexual reproduction, which means that offspring are always generated from two distinct parents, where the resulting child has the same genetic characteristics as both parents, meaning the `length` and `domain` of the genome remain the same. When the two parents are selected from the pool, their genes are combined using the [***One-point crossover***](http://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)#One-point_crossover) technique. A random index is chosen between `0` and `length - 1` of the gene sequence, both genomes are copied, "split" at this index, and recombined to form the child's genetic code. +This algorithm simulates sexual reproduction, as opposed to asexual reproduction, which means that offspring are always generated from two distinct parents, where the resulting child has the same genetic characteristics as both parents, meaning the `length` and `domain` of the genome remain the same. When the two parents are selected from the pool, their genes are combined using either the [***One-point crossover***](http://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)#One-point_crossover) or [***Two-point crossover***](http://en.wikipedia.org/wiki/Crossover_%28genetic_algorithm%29#Two-point_crossover) techniques. Single-point crossover results in a random index being chosen between `1` and `length - 2` of the gene sequence, both genomes are copied, "split" at this index, and recombined to form the child's genetic code. The range of this index ensures that at least one gene from each parent makes it into the offspring's initial genome. Two-point crossover results in a random range being selected with the same restrictions as above, and that range is used to generate a "slice" from the genome where one parent contributes their genes from before and after the range, and the other contributes their genes from the range itself. ->***Example:*** If the domain is defined as `@"AB"`, with the first parent's gene sequence as `AAAAA` and the second's as `BBBBB`, if the randomly chosen index was `2`, the first parent's contribution would be `AA`, and the second parent's contribution would be `BBB`, resulting in the child's genetic sequence of `AABBB`. +>***One-point Example:*** Consider a domain defined as `@"AB"`, with the first parent's gene sequence as `AAAAA` and the second's as `BBBBB`. If the randomly chosen index was `2`, the first parent's contribution would be `AA`, and the second parent's contribution would be `BBB`, resulting in the child's genetic sequence being `AABBB`. + +>***Two-point example*** Consider a domain defined as `@"ABCD1234"`, with the first parent's gene sequence as `AABBCCDD` and the second's as `11223344`. If the randomly generate range was `(2,3)`, that range would be used to select `223` from the second parent's genes, as well as `AA` and `CDD` from the first parent's genes, resulting in the child's genetic sequence being `AA223CDD`. After the crossover process is complete, every gene in the offspring's genetic sequence has a chance to [***mutate***](http://en.wikipedia.org/wiki/Mutation_(genetic_algorithm)). This algorithm utilizes a uniform mutation strategy, meaning that if by chance the gene does mutate, it will mutate into one of the characters defined by the domain of their genome, ***not including the current character***, with an equal chance to mutate into each. diff --git a/Source/EvolutionManager.h b/Source/EvolutionManager.h index a2ee337..fdc57ed 100644 --- a/Source/EvolutionManager.h +++ b/Source/EvolutionManager.h @@ -27,6 +27,7 @@ @property (nonatomic) NSUInteger currentGeneration; +@property (nonatomic) enum CrossoverMethod crossoverMethod; @property (nonatomic) NSUInteger tournamentSize; @property (nonatomic) CGFloat elitismPercentage; @property (nonatomic) CGFloat mutationRate; diff --git a/Source/EvolutionManager.m b/Source/EvolutionManager.m index a198dff..9c19f78 100644 --- a/Source/EvolutionManager.m +++ b/Source/EvolutionManager.m @@ -8,6 +8,7 @@ #import "EvolutionManager.h" +static enum CrossoverMethod const kDefaultCrossoverMethod = CrossoverMethodOnePoint; static CGFloat const kDefaultMutationRate = 0.05; static CGFloat const kDefaultElitismPercentage = 0.10; static NSInteger const kDefaultTournamentSize = 2; @@ -30,6 +31,7 @@ - (instancetype)initWithPopulation:(Population *)population { self.population = population; // Set our defaults. + self.crossoverMethod = kDefaultCrossoverMethod; self.mutationRate = kDefaultMutationRate; self.elitismPercentage = kDefaultElitismPercentage; self.tournamentSize = self.population.organisms.count > 3 ? kDefaultTournamentSize : 1; @@ -130,7 +132,7 @@ - (NSArray *)generateOffspringFromOrganisms:(NSArray *)parents count:(NSUInteger continue; } - Organism *child = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:self.mutationRate]; + Organism *child = [parent1 mateWithOrganism:parent2 crossoverMethod:self.crossoverMethod mutationRate:self.mutationRate]; // Add this child to our array of offspring. [offspring addObject:child]; diff --git a/Source/Genome.h b/Source/Genome.h index eee672d..9e936c6 100644 --- a/Source/Genome.h +++ b/Source/Genome.h @@ -8,6 +8,8 @@ #import +static NSUInteger const kMinimumGeneSequenceLength = 4; + @interface Genome : NSObject @property (nonatomic, strong) NSString *domain; diff --git a/Source/Genome.m b/Source/Genome.m index 8affe71..271afd3 100644 --- a/Source/Genome.m +++ b/Source/Genome.m @@ -9,6 +9,7 @@ #import "Genome.h" #import "Random.h" + @implementation Genome #pragma mark - Initializers @@ -25,7 +26,7 @@ - (instancetype)initWithGeneSequence:(NSString *)geneSequence domain:(NSString * } NSParameterAssert(geneSequence); - NSParameterAssert(geneSequence.length > 0); + NSParameterAssert(geneSequence.length >= kMinimumGeneSequenceLength); NSParameterAssert(domain); NSParameterAssert(domain.length > 0); diff --git a/Source/Organism.h b/Source/Organism.h index f2386b5..f38fa59 100644 --- a/Source/Organism.h +++ b/Source/Organism.h @@ -9,6 +9,11 @@ #import #import "Genome.h" +typedef NS_ENUM(NSUInteger, CrossoverMethod) { + CrossoverMethodOnePoint, + CrossoverMethodTwoPoint +}; + @interface Organism : NSObject @property (nonatomic, strong) Genome *genome; @@ -18,6 +23,6 @@ - (instancetype)initWithGenome:(Genome *)genome; - (instancetype)initRandomWithGeneSequenceLength:(NSUInteger)length domain:(NSString *)domain; -+ (instancetype)offspringFromParent1:(Organism *)parent1 parent2:(Organism *)parent2 mutationRate:(CGFloat)mutationRate; +- (instancetype)mateWithOrganism:(Organism *)mate crossoverMethod:(enum CrossoverMethod)crossoverMethod mutationRate:(CGFloat)mutationRate; @end diff --git a/Source/Organism.m b/Source/Organism.m index 89a954f..448ee09 100644 --- a/Source/Organism.m +++ b/Source/Organism.m @@ -36,22 +36,17 @@ - (instancetype)initWithGenome:(Genome *)genome { #pragma mark - Reproduction -+ (instancetype)offspringFromParent1:(Organism *)parent1 parent2:(Organism *)parent2 mutationRate:(CGFloat)mutationRate { - NSParameterAssert(parent1); - NSParameterAssert(parent2); - NSParameterAssert([parent1.genome.domain isEqualToString:parent2.genome.domain]); - NSParameterAssert(parent1.genome.sequence.length == parent2.genome.sequence.length); +- (instancetype)mateWithOrganism:(Organism *)mate crossoverMethod:(enum CrossoverMethod)crossoverMethod mutationRate:(CGFloat)mutationRate { + NSParameterAssert(mate); + NSParameterAssert([self.genome.domain isEqualToString:mate.genome.domain]); + NSParameterAssert(self.genome.sequence.length == mate.genome.sequence.length); NSParameterAssert(mutationRate >= 0 && mutationRate <= 1); - - // Randomly generate a crossover point and combine the parent's genomes there - which will - // be the child's starting gene sequence. - NSInteger crossoverPoint = [Random randomIntegerFromMin:0 toMax:parent1.genome.sequence.length - 1]; - NSString *parent1Contribution = [parent1.genome.sequence substringToIndex:crossoverPoint]; - NSString *parent2Contribution = [parent2.genome.sequence substringFromIndex:crossoverPoint]; - NSString *offspringGeneSequence = [parent1Contribution stringByAppendingString:parent2Contribution]; + + // Generate the offspring's gene sequence from ours and our mate's, using the defined crossover method. + NSString *offspringGeneSequence = [self generateChildGeneSequenceFromMate:mate crossoverMethod:crossoverMethod]; // Create the child's genome with the parent's same configuration and the crossed over genetic pattern. - Genome *childsGenome = [[Genome alloc] initWithGeneSequence:offspringGeneSequence domain:parent1.genome.domain]; + Genome *childsGenome = [[Genome alloc] initWithGeneSequence:offspringGeneSequence domain:self.genome.domain]; // Tell the genome to handle mutation given the provided mutation rate. [childsGenome handleMutationWithRate:mutationRate]; @@ -62,6 +57,48 @@ + (instancetype)offspringFromParent1:(Organism *)parent1 parent2:(Organism *)par return child; } +- (NSString *)generateChildGeneSequenceFromMate:(Organism *)mate crossoverMethod:(CrossoverMethod)crossoverMethod { + NSString *ourGeneSequence = self.genome.sequence; + NSString *mateGeneSequence = mate.genome.sequence; + + switch (crossoverMethod) { + + // Randomly generate a crossover point and combine the parent's genomes there - which will + // be the child's starting gene sequence. + case CrossoverMethodOnePoint: { + NSInteger crossoverPoint = [Random randomIntegerFromMin:1 toMax:ourGeneSequence.length - 2]; + NSString *ourContribution = [ourGeneSequence substringToIndex:crossoverPoint]; + NSString *mateContribution = [mateGeneSequence substringFromIndex:crossoverPoint]; + + return [ourContribution stringByAppendingString:mateContribution]; + } + + // The same as one-point, although two points (one range) are chosen and the genome is split on those, + // with the our genome contributing to the first and last 1/3 of the genes and the mate + // contributing to the middle 1/3. + case CrossoverMethodTwoPoint: { + NSRange rangeForCrossover = [[self class] generateRangeForTwoPointCrossoverFromLength:self.genome.sequence.length]; + + NSString *ourFirstContribution = [ourGeneSequence substringToIndex:rangeForCrossover.location]; + NSString *mateContribution = [mateGeneSequence substringWithRange:rangeForCrossover]; + NSString *ourSecondContribution = [ourGeneSequence substringFromIndex:rangeForCrossover.location + rangeForCrossover.length]; + + return [[ourFirstContribution stringByAppendingString:mateContribution] stringByAppendingString:ourSecondContribution]; + } + } +} + + +#pragma mark - Crossover Utils + ++ (NSRange)generateRangeForTwoPointCrossoverFromLength:(NSUInteger)geneSequenceLength { + NSParameterAssert(geneSequenceLength >= kMinimumGeneSequenceLength); + + NSInteger beginningOfMateRange = [Random randomIntegerFromMin:1 toMax:floor((geneSequenceLength / 2.0)) - 1]; + NSInteger lengthOfMateRange = [Random randomIntegerFromMin:1 toMax:geneSequenceLength - beginningOfMateRange - 2]; + return NSMakeRange(beginningOfMateRange, lengthOfMateRange); +} + #pragma mark - Debugging