<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harrison Yan</title>
    <description>The latest articles on DEV Community by Harrison Yan (@hyan18).</description>
    <link>https://dev.to/hyan18</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F349432%2Fa2832692-0162-493b-8051-6768b5852dc7.jpeg</url>
      <title>DEV Community: Harrison Yan</title>
      <link>https://dev.to/hyan18</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hyan18"/>
    <language>en</language>
    <item>
      <title>Sorting Algorithms</title>
      <dc:creator>Harrison Yan</dc:creator>
      <pubDate>Tue, 07 Apr 2020 14:52:35 +0000</pubDate>
      <link>https://dev.to/hyan18/sorting-algorithms-4851</link>
      <guid>https://dev.to/hyan18/sorting-algorithms-4851</guid>
      <description>&lt;p&gt;A sorting algorithm is an algorithm which puts elements of a list in a certain order, most commonly numerical and alphabetical order.&lt;br&gt;
In this post, I will be investigating the complexity of five sorting algorithms, where two of which are considered efficient sorting algorithms.&lt;/p&gt;
&lt;h3&gt;
  
  
  Bubble Sort
&lt;/h3&gt;

&lt;p&gt;One of the simplest sorting algorithms is bubble sort which works as follows:&lt;br&gt;
Starting from the beginning of the list,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compare the current element with the next element&lt;/li&gt;
&lt;li&gt;If the current element is greater than the next element swap them&lt;/li&gt;
&lt;li&gt;Current element is now the next element&lt;/li&gt;
&lt;li&gt;Repeat from step 1 until at the end of the unordered elements&lt;/li&gt;
&lt;li&gt;Current element is now the first element, repeat from step 1 (Go back to the beginning of the list and make another pass)&lt;/li&gt;
&lt;li&gt;Stop when no swaps were made for a pass&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is an example of Bubble Sort in action:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RoVa7BZg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/c/c8/Bubble-sort-example-300px.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RoVa7BZg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/c/c8/Bubble-sort-example-300px.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Analysis
&lt;/h4&gt;

&lt;p&gt;In code we have,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module BubbleSort

  def self.run(array)
    a = array.dup
    to_sort = array.length - 1
    while to_sort &amp;gt; 0
      for i in 0...(to_sort) do
        if a[i] &amp;gt; a[i+1]
          a[i], a[i+1] = a[i+1], a[i]
        end
      end
      to_sort -= 1
    end
    return a
  end

end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We could predict the complexity of this code to be O(n^2) as we have one loop nested in another (the for loop nested in the while loop) with both of these iterating based off of a variable which is dependent on n.&lt;/p&gt;

&lt;p&gt;Looking at the algorithm, with each pass (steps 1 to 4) the largest unordered element will have been sorted to the end of the list. We can see that in the worst case (when the list starts in reverse order), on a given input of size n, there will need to be n-1 comparisons to sort the first element, n-2 comparisons to sort the next element and so on until the last unordered element which will need 1 comparison. Using Big O notation, simply letting each pass use n-1 comparisons and we make a total of n passes. Therefore, denoting the worst case runtime as T(n), we can see that&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T(n) = O(n*(n-1)) =&amp;gt; T(n) = O(n^2)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Selection Sort
&lt;/h3&gt;

&lt;p&gt;Another simple sorting algorithm is selection sort which works as follows:&lt;br&gt;
Starting from the beginning of the list,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the minimum unsorted element&lt;/li&gt;
&lt;li&gt;Swap it with the current index (front of the unsorted list)&lt;/li&gt;
&lt;li&gt;Move to the next index and repeat from step 1&lt;/li&gt;
&lt;li&gt;Repeat until at the end of the list&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is an example of Selection Sort in action:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T7PUry2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/3840/1%2Ak0dHMa2l2bRr95VB4llOqw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T7PUry2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/3840/1%2Ak0dHMa2l2bRr95VB4llOqw.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Analysis
&lt;/h4&gt;

&lt;p&gt;In code we have,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module SelectionSort

  def self.run(array)
    a = array.dup
    i = 0
    while i &amp;lt; array.length
      min = { :number =&amp;gt; a[i], :index =&amp;gt; i }
      for j in i...(array.length) do
        if a[j] &amp;lt; min[:number]
          min[:number] = a[j]
          min[:index] = j
        end
      end
      a[i], a[min[:index]] = a[min[:index]], a[i]
      i += 1
    end
    return a
  end

end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Similar to bubble sort we have one loop nested in another (a for loop nested in a while loop) with both of these iterating based off of a variable which is dependent on n. So we also predict selection sort to have a complexity of O(n^2)&lt;/p&gt;

