avatarThe Pragmatic Programmers

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

1766

Abstract

hljs-built_in">split</span>(p2.genes, crossover_point) ​ c = %Chromosome{​genes:​ front ++ back, ​size:​ length(p1)} ​ [c | <span class="hljs-type">chd</span>] ​ ​<span class="hljs-keyword">end</span>​ ​ ) ​ ​<span class="hljs-keyword">end</span></pre></div><p id="e3b1">Start by examining the first two function definitions. Thanks to Elixir’s rich set of pattern matching features, you can define functions on specific inputs. The first definition is to ensure that the function doesn’t accept empty lists. The next definition simply returns the same chromosome if only one parent is provided.</p><p id="3e49">The next function will work on a list of an arbitrary number of parents. The function starts by selecting a crossover point between 0 and the length of the first parent.</p><p id="5174">Now, examine this code block:</p><div id="30ab"><pre>​ parents ​ <span class="hljs-string">|> Enum.chunk_every(2, 1, [hd(parents)])</span><span class="hljs-string">|> Enum.map(&(List.to_tuple(&1)))</span></pre></div><p id="bd13">Enum.chunk_every/3 will pair every parent into groupings of two starting at index 1. The last argument, [hd(parents)], tells the function to group the leftover elements with the first element of parents. Then, each element is transformed into a tuple to make pattern matching easier.</p><p id="4d25">Now, examine the final block:</p><div id="4cc7"><pre>​ |> Enum.<span class="hljs-built_in">reduce</span>( ​ [], ​ ​fn​ {p1, p2}, chd -> ​ {front, } = Enum.<span class="hljs-built_in">split</span>(p1.genes, crossover_point) ​ {, <span class="hljs-built_in">back</span>} = Enum.<span class="hljs-built_in">split</span>(p2.genes, crossover_point) ​

Options

c = %Chromosome{​genes:​ front ++ <span class="hljs-built_in">back</span>, ​size:​ <span class="hljs-built_in">length</span>(p1)} ​ [c | chd] ​ ​end​</pre></div><p id="ec77">Enum.reduce/3 takes an enumerable, an accumulator, and a function as input. The enumerable is the list of tuples that represent pairs of parents. The accumulator is an empty list. The function simply implements single-point crossover on each pairing of parents and returns the child at the front of the accumulator.</p><p id="e206">This same algorithm can be implemented for each crossover strategy, replacing the body of the anonymous function in Enum.reduce/3 with whatever strategy you want.</p><p id="90e6">Crossing over more than two parents can add a lot of unnecessary complexity to your algorithms. It’s best not to use this approach unless you absolutely have to.</p><p id="79e6"><i>👈 <a href="https://readmedium.com/implementing-other-common-crossover-strategie-s-d602986d39e">Implementing Other Common Crossover Strategie s</a> | <a href="https://readmedium.com/table-of-contents-879fc8614df">TOC</a> | <a href="https://readmedium.com/implementing-chromosome-repairment-7d0f94cda35a">Implementing Chromosome Repairment</a> 👉</i></p><p id="65a9"><i>Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats <a href="https://pragprog.com/titles/smgaelixir">directly from the Pragmatic Programmers</a>. If you notice a code error or formatting mistake, please let us know <a href="https://readmedium.com/how-to-report-errata-4e164674347a">here</a> so that we can fix it.</i></p><figure id="5dfe"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*U2E2a23SuM4520w9CUXSWw.jpeg"><figcaption></figcaption></figure></article></body>

Crossing Over More Than Two Parents

Genetic Algorithms in Elixir — by Sean Moriarity (53 / 101)

👈 Implementing Other Common Crossover Strategie s | TOC | Implementing Chromosome Repairment 👉

Some algorithms require you to select more than two parents for crossover. All of the algorithms presented above can be implemented on multiple parents.

Here’s an example of single-point crossover on multiple parents:

​ ​def​ single_point_crossover([]), ​do​:
​   ​raise​ ​"​​You must have at least one parent!"​
​ ​def​ single_point_crossover([p1 | []]), ​do​: p1
​ 
​ ​def​ single_point_crossover(parents) ​do​
​     crossover_point = ​:rand​.uniform(hd(parents).size)
​     parents
​     |> Enum.chunk_every(2, 1, [hd(parents)])
​     |> Enum.map(&(List.to_tuple(&1)))
​     |> Enum.reduce(
​         [],
​         ​fn​ {p1, p2}, chd ->
​             {front, _} = Enum.split(p1.genes, crossover_point)
​             {_, back} = Enum.split(p2.genes, crossover_point)
​             c = %Chromosome{​genes:​ front ++ back, ​size:​ length(p1)}
​             [c | chd]
​         ​end​
​     )
​ ​end

Start by examining the first two function definitions. Thanks to Elixir’s rich set of pattern matching features, you can define functions on specific inputs. The first definition is to ensure that the function doesn’t accept empty lists. The next definition simply returns the same chromosome if only one parent is provided.

The next function will work on a list of an arbitrary number of parents. The function starts by selecting a crossover point between 0 and the length of the first parent.

Now, examine this code block:

​ parents
​ |> Enum.chunk_every(2, 1, [hd(parents)])|> Enum.map(&(List.to_tuple(&1)))

Enum.chunk_every/3 will pair every parent into groupings of two starting at index 1. The last argument, [hd(parents)], tells the function to group the leftover elements with the first element of parents. Then, each element is transformed into a tuple to make pattern matching easier.

Now, examine the final block:

​ |> Enum.reduce(
​     [],
​     ​fn​ {p1, p2}, chd ->
​         {front, _} = Enum.split(p1.genes, crossover_point)
​         {_, back} = Enum.split(p2.genes, crossover_point)
​         c = %Chromosome{​genes:​ front ++ back, ​size:​ length(p1)}
​         [c | chd]
​     ​end​

Enum.reduce/3 takes an enumerable, an accumulator, and a function as input. The enumerable is the list of tuples that represent pairs of parents. The accumulator is an empty list. The function simply implements single-point crossover on each pairing of parents and returns the child at the front of the accumulator.

This same algorithm can be implemented for each crossover strategy, replacing the body of the anonymous function in Enum.reduce/3 with whatever strategy you want.

Crossing over more than two parents can add a lot of unnecessary complexity to your algorithms. It’s best not to use this approach unless you absolutely have to.

👈 Implementing Other Common Crossover Strategie s | TOC | Implementing Chromosome Repairment 👉

Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats directly from the Pragmatic Programmers. If you notice a code error or formatting mistake, please let us know here so that we can fix it.

Smgaelixir
Recommended from ReadMedium