A running gene crossover function for a Genetic Algorithm

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












I'm writing a Genetic Algorithm, and need to write a function that crosses two gene sequences. Basically, I want it to work like this:



(running-cross [1 2 3 4 5 6 7 8 9] ; Gene sequence 1
[11 22 33 44 55 66 77 88 99] ; Gene sequence 2
[2 5]) ; The "cross points"
=> [1 2 33 44 55 6 7 8 9]


Note how it crosses over to the second sequence at index 2, and back to the first sequence at index 5. See the related PPCG challenge for more examples.



I have two main concerns with my implementation:



  • It's ugly. The reducing function is atrocious, but I don't know what can be improved. The short names don't help, but longer names would add significant bloat which wouldn't be great either.


  • It's inefficient. It requires iterating the entire gene sequence, even if there are few, or even no cross over points. Of course I could add a special case and check if the points are empty first, but that still doesn't help much. Say there's a single cross over point at the end of the genes. It will require a full iteration regardless. I can't tell if O(n) is the best I'm going to get, but I'd prefer n be the number of cross-over points, not the number of genes per sequence.



(defn running-cross [genes other-genes cross-points]
(let [cp-set (set cross-points)]
(->> (map vector (range) genes other-genes)

(reduce (fn [[g1? acc] [i g1 g2]]
(let [g1?' (if (cp-set i) (not g1?) g1?)]
[g1?' (conj acc (if g1?' g1 g2))]))
[true ])

(second))))