&lt;p&gt;Looking at the algorithm, with each pass the smallest unordered element in the list is swapped to the front of the array. In the worst case, we have to make n-1 comparisons to find the minimum the first time and one less comparison thereafter. Since each pass puts the next smallest unordered element in the correct place we need to make n-1 passes (final element is sorted without the need for another pass). So with similar reasoning to the case of bubble sort, we can approximate as follows&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T(n) = O((n-1)*(n-1)) =&amp;gt; T(n) = O(n^2)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Insertion Sort
&lt;/h3&gt;

&lt;p&gt;The final simple sorting algorithm is insertion sort which works as follows:&lt;br&gt;
Starting from the second element of the list,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compare the current element with the previous element&lt;/li&gt;
&lt;li&gt;If the current element is less than the previous element swap them&lt;/li&gt;
&lt;li&gt;Repeat step 2 until the current element is greater than the previous element&lt;/li&gt;
&lt;li&gt;Move to the next index and repeat from step 1&lt;/li&gt;
&lt;li&gt;Repeat until at the end of the list&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is an example of Insertion Sort in action:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IBBqZA5R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/0/0f/Insertion-sort-example-300px.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IBBqZA5R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/0/0f/Insertion-sort-example-300px.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Analysis
&lt;/h4&gt;

&lt;p&gt;In code we have,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module InsertionSort

  def self.run(array)
    a = array.dup
    i = 1
    while i &amp;lt; array.length
      j = i - 1
      while  (a[j+1] &amp;lt; a[j]) &amp;amp;&amp;amp; (j &amp;gt;= 0)
        a[j+1], a[j] = a[j], a[j+1]
        j -= 1
      end
      i += 1
    end
    return a
  end

end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This time we don't have a for loop nested within a while loop, we instead have a while loop nested within another while loop. The outer while loop is clearly iterating over a variable dependent on n, the inner loop is not as obvious. &lt;/p&gt;

&lt;p&gt;Looking at the algorithm, with each pass an element is added to the sublist of ordered elements at the front of the list. We make at most k comparisons for the kth pass and we make a total of n-1 comparisons, summing we can conclude&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T(n) = O((n-1)*(n/2)) = O(n^2)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison of simple sorting algorithms
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9a5ZQXeI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/id3ciqb81b9lp3k83iib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9a5ZQXeI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/id3ciqb81b9lp3k83iib.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
The graph does seem to show that the algorithms are quadratic in growth (although there is a lack of data due to the limit of computational power) where insertion sort is the marginally the most efficient followed by selection sort, and then bubble sort.&lt;/p&gt;
&lt;h3&gt;
  
  
  Efficient Sorting Algorithms
&lt;/h3&gt;

&lt;p&gt;Now we move onto the efficient sorting algorithms which are called this way because their average time complexity is O(nlogn) - and it can be proved that any comparison sorting algorithm is Theta(nlogn) which means that the time complexity is bounded below by nlogn and so it is the most efficient it can be.&lt;/p&gt;
&lt;h3&gt;
  
  
  Merge Sort
&lt;/h3&gt;

&lt;p&gt;The first efficient sorting algorithm is merge sort which is a divide and conquer algorithm (and so uses recursion) which works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split the list into sublists each containing one element&lt;/li&gt;
&lt;li&gt;Repeatedly merge sublists until there is only one sublist remaining - the sorted list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When merging, sorted sublists are created by choosing the smallest element from each sublist to be merged until one sublist is empty. If the other sublist still has elements then append the rest of the elements to the end of the sorted sublist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pdU-IP47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pdU-IP47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Analysis
&lt;/h4&gt;

&lt;p&gt;In code we have,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module MergeSort

  def self.run(array)
    a = array.dup
    TopDownSplitMerge(array, 0, array.length, a) # Sort from (input) array into a
    return a
  end

  def self.TopDownSplitMerge(b, start, finish, a)
    return if (finish - start &amp;lt; 2) # Base case: An array of one element is sorted

    middle = (finish + start) / 2
    # Recursively sort both runs from array a into array b
    TopDownSplitMerge(a, start, middle, b) # Sort the left run
    TopDownSplitMerge(a, middle, finish, b) # Sort the right run
    TopDownMerge(b, start, middle, finish, a) # Merge the resulting runs from array b into array a
  end

  def self.TopDownMerge(a, start, middle, finish, b)
    i = start
    j = middle

    for k in start...finish do
      if (i &amp;lt; middle &amp;amp;&amp;amp; (j &amp;gt;= finish || a[i] &amp;lt;= a[j]))
        b[k] = a[i]
        i += 1
      else
        b[k] = a[j]
        j += 1
      end
    end
  end

