F# Program to Implement Tim Sort

F# Program to Implement Tim Sort

Sorting is one of the most important tasks in programming. It helps us organize data so it can be searched, processed, or displayed efficiently. Tim Sort is a highly practical sorting algorithm that combines the strengths of Insertion Sort and Merge Sort. It is designed to be fast on real-world data and is widely used in programming languages like Python and Java for their built-in sort functions.

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 works by dividing the array into small chunks called “runs” and sorting each run with a simple algorithm like Insertion Sort. Then, it merges these runs efficiently using a method similar to Merge Sort. This hybrid approach makes Tim Sort particularly effective for datasets that already have some order, which is common in real-world applications like sorting user lists, scores, or dates. For beginners, implementing Tim Sort in F# provides a great way to explore sorting, recursion, and efficient merging.

Program 1: Basic Tim Sort Using Arrays and Runs

This program demonstrates a simple Tim Sort for integer arrays. It sorts small chunks with Insertion Sort and then merges them into a fully sorted array.

open System

let minRun = 32

let insertionSort (arr:int[]) left right =

    for i in left + 1 .. right do

        let key = arr.[i]
        let mutable j = i - 1

        while j >= left && arr.[j] > key do

            arr.[j + 1] <- arr.[j]
            j <- j - 1

        arr.[j + 1] <- key


let merge (arr:int[]) l m r =

    let len1 = m - l + 1
    let len2 = r - m
    let left = Array.init len1 (fun i -> arr.[l + i])
    let right = Array.init len2 (fun i -> arr.[m + 1 + i])

    let mutable i = 0
    let mutable j = 0
    let mutable k = l

    while i < len1 && j < len2 do

        if left.[i] <= right.[j] then
            arr.[k] <- left.[i]
            i <- i + 1
        else
            arr.[k] <- right.[j]
            j <- j + 1

        k <- k + 1


    while i < len1 do

        arr.[k] <- left.[i]
        i <- i + 1
        k <- k + 1


    while j < len2 do

        arr.[k] <- right.[j]
        j <- j + 1
        k <- k + 1


let timSort (arr:int[]) =

    let n = arr.Length

    for i in 0 .. minRun .. n - 1 do

        let right = Math.Min(i + minRun - 1, n - 1)
        insertionSort arr i right

    let mutable size = minRun

    while size < n do

        let mutable left = 0

        while left < n do

            let mid = Math.Min(left + size - 1, n - 1)
            let right = Math.Min((left + 2 * size - 1), n - 1)
            if mid < right then
                merge arr left mid right
            left <- left + 2 * size

        size <- size * 2


[<EntryPoint>]
let main argv =

    let numbers = [| 5; 21; 7; 23; 19; 1; 15; 3 |]

    timSort numbers
    numbers |> Array.iter (printf "%d ")

    0

This program works by sorting small runs first using Insertion Sort, which is very efficient for small arrays. Then, it merges these runs progressively to create a fully sorted array. Beginners can follow each step to see how small sorted chunks gradually combine into a complete sort.

Program 2: Tim Sort with Step-by-Step Printing

This version prints intermediate steps during the merge process. It is ideal for beginners who want to visualize how Tim Sort works internally.

open System

let minRun = 32

let insertionSortStep (arr:int[]) left right =

    for i in left + 1 .. right do

        let key = arr.[i]
        let mutable j = i - 1

        while j >= left && arr.[j] > key do

            arr.[j + 1] <- arr.[j]
            j <- j - 1

        arr.[j + 1] <- key

    printfn "After insertion sort from %d to %d: %A" left right arr


let mergeStep (arr:int[]) l m r =

    let len1 = m - l + 1
    let len2 = r - m
    let left = Array.init len1 (fun i -> arr.[l + i])
    let right = Array.init len2 (fun i -> arr.[m + 1 + i])

    let mutable i = 0
    let mutable j = 0
    let mutable k = l

    while i < len1 && j < len2 do

        if left.[i] <= right.[j] then
            arr.[k] <- left.[i]
            i <- i + 1
        else
            arr.[k] <- right.[j]
            j <- j + 1

        k <- k + 1


    while i < len1 do

        arr.[k] <- left.[i]
        i <- i + 1
        k <- k + 1


    while j < len2 do

        arr.[k] <- right.[j]
        j <- j + 1
        k <- k + 1

    printfn "After merging from %d to %d: %A" l r arr


