Ruby Program to Implement Tim Sort

Ruby Program to Implement Tim Sort

Sorting is a fundamental concept in programming because it allows us to organize data efficiently, making it easier to search, analyze, and manipulate. One of the most powerful and practical sorting algorithms used in modern programming is Tim Sort. Tim Sort is a hybrid algorithm that combines the principles of Merge Sort and Insertion Sort. It was designed to take advantage of partially ordered datasets, making it very efficient for real-world data that often contains already sorted sequences.

Pluralsight Logo
Accelerate your tech career
with hands-on learning.
Whether you're a tech newbie or a total pro,
get the skills and confidence to land your next move.
Start 10-Day Free Trial

Tim Sort is widely used in programming languages like Python and Java for their default sorting functions because it is stable, adaptive, and performs exceptionally well on practical datasets. Learning Tim Sort in Ruby helps beginners understand how combining simple algorithms can lead to high performance and efficiency. In this article, we will explore multiple ways to implement Tim Sort in Ruby, covering loop-based, recursive, and optimized approaches. By the end, beginners will have a clear understanding of how Tim Sort works and how to apply it effectively in Ruby.

Program 1: Tim Sort Using Loops and Merge Logic

This first program demonstrates a basic Tim Sort implementation using loops. It sorts a predefined array of integers by breaking it into small runs, sorting them using Insertion Sort, and merging them.

# Program 1: Tim Sort using loops
RUN = 32

def insertion_sort(arr, left, right)

  (left + 1..right).each do |i|

    temp = arr[i]
    j = i - 1

    while j >= left && arr[j] > temp

      arr[j + 1] = arr[j]
      j -= 1

    end

    arr[j + 1] = temp

  end

end

def merge(arr, l, m, r)

  left = arr[l..m]
  right = arr[m + 1..r]
  i = 0
  j = 0
  k = l

  while i < left.length && j < right.length

    if left[i] <= right[j]

      arr[k] = left[i]
      i += 1

    else

      arr[k] = right[j]
      j += 1

    end

    k += 1

  end

  while i < left.length

    arr[k] = left[i]
    i += 1
    k += 1

  end

  while j < right.length

    arr[k] = right[j]
    j += 1
    k += 1

  end

end

def tim_sort(arr)

  n = arr.length

  # Sort small runs using insertion sort
  (0...n).step(RUN) do |i|
    insertion_sort(arr, i, [i + RUN - 1, n - 1].min)
  end

  size = RUN

  while size < n

    (0...n).step(2 * size) do |left|

      mid = left + size - 1
      right = [left + 2 * size - 1, n - 1].min

      merge(arr, left, mid, right) if mid < right

    end

    size *= 2

  end

  arr

end

numbers = [5, 21, 7, 23, 19, 1, 12, 3]

sorted_array = tim_sort(numbers)

puts "Sorted array: #{sorted_array}"

In this program, the array is first divided into small runs of size 32 (a typical choice in Tim Sort). Each run is sorted using Insertion Sort, which is efficient for small sequences. Then, the runs are merged iteratively using merge logic. Beginners can see how combining simple sorting techniques results in a fast and adaptive algorithm suitable for real-world data.

Program 2: Tim Sort for Floating-Point Numbers

Tim Sort is versatile and can handle floating-point numbers easily. This example demonstrates sorting a predefined array of floats using the same logic.

# Program 2: Tim Sort for floating-point numbers
RUN = 32

def insertion_sort(arr, left, right)

  (left + 1..right).each do |i|

    temp = arr[i]
    j = i - 1

    while j >= left && arr[j] > temp

      arr[j + 1] = arr[j]
      j -= 1

    end

    arr[j + 1] = temp

  end

end

def merge(arr, l, m, r)

  left = arr[l..m]
  right = arr[m + 1..r]
  i = 0
  j = 0
  k = l

  while i < left.length && j < right.length

    if left[i] <= right[j]

      arr[k] = left[i]
      i += 1

    else

      arr[k] = right[j]
      j += 1

    end

    k += 1

  end

  while i < left.length

    arr[k] = left[i]
    i += 1
    k += 1

  end

  while j < right.length

    arr[k] = right[j]
    j += 1
    k += 1

  end

end

def tim_sort(arr)

  n = arr.length

  # Sort small runs using insertion sort
  (0...n).step(RUN) do |i|
    insertion_sort(arr, i, [i + RUN - 1, n - 1].min)
  end

  size = RUN

  while size < n

    (0...n).step(2 * size) do |left|

      mid = left + size - 1
      right = [left + 2 * size - 1, n - 1].min

      merge(arr, left, mid, right) if mid < right

    end

    size *= 2

  end

  arr

end

numbers = [3.4, 1.2, 7.8, 0.5, 2.3, 5.6]