end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It can be shown, using a recursion tree, that in the worst case&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T(n) = O(nlogn)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Sort
&lt;/h3&gt;

&lt;p&gt;Another efficient sorting algorithm which is also a divide and conquer algorithm is quick sort which works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose a pivot element&lt;/li&gt;
&lt;li&gt;Partition the remaining elements into two sub-arrays, one containing elements less than the pivot and the other containing elements greater than the pivot&lt;/li&gt;
&lt;li&gt;Recursively sort the sub-arrays&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yQHHPb9a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/9/9c/Quicksort-example.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yQHHPb9a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/9/9c/Quicksort-example.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Analysis
&lt;/h4&gt;

&lt;p&gt;In code we have,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module QuickSort

  def self.run(array, pivot = "random")
    a = array.dup
    return a if a.length &amp;lt; 2

    pivot == "random" ? r = rand(a.length) : r = 0
    pivot = a[r]
    lower = []
    upper = []
    (a[...r] + a[(r+1)..]).each { |n|
      if n &amp;lt; pivot
        lower.append(n)
      else
        upper.append(n)
      end
    }
    run(lower) + [pivot] + run(upper)
  end

end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It can be shown that in the worst case,&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T(n) = O(n^2)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison of efficient sorting algorithms
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9WfxoCzi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hanisoau4tr6cbvy19bb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9WfxoCzi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hanisoau4tr6cbvy19bb.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
We can see that Merge sort and quick sort are much more efficient than even the fastest simple sort, insertion sort. From the graph, the efficient sorts are much more linear in growth which coincides with the complexity of nlogn as opposed to n^2 - this is a little surprising for the case of quick sort which has a time complexity of O(n^2).&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a09qHWUs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1gb2d1sz9bm55czneclt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a09qHWUs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1gb2d1sz9bm55czneclt.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
It can be shown, through probabilistic analysis, that quick sort, with a random pivot, actually has an average time complexity of O(nlogn). We can see this on the above graph where quick sort and merge sort are quite similar in growth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thoughts
&lt;/h3&gt;

&lt;p&gt;There are a wide variety of sorting algorithms and it seems that divide and conquer is a very powerful paradigm in terms of complexity. Analysing algorithms for the worst case is helpful but it is important to look at the average case as it can allow you to use algorithms which may be slightly less efficient but better in other ways such as simplicity or even time complexity.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Algorithmic Complexity - Shuffle</title>
      <dc:creator>Harrison Yan</dc:creator>
      <pubDate>Wed, 11 Mar 2020 20:31:35 +0000</pubDate>
      <link>https://dev.to/hyan18/algorithmic-complexity-shuffle-4nah</link>
      <guid>https://dev.to/hyan18/algorithmic-complexity-shuffle-4nah</guid>
      <description>&lt;p&gt;After graduating from Makers Academy I thought it would be interesting to look at topics which are not covered on the course but are still important to being a developer, this included Algorithmic Complexity.&lt;/p&gt;

&lt;p&gt;Algorithmic complexity can be split into two parts, time complexity and space complexity. I will be focusing on is time complexity which questions how efficient an algorithm is as the input gets larger.&lt;/p&gt;

&lt;p&gt;The first function I looked at was Shuffle but before I did anything I had to create a timing framework so I could time my code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timing Framework
&lt;/h3&gt;

&lt;p&gt;In Ruby, there are a few ways you could do this, I ended up using the Benchmark module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'benchmark'
n = 50000
for i in 1..10 do
  size = n*i
  arr = ((1..size).to_a)
  Benchmark.bm do |x|
    x.report { arr.shuffle }
  end
end
# Output
user     system      total        real
   0.000000   0.000000   0.000000 (  0.001211)
       user     system      total        real
   0.000000   0.000000   0.000000 (  0.002816)
       user     system      total        real
   0.000000   0.000000   0.000000 (  0.005472)
       user     system      total        real
   0.000000   0.015625   0.015625 (  0.007966)
       user     system      total        real
   0.000000   0.000000   0.000000 (  0.010034)
       user     system      total        real
   0.000000   0.000000   0.000000 (  0.014221)
       user     system      total        real
   0.000000   0.000000   0.000000 (  0.012675)
       user     system      total        real
   0.015625   0.000000   0.015625 (  0.011585)
       user     system      total        real
   0.000000   0.015625   0.015625 (  0.018004)
       user     system      total        real
   0.015625   0.000000   0.015625 (  0.016173)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main information I was looking for was the realtime (last column) so I instead just used Benchmark.realtime and also ran the code multiple times so I could use the average runtime. Some further optimisations I did were, I changed the input size to be a range to allow for easier adjustment, returned the time in (Seconds x 10⁹) for clarity and removed outliers by throwing away the top and bottom 5% of results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'benchmark'