let timSortStep (arr:int[]) =

    let n = arr.Length

    for i in 0 .. minRun .. n - 1 do

        let right = Math.Min(i + minRun - 1, n - 1)
        insertionSortStep arr i right

    let mutable size = minRun

    while size < n do

        let mutable left = 0

        while left < n do

            let mid = Math.Min(left + size - 1, n - 1)
            let right = Math.Min((left + 2 * size - 1), n - 1)
            if mid < right then
                mergeStep arr left mid right
            left <- left + 2 * size

        size <- size * 2


[<EntryPoint>]
let main argv =

    let numbers = [| 12; 6; 7; 5; 3; 0; 11; 8 |]

    timSortStep numbers
    printfn "Final sorted array: %A" numbers

    0

Step-by-step printing helps beginners see how Tim Sort first organizes small runs and then merges them in stages. It builds intuition on why merging sorted chunks is faster than sorting the entire array at once.

Program 3: Tim Sort Using Lists Instead of Arrays

This program demonstrates Tim Sort using F# lists for a functional programming approach. It emphasizes immutability while still applying the same run-and-merge logic.

open System

let insertionSortList (lst : int list) =

    let rec insert x sorted =
        match sorted with
        | [] -> [x]
        | h :: t when x <= h -> x :: sorted
        | h :: t -> h :: insert x t

    List.fold (fun acc x -> insert x acc) [] lst


let rec mergeList l1 l2 =
    match l1, l2 with
    | [], l | l, [] -> l
    | h1 :: t1, h2 :: t2 ->
        if h1 <= h2 then h1 :: mergeList t1 l2
        else h2 :: mergeList l1 t2


let timSortList (lst : int list) =

    if List.isEmpty lst then []
    else
        let minRun = 32

        let rec splitRuns remaining runs =
            match remaining with
            | [] -> List.rev runs
            | _ ->
                let size = min minRun (List.length remaining)
                let run, rest = List.splitAt size remaining
                splitRuns rest (insertionSortList run :: runs)

        let runs = splitRuns lst []

        let rec mergeAll rs =
            match rs with
            | [] -> []
            | [r] -> r
            | r1 :: r2 :: rest ->
                mergeAll (mergeList r1 r2 :: rest)

        mergeAll runs


[<EntryPoint>]
let main argv =

    let numbers = [12; 5; 7; 3; 19; 1; 4; 8]
    let sorted = timSortList numbers

    printfn "Sorted list:"
    sorted |> List.iter (printf "%d ")

    0

Using lists shows a functional approach where the original array is not modified. Beginners can learn how recursion and higher-order functions replace loops in F#.

Program 4: Tim Sort with Generic Data Types

This program generalizes Tim Sort for any comparable type, making it reusable for integers, floats, or strings.

open System