share|improve this question



























    up vote
    3
    down vote

    favorite












    I'm writing a Genetic Algorithm, and need to write a function that crosses two gene sequences. Basically, I want it to work like this:



    (running-cross [1 2 3 4 5 6 7 8 9] ; Gene sequence 1
    [11 22 33 44 55 66 77 88 99] ; Gene sequence 2
    [2 5]) ; The "cross points"
    => [1 2 33 44 55 6 7 8 9]


    Note how it crosses over to the second sequence at index 2, and back to the first sequence at index 5. See the related PPCG challenge for more examples.



    I have two main concerns with my implementation:



    • It's ugly. The reducing function is atrocious, but I don't know what can be improved. The short names don't help, but longer names would add significant bloat which wouldn't be great either.


    • It's inefficient. It requires iterating the entire gene sequence, even if there are few, or even no cross over points. Of course I could add a special case and check if the points are empty first, but that still doesn't help much. Say there's a single cross over point at the end of the genes. It will require a full iteration regardless. I can't tell if O(n) is the best I'm going to get, but I'd prefer n be the number of cross-over points, not the number of genes per sequence.



    (defn running-cross [genes other-genes cross-points]
    (let [cp-set (set cross-points)]
    (->> (map vector (range) genes other-genes)

    (reduce (fn [[g1? acc] [i g1 g2]]
    (let [g1?' (if (cp-set i) (not g1?) g1?)]
    [g1?' (conj acc (if g1?' g1 g2))]))
    [true ])

    (second))))






    share|improve this question























      up vote
      3
      down vote

      favorite









      up vote
      3
      down vote

      favorite











      I'm writing a Genetic Algorithm, and need to write a function that crosses two gene sequences. Basically, I want it to work like this:



      (running-cross [1 2 3 4 5 6 7 8 9] ; Gene sequence 1
      [11 22 33 44 55 66 77 88 99] ; Gene sequence 2
      [2 5]) ; The "cross points"
      => [1 2 33 44 55 6 7 8 9]


      Note how it crosses over to the second sequence at index 2, and back to the first sequence at index 5. See the related PPCG challenge for more examples.



      I have two main concerns with my implementation:



      • It's ugly. The reducing function is atrocious, but I don't know what can be improved. The short names don't help, but longer names would add significant bloat which wouldn't be great either.


      • It's inefficient. It requires iterating the entire gene sequence, even if there are few, or even no cross over points. Of course I could add a special case and check if the points are empty first, but that still doesn't help much. Say there's a single cross over point at the end of the genes. It will require a full iteration regardless. I can't tell if O(n) is the best I'm going to get, but I'd prefer n be the number of cross-over points, not the number of genes per sequence.



      (defn running-cross [genes other-genes cross-points]
      (let [cp-set (set cross-points)]
      (->> (map vector (range) genes other-genes)

      (reduce (fn [[g1? acc] [i g1 g2]]
      (let [g1?' (if (cp-set i) (not g1?) g1?)]
      [g1?' (conj acc (if g1?' g1 g2))]))
      [true ])

      (second))))






      share|improve this question













      I'm writing a Genetic Algorithm, and need to write a function that crosses two gene sequences. Basically, I want it to work like this:



      (running-cross [1 2 3 4 5 6 7 8 9] ; Gene sequence 1
      [11 22 33 44 55 66 77 88 99] ; Gene sequence 2
      [2 5]) ; The "cross points"
      => [1 2 33 44 55 6 7 8 9]


      Note how it crosses over to the second sequence at index 2, and back to the first sequence at index 5. See the related PPCG challenge for more examples.



      I have two main concerns with my implementation:



      • It's ugly. The reducing function is atrocious, but I don't know what can be improved. The short names don't help, but longer names would add significant bloat which wouldn't be great either.


      • It's inefficient. It requires iterating the entire gene sequence, even if there are few, or even no cross over points. Of course I could add a special case and check if the points are empty first, but that still doesn't help much. Say there's a single cross over point at the end of the genes. It will require a full iteration regardless. I can't tell if O(n) is the best I'm going to get, but I'd prefer n be the number of cross-over points, not the number of genes per sequence.



      (defn running-cross [genes other-genes cross-points]
      (let [cp-set (set cross-points)]
      (->> (map vector (range) genes other-genes)

      (reduce (fn [[g1? acc] [i g1 g2]]
      (let [g1?' (if (cp-set i) (not g1?) g1?)]
      [g1?' (conj acc (if g1?' g1 g2))]))
      [true ])

      (second))))








      share|improve this question












      share|improve this question




      share|improve this question








      edited Mar 25 at 1:25
























      asked Mar 24 at 23:30









      Carcigenicate

      2,32411128




      2,32411128




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          1
          down vote



          accepted










          I tried writing it with a recursive loop. I personally find it a bit easier to read, but I guess it depends on how familiar you are with recursion.



          (defn running-cross
          [genes other-genes cross-points]
          (loop [remaining-cross-points cross-points
          last-cross 0
          take-from-g1? true
          result ]
          (let [next-gene (if take-from-g1? genes other-genes)
          next-cross (first remaining-cross-points)]
          (if next-cross
          (recur (rest remaining-cross-points)
          next-cross
          (not take-from-g1?)
          (concat result (subvec next-gene last-cross next-cross)))
          (concat result (subvec next-gene last-cross))))))


          As for efficiency, my algorithm is a lot faster; especially when there are few cross-points. I believe this is mainly because it iterates over the cross-points instead of the full length of the genes, and then uses subvec and concat which are quite efficient when used on vectors.



          Here are some times:



          ;; 5 000 000 length genes, crossing over on every index
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 3298.197955 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 11672.627633 msecs"
          nil

          ;; 5 000 000 length genes, crossing over once
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 287.335904 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 6160.856827 msecs"
          nil





          share|improve this answer





















          • I actually prefer the look of mine, but I can't deny that efficiency increase. That will definitely help down the road. Thank you.
            – Carcigenicate
            Apr 3 at 10:58










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190411%2fa-running-gene-crossover-function-for-a-genetic-algorithm%23new-answer', 'question_page');

          );

          Post as a guest






























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          1
          down vote



          accepted










          I tried writing it with a recursive loop. I personally find it a bit easier to read, but I guess it depends on how familiar you are with recursion.



          (defn running-cross
          [genes other-genes cross-points]
          (loop [remaining-cross-points cross-points
          last-cross 0
          take-from-g1? true
          result ]
          (let [next-gene (if take-from-g1? genes other-genes)
          next-cross (first remaining-cross-points)]
          (if next-cross
          (recur (rest remaining-cross-points)
          next-cross
          (not take-from-g1?)
          (concat result (subvec next-gene last-cross next-cross)))
          (concat result (subvec next-gene last-cross))))))


          As for efficiency, my algorithm is a lot faster; especially when there are few cross-points. I believe this is mainly because it iterates over the cross-points instead of the full length of the genes, and then uses subvec and concat which are quite efficient when used on vectors.



          Here are some times:



          ;; 5 000 000 length genes, crossing over on every index
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 3298.197955 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 11672.627633 msecs"
          nil

          ;; 5 000 000 length genes, crossing over once
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 287.335904 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 6160.856827 msecs"
          nil





          share|improve this answer





















          • I actually prefer the look of mine, but I can't deny that efficiency increase. That will definitely help down the road. Thank you.
            – Carcigenicate
            Apr 3 at 10:58














          up vote
          1
          down vote



          accepted










          I tried writing it with a recursive loop. I personally find it a bit easier to read, but I guess it depends on how familiar you are with recursion.



          (defn running-cross
          [genes other-genes cross-points]
          (loop [remaining-cross-points cross-points
          last-cross 0
          take-from-g1? true
          result ]
          (let [next-gene (if take-from-g1? genes other-genes)
          next-cross (first remaining-cross-points)]
          (if next-cross
          (recur (rest remaining-cross-points)
          next-cross
          (not take-from-g1?)
          (concat result (subvec next-gene last-cross next-cross)))
          (concat result (subvec next-gene last-cross))))))


          As for efficiency, my algorithm is a lot faster; especially when there are few cross-points. I believe this is mainly because it iterates over the cross-points instead of the full length of the genes, and then uses subvec and concat which are quite efficient when used on vectors.



          Here are some times:



          ;; 5 000 000 length genes, crossing over on every index
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 3298.197955 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 11672.627633 msecs"
          nil

          ;; 5 000 000 length genes, crossing over once
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 287.335904 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 6160.856827 msecs"
          nil





          share|improve this answer





















          • I actually prefer the look of mine, but I can't deny that efficiency increase. That will definitely help down the road. Thank you.
            – Carcigenicate
            Apr 3 at 10:58












          up vote
          1
          down vote



          accepted







          up vote
          1
          down vote



          accepted






          I tried writing it with a recursive loop. I personally find it a bit easier to read, but I guess it depends on how familiar you are with recursion.



          (defn running-cross
          [genes other-genes cross-points]
          (loop [remaining-cross-points cross-points
          last-cross 0
          take-from-g1? true
          result ]
          (let [next-gene (if take-from-g1? genes other-genes)
          next-cross (first remaining-cross-points)]
          (if next-cross
          (recur (rest remaining-cross-points)
          next-cross
          (not take-from-g1?)
          (concat result (subvec next-gene last-cross next-cross)))
          (concat result (subvec next-gene last-cross))))))


          As for efficiency, my algorithm is a lot faster; especially when there are few cross-points. I believe this is mainly because it iterates over the cross-points instead of the full length of the genes, and then uses subvec and concat which are quite efficient when used on vectors.



          Here are some times:



          ;; 5 000 000 length genes, crossing over on every index
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 3298.197955 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 11672.627633 msecs"
          nil

          ;; 5 000 000 length genes, crossing over once
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 287.335904 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 6160.856827 msecs"
          nil





          share|improve this answer













          I tried writing it with a recursive loop. I personally find it a bit easier to read, but I guess it depends on how familiar you are with recursion.



          (defn running-cross
          [genes other-genes cross-points]
          (loop [remaining-cross-points cross-points
          last-cross 0
          take-from-g1? true
          result ]
          (let [next-gene (if take-from-g1? genes other-genes)
          next-cross (first remaining-cross-points)]
          (if next-cross
          (recur (rest remaining-cross-points)
          next-cross
          (not take-from-g1?)
          (concat result (subvec next-gene last-cross next-cross)))
          (concat result (subvec next-gene last-cross))))))


          As for efficiency, my algorithm is a lot faster; especially when there are few cross-points. I believe this is mainly because it iterates over the cross-points instead of the full length of the genes, and then uses subvec and concat which are quite efficient when used on vectors.



          Here are some times:



          ;; 5 000 000 length genes, crossing over on every index
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 3298.197955 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) (vec (range 5000000)))) nil)
          "Elapsed time: 11672.627633 msecs"
          nil

          ;; 5 000 000 length genes, crossing over once
          user=> (do (time (running-cross-mine (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 287.335904 msecs"
          nil
          user=> (do (time (running-cross-yours (vec (range 5000000)) (vec (range 5000000)) [2500000])) nil)
          "Elapsed time: 6160.856827 msecs"
          nil






          share|improve this answer













          share|improve this answer



          share|improve this answer











          answered Apr 3 at 8:28









          rreillo

          1,058510




          1,058510











          • I actually prefer the look of mine, but I can't deny that efficiency increase. That will definitely help down the road. Thank you.
            – Carcigenicate
            Apr 3 at 10:58
















          • I actually prefer the look of mine, but I can't deny that efficiency increase. That will definitely help down the road. Thank you.
            – Carcigenicate
            Apr 3 at 10:58















          I actually prefer the look of mine, but I can't deny that efficiency increase. That will definitely help down the road. Thank you.
          – Carcigenicate
          Apr 3 at 10:58




          I actually prefer the look of mine, but I can't deny that efficiency increase. That will definitely help down the road. Thank you.
          – Carcigenicate
          Apr 3 at 10:58












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190411%2fa-running-gene-crossover-function-for-a-genetic-algorithm%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Python Lists

          Aion

          JavaScript Array Iteration Methods