Sorting algorithms in Ruby, Part 1

For fun, I decided to implement some classic sorting algorithms in an object oriented language. In this case Ruby.

Later I plan to implement some sorting algorithms in a functional language to contrast the differences.

For now, here are the algorithms I chose the implement.

Bubble sort
Selection sort
Insertion sort
Merge sort

I implemented those by reading the textual descriptions of the algorithms on Wikipedia and trying to refrain from looking at actual source code or pseudo code.

To test the algorithms I wrote some simple test cases.

require 'test/unit'
require './bubble_sort.rb'
require './selection_sort.rb'
require './insertion_sort.rb'
require './merge_sort.rb'

class SearchTest < Test::Unit::TestCase
	@@unsorted = [5, 2, 3, 8, 1, 3, 12, 10, 11, 6]
	@@sorted = @@unsorted.sort 
	
	def test_bubble_sort		
		bubble_sort = BubbleSort.new
		result = bubble_sort.sort(@@unsorted)
		
		assert_equal @@sorted, result
	end
	
	def test_selection_sort
		selection_sort = SelectionSort.new
		result = selection_sort.sort(@@unsorted)
		
		assert_equal @@sorted, result
	end
	
	def test_insertion_sort_out_of_place
		insertion_sort = InsertionSort.new
		result = insertion_sort.sort_out_of_place(@@unsorted)
		
		assert_equal @@sorted, result
	end
	
	def test_insertion_sort_in_place
		insertion_sort = InsertionSort.new
		result = insertion_sort.sort_in_place(@@unsorted)
		
		assert_equal @@sorted, result
	end
	
	def test_merge_sort
		merge_sort = MergeSort.new
		result = merge_sort.sort(@@unsorted.dup)
		
		assert_equal @@sorted, result
	end
	
	# Additional tests on merge sort since it's more complex than the others
	def test_merge_sort_random_arrays
		merge_sort = MergeSort.new
		a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
		
		assert_equal a, merge_sort.sort(a.shuffle)
		assert_equal a, merge_sort.sort(a.shuffle)
		assert_equal a, merge_sort.sort(a.shuffle)
	end
end

In these tests I create an unsorted array which I then sort using Ruby’s built-in sort method. I then run the unsorted array into various objects implementing the algorithms and compare the results with the one sorted by Ruby.

Bubble sort

The first one is bubble sort. It’s fairly inefficient, the complexity is O(n2). If you have done some CS classes in the past you probably went over this one. It iterates over the array, comparing two values at a time and bubbles up the highest values to the end of the array. It continues doing this until it can determine that no more swaps are needed. In other words, that the array is now sorted.

While it can beat some algorithms for nearly sorted arrays, ie: [1, 2, 4, 3, 5, 6], in the real world it’s uses are mostly academic.

class BubbleSort
	def sort(to_sort)
		sorted = false

		until sorted
			sorted = true

			for index in 0..(to_sort.length - 2)
				if to_sort[index] > to_sort[index + 1]
					sorted = false
					to_sort[index], to_sort[index + 1] = to_sort[index + 1], to_sort[index]
				end
			end
		end

		return to_sort
	end
end

There are ways to optimize bubble sort. Rather than looking into common ways to optimize Bubble sort, I will take this time to look into more algorithms.

Selection sort

Selection sort is another inefficient but simple sort. It’s complexity is again O(n2), because of the nested loop.

It’s an in-place sort, meaning that it uses a small constant amount of data to perform it’s sort. In-place is also frequently taken to mean that it works on the data set at hand, by swapping the existing values.

Selection sort works by dividing the array in two; the sorted array at the beginning and the unsorted array at the end. Initially the whole array is unsorted.

The algorithm iterates over the array finding the smallest element. It then swaps this element to put it at the loop’s current index. What this does is add it to the end of sorted elements.

I will illustrate this with a sample array. [3, 6, 1, 2]
The sorted and unsorted values will be divided with a | symbol.

Initial value:
[ | 3, 6, 1, 2]

First pass:
[1 | 6, 3, 2]

Second pass:
[1, 2 | 3, 6]

Third pass:
[1, 2, 3, 6]

As you can see in this example, there is no need to do a fourth pass. When we are left with a single element, it is by definition already sorted.

class SelectionSort
	def sort(to_sort)
		for index in 0..(to_sort.length - 2)
			# select the first element as the temporary minimum
			index_of_minimum = index

			# iterate over all other elements to find the minimum
			for inner_index in index..(to_sort.length - 1)
				if to_sort[inner_index] < to_sort[index_of_minimum]
					index_of_minimum = inner_index
				end
			end

			if index_of_minimum != index
				to_sort[index], to_sort[index_of_minimum] = to_sort[index_of_minimum], to_sort[index]
			end
		end

		return to_sort
	end