sorted_array = tim_sort(numbers)

puts "Sorted floats: #{sorted_array}"

The sorting process remains unchanged because Tim Sort relies on comparisons, not the data type. Beginners can understand that Tim Sort’s flexibility allows it to sort both integers and decimal numbers efficiently.

Program 3: Recursive Tim Sort

Tim Sort can also be implemented using recursion, particularly in the merging step. This approach highlights the power of recursion in sorting algorithms.

# Program 3: Recursive Tim Sort merge
def recursive_merge_sort(arr)

  return arr if arr.length <= 1

  mid = arr.length / 2

  left = recursive_merge_sort(arr[0...mid])
  right = recursive_merge_sort(arr[mid..-1])

  merge(left, right)

end

def merge(left, right)

  result = []
  i = j = 0

  while i < left.length && j < right.length

    if left[i] <= right[j]

      result << left[i]
      i += 1

    else

      result << right[j]
      j += 1

    end

  end

  result.concat(left[i..-1]) if i < left.length
  result.concat(right[j..-1]) if j < right.length

  result

end

numbers = [8, 3, 7, 4, 1, 6, 5, 2]

sorted_array = recursive_merge_sort(numbers)

puts "Sorted array (recursive): #{sorted_array}"

In this recursive version, the array is divided until each subarray has one element, then merged back in order. Beginners can see how recursion simplifies the merging logic and how it forms the backbone of Tim Sort’s adaptive behavior.

Program 4: Tim Sort Optimized with Smaller Runs

Tim Sort performs better if the run size is adjusted dynamically. This example demonstrates using smaller runs for datasets with many small ordered sequences.

# Program 4: Tim Sort with smaller runs
RUN = 16

def insertion_sort(arr, left, right)

  (left + 1..right).each do |i|

    temp = arr[i]
    j = i - 1

    while j >= left && arr[j] > temp

      arr[j + 1] = arr[j]
      j -= 1

    end

    arr[j + 1] = temp

  end

end

def merge(arr, l, m, r)

  left = arr[l..m]
  right = arr[m + 1..r]
  i = 0
  j = 0
  k = l

  while i < left.length && j < right.length

    if left[i] <= right[j]

      arr[k] = left[i]
      i += 1

    else

      arr[k] = right[j]
      j += 1

    end

    k += 1

  end

  while i < left.length

    arr[k] = left[i]
    i += 1
    k += 1

  end

  while j < right.length

    arr[k] = right[j]
    j += 1
    k += 1

  end

end

def tim_sort(arr)

  n = arr.length

  # Sort small runs using insertion sort
  (0...n).step(RUN) do |i|
    insertion_sort(arr, i, [i + RUN - 1, n - 1].min)
  end

  size = RUN

  while size < n

    (0...n).step(2 * size) do |left|

      mid = left + size - 1
      right = [left + 2 * size - 1, n - 1].min

      merge(arr, left, mid, right) if mid < right

    end

    size *= 2

  end

  arr

end

numbers = [22, 11, 99, 88, 9, 7, 42, 0, 5]

sorted_array = tim_sort(numbers)

puts "Optimized Tim Sort array: #{sorted_array}"

By reducing the run size from 32 to 16, small pre-sorted segments are leveraged more effectively. Beginners can observe how adjusting parameters can optimize performance for specific datasets while keeping the core logic simple.

Frequently Asked Questions (FAQ)

Tim Sort is powerful, but beginners often have questions. Here are some common queries:

Q1: What is the time complexity of Tim Sort?
Tim Sort has a worst-case time complexity of O(n log n) and performs very well on partially sorted datasets, often close to O(n).

Q2: Is Tim Sort stable?
Yes, Tim Sort is stable, which means the relative order of equal elements is preserved.

Q3: Can Tim Sort handle different types of numbers?
Yes, it can handle integers, floating-point numbers, and even custom objects if a comparison method is provided.

Q4: Why is Tim Sort better than Merge Sort or Insertion Sort alone?
Tim Sort combines the efficiency of Insertion Sort for small runs with Merge Sort for larger merges. This makes it adaptive, fast, and efficient for real-world data.

Q5: When should I use Tim Sort?
Tim Sort is ideal for practical datasets that are partially sorted or contain small runs. It is widely used in programming languages like Python and Java for default sorting because of its stability and speed.

Conclusion

In this article, we explored how to implement Tim Sort in Ruby using loops, recursion, and optimized run sizes. Tim Sort is a hybrid sorting algorithm that leverages the simplicity of Insertion Sort for small sequences and the power of Merge Sort for merging. By practicing these examples, beginners can understand adaptive sorting, merging logic, and parameter optimization. Tim Sort is stable, versatile, and performs exceptionally well on real-world datasets, making it a valuable addition to any programmer’s toolkit.

Scroll to Top