a = 5000
b = 100000
count = b / a
for i in 1..count do
  size = a*i
  arr = ((1..size).to_a)
  results = []
times = 100
for i in 1..times do
    results &amp;lt;&amp;lt; Benchmark.realtime { arr.shuffle }
  end
  out = times*0.05
results.sort!
  results.shift(out)
  results.pop(out)
  time = (results.inject(:+)/results.length) * (10**9)
  puts "#{size} #{time}"
end
# Output
5000 106623.33333534157
10000 222154.4444384765
15000 319890.00000167935
20000 439441.1111257391
25000 380744.4444520217
30000 442679.9999995031
35000 525283.3333340985
40000 786333.3333135516
45000 752957.7777657121
50000 773688.8888934522
55000 915482.2222095997
60000 1063672.2222165596
65000 941309.9999922755
70000 1014344.4444490038
75000 1111192.222232502
80000 1243894.4444486173
85000 1382976.666665551
90000 1472218.8888855372
95000 1501871.1111072965
100000 1648841.111111526
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then used Excel to plot the data on a graph.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F72tqt0bqf3xvjrr0ezkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F72tqt0bqf3xvjrr0ezkb.png" alt="Ruby Shuffle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks pretty good, now I can try to write my own algorithm for shuffle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shuffle Algorithm: Naive Approach
&lt;/h3&gt;

&lt;p&gt;My first approach was a naive one which involved using the delete function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def bad_shuffle(array)
  arr = array.dup
  new_arr = []
  until arr.empty?
    i = rand(arr.length)
    new_arr &amp;lt;&amp;lt; arr.delete_at(i)
  end
  new_arr
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was really slow and by isolating lines in the code I was able to discover that it was slow because of the delete_at function, so I had to do some research. I found out that delete functions on arrays are slow because after deleting an element, you have to 're-index' all the elements that come after it. This can at worst, on an input of size n, be an O(n) operation (in the case where you are removing the first element in the array).&lt;br&gt;
This lead to the below graph which looks to be exponential in growth - not what you like to see.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frw5z3z7oujtagmpbvfqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frw5z3z7oujtagmpbvfqf.png" alt="Bad Shuffle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was at this point I had remembered in my Combinatorial Optimisation module there was the mention of a shuffle algorithm which doesn't use delete.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fisher-Yates Shuffle
&lt;/h3&gt;

&lt;p&gt;Instead of deleting elements, Fisher-Yates moves elements to the end of the array and so only needs to make lookups, which are fast (actually O(1)), and swaps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def fy_shuffle(array)
  arr = array.dup
  length = arr.length
  while length &amp;gt; 1
    r = rand(length)
    arr[r], arr[length-1] = arr[length-1], arr[r]
    length -= 1
  end
  arr
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resulted in a much better graph that looked quite linear.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuhqn367glbva400hk3tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuhqn367glbva400hk3tm.png" alt="Fisher-Yates"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Inside-out
&lt;/h3&gt;

&lt;p&gt;I also tried implementing the inside-out variant of Fisher-Yates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def io_shuffle(array)
  new_arr = []
  for i in 0...(array.length)
    j = rand(i+1)
    new_arr[i], new_arr[j] = new_arr[j], new_arr[i]  if j != i
    new_arr[j] = array[i]
  end
  new_arr
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was also linear in growth but didn't seem to do better than Fisher-Yates in this case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frzrvbdqz6y3urteqa8k8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frzrvbdqz6y3urteqa8k8.png" alt="Inside-out"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnpsmbhkvof3x0p3gmgh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnpsmbhkvof3x0p3gmgh6.png" alt="Shuffle-Comparison"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whilst being much more efficient than the naive approach (which I couldn't fit on the graph because of how drastically slow it is), both approaches were still not as efficient as the built-in method. &lt;br&gt;
Upon further research, it does turn out that Ruby's shuffle also uses the Fisher-Yates algorithm but it's that more efficient because it is written in C, a low-level language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thoughts
&lt;/h3&gt;

&lt;p&gt;I feel like this exercise was a great introduction into algorithmic complexity and I hope to learn more about it.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