end

Insertion sort

For Insertion sort, I read a description stating that the algorithm works by going through the elements to be sorted and inserting them in their correct order in a new sorted array. This is an in-place algorithm. An even easier way to write it, would be to use an out of place algorithm and create another array like this:

class InsertionSort
	def sort_out_of_place(to_sort)
		sorted = []
		
		to_sort.each do |element| 
			for index in 0..(to_sort.length - 1)
				sorted.insert(index, element) if to_sort[index] > element
			end
		end		
		
		return to_sort	 
	end
end

Wow, that one was easy. But now let’s try the slightly harder in-place version. This version works on the array to be sorted rather than create a new array.

def sort_in_place(to_sort)
		# index starts at one, we can skip the first element, since we would
		# otherwise take it and place it in the first position, which it already is
		for index in 1..(to_sort.length - 1)
			for inner_index in 0..(index - 1)
				if to_sort[inner_index] >= to_sort[index]
					to_sort.insert(inner_index, to_sort[index])
					to_sort.delete_at(index + 1)
				end
			end
		end
		
		return to_sort
	end

Ok, so it’s not that much more complicated. That’s because I used Ruby’s insert and delete_at. Normally in my inner loop I would have had to shift (move) manually all the elements in-place to put my new element to be sorted in the right position thus repositioning many elements.

What this means is that my implementation is really only truly in-place if Ruby’s insert and delete_at are implemented in-place themselves. The logic of the algorithm can be considered in-place if we consider the two methods as abstractions.

Either way, this is a more faithful implementation of the original algorithm than the first out of place version. Doing it in-place uses less memory than the out of place version but is slightly harder to code and understand.

As for the complexity it’s a O(n2) algorithm, but it is considered more efficient in practice than Selection sort and Bubble sort.

Let’s move on to a more efficient algorithm.

Merge sort

Because recursion kicks ass!

Now we are getting at more interesting and more efficient algorithms. Merge sort is an O (n log n) algorithm. This is a lot better than the previous algorithms. It’s also the first to use recursion thus using a divide and conquer approach.

I have commented the code, and will let these comments and the source code do most of the talking.

lass MergeSort
	def sort(to_sort)
		# if the array is of length 0 or 1, consider it is already sorted
		if to_sort.length <= 1 
			then return to_sort 
		end
		
		# otherwise split the remaining elements in two
		# I had to look this line on the web (source refactormycode.com)
		second_array = to_sort.slice!((to_sort.length / 2.0).round..to_sort.length)
				
		# recursive method call on both arrays
		first_sorted_array = sort(to_sort)
		second_sorted_array = sort(second_array)
		
		# merge the two sorted arrays together
		return merge(first_sorted_array, second_sorted_array)
	end
	
private
	def merge(first_array, second_array)
		# if either array is empty consider the other already sorted
		return second_array if first_array.empty?
		return first_array if second_array.empty?
		
		# remove the smallest element out of the two arrays
		if first_array.first <= second_array.first 
			element = first_array.shift
		else
			element = second_array.shift
		end		
	
		# recursive call to construct the merged array	
		return [element] + merge(first_array, second_array)
	end
end

To implement Merge sort, you also need to implement a merge function. This function will pick values from two arrays and construct a merged sorted array. The two arrays used for input are considered already sorted. The job of the merge function is then to look at the first element of each of these arrays and find the smallest.

ie:

[1, 4, 7, 9] and [2, 5, 6]
[]

In this case the merge function would look at 1 and 2. Select 1 as the smallest value and remove it from the first array. The value would then be used as the first value of the new merged array.

[4, 7, 9] and [2, 5, 6]
[1]

The process then repeats itself until there are no more elements in either arrays.

For your merge function you can use an iterative approach. In such an approach you loop, taking elements from the two arrays to be merged and placing them in a merged array, until you have no more elements left to take from the original arrays.

Since Merge sort is a recursive algorithm, I also went with a recursive merge function.

When I write a recursive function, I always start by defining the condition(s) that will end the recursion and then consider what I need to do to get to this condition.

In this case, we will end the recursion when one or our arrays to be merged is empty. If this is the case, we can return the other array as there are still values in it to be merged.

If we are not facing an empty array, we simply need to look at the first value from each array, remove it and return it along with a recursive call to merge.

This merge function required more thinking than the actual sort function which just keeps splitting the array in two, calling sort recursively on both of the new arrays.

Until next time…

This is it for part 1. Stay tuned for part 2 where I will look into more sorting algorithms in Ruby, more specifically heapsort and quicksort.

Sorting algorithms in Ruby, interlude
Sorting algorithms in Ruby, Part 2
Sorting algorithms in Ruby, Part 3

One thought on “Sorting algorithms in Ruby, Part 1

Leave a comment