let insertionSortGeneric (arr:'a[]) left right =

    for i in left + 1 .. right do

        let key = arr.[i]
        let mutable j = i - 1

        while j >= left && arr.[j] > key do

            arr.[j + 1] <- arr.[j]
            j <- j - 1

        arr.[j + 1] <- key


let mergeGeneric (arr:'a[]) l m r =

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

    while i < left.Length && j < right.Length do

        if left.[i] <= right.[j] then
            arr.[k] <- left.[i]
            i <- i + 1
        else
            arr.[k] <- right.[j]
            j <- j + 1

        k <- k + 1

    while i < left.Length do

        arr.[k] <- left.[i]
        i <- i + 1
        k <- k + 1

    while j < right.Length do

        arr.[k] <- right.[j]
        j <- j + 1
        k <- k + 1


let timSortGeneric (arr:'a[]) =

    let n = arr.Length
    let minRun = 32

    for i in 0 .. minRun .. n - 1 do

        let right = Math.Min(i + minRun - 1, n - 1)
        insertionSortGeneric arr i right

    let mutable size = minRun

    while size < n do

        let mutable left = 0

        while left < n do

            let mid = Math.Min(left + size - 1, n - 1)
            let right = Math.Min(left + 2*size - 1, n - 1)

            if mid < right then
                mergeGeneric arr left mid right

            left <- left + 2*size

        size <- size * 2


[<EntryPoint>]
let main argv =

    let numbers = [| "pear"; "apple"; "orange"; "banana" |]
    timSortGeneric numbers
    numbers |> Array.iter (printf "%s ")

    0

This program is useful for beginners because it shows that Tim Sort is not limited to numbers. Strings, dates, or any comparable data can be sorted with the same logic.

Program 5: Tim Sort with Custom Min Run

This version allows the user to define the minimum run size, which can impact performance. It is useful for experimenting with optimization.

open System

let insertionSortMinRun (arr:int[]) left right =

    for i in left + 1 .. right do

        let key = arr.[i]
        let mutable j = i - 1

        while j >= left && arr.[j] > key do

            arr.[j + 1] <- arr.[j]
            j <- j - 1

        arr.[j + 1] <- key


let mergeMinRun (arr:int[]) l m r =

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

    while i < left.Length && j < right.Length do

        if left.[i] <= right.[j] then
            arr.[k] <- left.[i]
            i <- i + 1
        else
            arr.[k] <- right.[j]
            j <- j + 1

        k <- k + 1

    while i < left.Length do

        arr.[k] <- left.[i]
        i <- i + 1
        k <- k + 1

    while j < right.Length do

        arr.[k] <- right.[j]
        j <- j + 1
        k <- k + 1

let timSortMinRun (arr:int[]) minRun =

    let n = arr.Length

    for i in 0 .. minRun .. n - 1 do

        let right = Math.Min(i + minRun - 1, n - 1)
        insertionSortMinRun arr i right

    let mutable size = minRun

    while size < n do

        let mutable left = 0

        while left < n do

            let mid = Math.Min(left + size - 1, n - 1)
            let right = Math.Min(left + 2*size - 1, n - 1)

            if mid < right then
                mergeMinRun arr left mid right

            left <- left + 2*size

        size <- size * 2


[<EntryPoint>]
let main argv =

    let numbers = [| 12; 4; 5; 3; 8; 7; 6 |]
    timSortMinRun numbers 4
    numbers |> Array.iter (printf "%d ")

    0

Custom min run size allows beginners to experiment and see how small changes affect the algorithm’s efficiency. It also demonstrates that Tim Sort can be tuned for different datasets.

Frequently Asked Questions (FAQ)

This section answers common beginner questions about Tim Sort in simple terms.

Q1. What makes Tim Sort different from other sorting algorithms?
Tim Sort combines Insertion Sort and Merge Sort, making it efficient for real-world data that is partially ordered.

Q2. Can Tim Sort handle negative numbers and decimals?
Yes, as long as the data can be compared, Tim Sort works for negative numbers, decimals, and even strings.

Q3. Is Tim Sort stable?
Yes, Tim Sort preserves the relative order of equal elements, which is important for certain datasets.

Q4. How does the minimum run size affect performance?
A smaller run size increases the number of merges, while a larger run size may make insertion sorting less efficient. Finding the right balance improves speed.

Q5. Should beginners learn Tim Sort?
Yes, it teaches important algorithm design concepts like hybrid sorting, runs, and efficient merging, which are useful for advanced programming.

Conclusion

Tim Sort is a practical and efficient sorting algorithm widely used in modern programming. Through multiple F# programs, we explored classic Tim Sort with arrays, step-by-step visualization, functional list-based implementation, generic types, and customizable min run size.

By practicing these examples, beginners can learn how hybrid algorithms work, how merging small sorted chunks can optimize performance, and how to apply these concepts to real-world datasets. Tim Sort not only sorts efficiently but also builds a deeper understanding of algorithmic thinking in F#.

Scroll to Top