A Shell Sort Implementation in Ruby

This algorithm expands on concepts derived from Insertion Sort, and effectively uses those strategies by optimizing and consolidating swap operations per scan. Wikipedia offers some great background information for Shell Sort.

The example below is coded to the "poor" implementation of Shell Sort which operates in O(N2) time1, and room for improvements exist by optimizing the gap sequence; N/2 is the gap strategy used here. An effective gap strategy can lend a O(N3/2) down to O(N4/3) runtime.

  1. def shell_sort (list)
  2. gap = list.length/2
  3. while(gap > 0)
  4. list[gap..list.length-1].each_with_index do |element, index|
  5. j = (index += gap)
  6. index.step(gap-1, -gap) do |j|
  7. if ( j > 0 && j >= gap && element < list[j-gap] ) then
  8. list[j] = list[j-gap]
  9. else
  10. break
  11. end
  12. end
  13. list[j] = element
  14. end
  15. gap /= 2
  16. end
  17. list
  18. end



Elements that are a "gap" apart, are compared and sorted; as the gap diminishes over each repitition, elements which are closest together are eventually compared.

Shell Sort Example 1
Gap Array Elements
input 81 94 11 96 12 35 17 95 28 58 41 75 15
5 35 17 11 28 12 41 75 15 96 58 81 94 95
3 28 12 11 35 15 41 58 17 94 75 81 96 95
1 11 12 15 17 28 35 41 58 75 81 94 95 96



Code Review
Start with a gap of N/2, and iterate until adjacent elements have been processed

  1. def shell_sort (list)
  2. gap = list.length/2
  3. while(gap > 0)

Currently Ruby does not support an iterator which starts at a given index, so we will use the standard array API to obtain a sub-list which starts from "gap" and ends at the last element. We then iterate over that sub-list, and obtain each element and index.

  1. list[gap..list.length-1].each_with_index do |element, index|

The sublist is returned as a seperate object, and therefore it's index starts at "0"; we must compensate for this by adjusting "index" to "index + gap". Additionally, we introduce "j" here so that we can use it outside of the upcoming loop; note, it's value at this point in the sequence is arbitrary and has no meaning.

  1. j = (index += gap)

We then take advantage of Ruby's "step" construct for integers, to loop using a custom step sequence of "-gap", starting at "gap-1". As we scan down the list, we essentially compare and swap elements, in a similar fashion to Insertion Sort.

  1. index.step(gap-1, -gap) do |j|
  2. if ( j > 0 && j >= gap && element < list[j-gap] ) then
  3. list[j] = list[j-gap]
  4. else
  5. break
  6. end
  7. end
  8. list[j] = element

I offer the same caveat as with the Insertion Sort tutorial; I attempted rubify the code, and therefore this implementation may not be as efficient as possible (e.g. use of "step" versus other loop tactics, the use of Ruby's sub-array functionality, having to re-adjust the index, etc..). However, I hope this provides some valuable insight into both Shell Sort and Ruby.

References
1. Weiss, Mark Allen (1999). "Data Structures and Algorithm Analysis in Java". Addison Wesley. ISBN 0201357542.
2. "Shell Sort". Wikipedia. http://en.wikipedia.org/wiki/Shell_sort

An Insertion Sort Implementation in Ruby

Ruby, which is progressively gaining popularity in the industry, has struck an interest with me, and I've decided to take up the task of learning the language. Although I was first introduced to Ruby back in 2002, I never took the time to really get to know it.

Fast forward 6yrs later, and I've decided to get serious about it, and what better way to learn than to get in there and code up some common algorithms. As I venture on this journey, I will document my experience in efforts to help out others who are learning as well.

For this first example, I've coded up the simple "Insertion Sort" algorithm. Please note that in my attempts to "rubify" the algorithm, it is highly likely I may not have written it efficiently.

  1. def insertion_sort (list)
  2. list.each_with_index do |element, index|
  3. index.downto(0) do |j|
  4. if (j > 0 && element < list[j-1]) then
  5. list[j] = list[j-1]
  6. else
  7. list[j] = element
  8. break
  9. end
  10. end
  11. end
  12. end

Insertion sort operates on the basis that elements are sorted progressively as the code scans the list, and therefore after each scan S, elements 0 through S-1 are assumed to be in order, and element S is moved towards the left until it is in order.

Insertion Sort Example 1
Scan Array Elements
input 34 8 64 51 32 21
1 8 34 64 51 32 21
2 8 34 64 51 32 21
3 8 34 51 64 32 21
4 8 32 34 51 64 21
5 8 21 32 34 51 64



Code Walkthrough
The following lines uses the standard list iterator API to obtain both the element and it's index, starting at the beginning of the list.

  1. list.each_with_index do |element, index|

The inner loop uses the "downto" construct to start at the current index (from the outer loop), and counts backward to "0". Note that "downto" does not alter the value of "index". This loop does the work of comparing and swaping the current element with it's predecessors, and will appropriately shift "greater" ones up the list.

  1. index.downto(0) do |j|

The following code segment defines the comparison logic in order to shift the element down the list, by moving all "greater" elements up the list. Once an appropriate location has been determined, the current element is inserted back into the list.

  1. if (j > 0 && element < list[j-1]) then
  2. list[j] = list[j-1]
  3. else
  4. list[j] = element
  5. break
  6. end

One very important point to note is that array boundaries are somewhat loose in Ruby, and therefore an index of "-1" is considered legitimate and will not produce an array/list access violation -- it would point to the last element in the list. Therefore, in the core logic presented above, we account for that by detecting when "j" is no longer greater than "0" in our conditional statement. Regardless, in order to properly implement "insertion sort", strict boundaries are needed.


Additionally, due to Ruby's OO nature, the code presented above will work with any list of classes that support the "<" comparison operator. With that being said, Ruby also makes it possible to have lists that contain a heterogeneous set of classes since there is a lack of type-safety -- this can be both an advantage or disadvantage depending on how you intend to use this algorithm. If you're using a mixed set of classes, you would have to ensure that there is some logical ordering defined within your classes to satisfy the comparison methods.


I hope this tutorial has been helpful!

References
1. Weiss, Mark Allen (1999). "Data Structures and Algorithm Analysis in Java". Addison Wesley. ISBN 0201357542


(c) 2009 sheel kapur / powered by WordPress