F# — Program.fs
F# — Randomized Quicksort (HW3)
Toolchain
- .NET SDK 10.0.400 (confirmed via
dotnet --versioninside WSL Ubuntu) - Target framework:
net10.0 - Language: F# (
-lang F#console template) - Run inside WSL2 Ubuntu per the shared
HW3/DESIGN.mdprotocol.
Files
fsharp.fsproj— generated bydotnet new console -lang F# -o .Program.fs— Randomized Quicksort implementation + timing harness.
Setup (one-time)
wsl.exe -d Ubuntu -- bash -c "cd '/mnt/c/Users/granite/Documents/DSU/Algorithms 482/3/HW3/fsharp' && dotnet new console -lang F# -o . --force"
This scaffolds fsharp.fsproj and Program.fs (subsequently replaced with the
real implementation).
Build (Release)
wsl.exe -d Ubuntu -- bash -c "cd '/mnt/c/Users/granite/Documents/DSU/Algorithms 482/3/HW3/fsharp' && dotnet build -c Release -o bin/publish"
This produces bin/publish/fsharp.dll (a native apphost bin/publish/fsharp is also
produced, but per the C# sibling's finding, the apphost doesn't reliably resolve the
.NET runtime in this WSL install since dotnet lives under /home/granite/.dotnet
rather than the apphost's default search location; launching the managed DLL through
the dotnet muxer is used instead, and is the same Release build/JIT behavior either
way — only the launch mechanism differs).
Run
wsl.exe -d Ubuntu -- bash -c "cd '/mnt/c/Users/granite/Documents/DSU/Algorithms 482/3/HW3/fsharp' && dotnet ./bin/publish/fsharp.dll"
Output: prints n,time_seconds rows to stdout as it runs, and writes the same rows to
HW3/results/fsharp.csv (path resolved relative to the running binary by walking up
from AppContext.BaseDirectory to find the shared HW3/results/ folder, regardless of
the working directory the binary is launched from).
Notes
- No NuGet packages used — .NET base class library only (
System,System.Diagnostics,System.IO). System.Randomis used both for test-array generation and for the uniformly random pivot index inRANDOMIZED-PARTITION.- The sort operates in place on a mutable
int[](viaArray.init/index mutation), which is the normal, idiomatic approach for a performance-sensitive in-place algorithm in F# even though F# is functional-first — this mirrors CLRS's own in-place pseudocode rather than building a new immutable array at each partition step (which would change both the algorithm's complexity class and its memory behavior).
// HW3 - CSC 482 Language Comparison
// Randomized Quicksort in F#, following CLRS Ch. 5 RANDOMIZED-QUICKSORT /
// RANDOMIZED-PARTITION pseudocode (Cormen, Leiserson, Rivest & Stein,
// "Introduction to Algorithms"). Original implementation - no external
// code copied. In-place Lomuto partition operating on a mutable int[]
// (the standard, idiomatic approach for this kind of performance-sensitive
// in-place algorithm even in a functional-first language like F#), with a
// uniformly random pivot chosen (and swapped into the last position of the
// current subarray) at every partition step, using System.Random for both
// pivot selection and test-data generation, per DESIGN.md.
open System
open System.Diagnostics
open System.IO
// Single shared RNG used both for generating test data and for randomized
// pivot selection.
let rng = Random()
/// Swap two elements of a mutable int array in place.
let inline swap (a: int[]) (i: int) (j: int) =
let tmp = a.[i]
a.[i] <- a.[j]
a.[j] <- tmp
/// CLRS PARTITION(A, p, r) - Lomuto partition scheme.
let partition (a: int[]) (p: int) (r: int) : int =
let x = a.[r]
let mutable i = p - 1
for j in p .. r - 1 do
if a.[j] <= x then
i <- i + 1
swap a i j
swap a (i + 1) r
i + 1
/// CLRS RANDOMIZED-PARTITION(A, p, r): pick a uniformly random index in
/// [p, r], swap it into the last position, then run the standard Lomuto
/// PARTITION.
let randomizedPartition (a: int[]) (p: int) (r: int) : int =
let i = rng.Next(p, r + 1) // uniform over [p, r], Next's upper bound is exclusive
swap a i r
partition a p r
/// CLRS RANDOMIZED-QUICKSORT(A, p, r)
let rec randomizedQuicksort (a: int[]) (p: int) (r: int) : unit =
if p < r then
let q = randomizedPartition a p r
randomizedQuicksort a p (q - 1)
randomizedQuicksort a (q + 1) r
/// Generate a fresh array of n random ints in [minInclusive, maxExclusive).
let generateRandomArray (n: int) (minInclusive: int) (maxExclusive: int) : int[] =
Array.init n (fun _ -> rng.Next(minInclusive, maxExclusive))
/// Resolve HW3/results/fsharp.csv relative to the running binary's location,
/// walking up from AppContext.BaseDirectory (e.g. fsharp/bin/publish/) until
/// a sibling "results" directory (or "fsharp" directory, i.e. the HW3 root)
/// is found, so the CSV lands in the shared results/ folder regardless of
/// the working directory the binary is launched from.
let resolveResultsPath () : string =
let rec walkUp (dir: string) (attemptsLeft: int) : string option =
if attemptsLeft <= 0 then
None
else
let candidate = Path.Combine(dir, "results")
let fsharpSibling = Path.Combine(dir, "fsharp")
if Directory.Exists candidate then
Some(Path.Combine(candidate, "fsharp.csv"))
elif Directory.Exists fsharpSibling then
let resultsDir = Path.Combine(dir, "results")
Directory.CreateDirectory(resultsDir) |> ignore
Some(Path.Combine(resultsDir, "fsharp.csv"))
else
match Directory.GetParent(dir) with
| null -> None
| parent -> walkUp parent.FullName (attemptsLeft - 1)
match walkUp AppContext.BaseDirectory 10 with
| Some p -> p
| None ->
let fallbackDir = Path.Combine(Directory.GetCurrentDirectory(), "..", "results")
Directory.CreateDirectory(fallbackDir) |> ignore
Path.Combine(fallbackDir, "fsharp.csv")
[<EntryPoint>]
let main _argv =
let startN = 1000L
let maxIterations = 25
let nCap = 200_000_000L
let timeCutoffSeconds = 4.0
let resultsPath = resolveResultsPath ()
printfn "n,time_seconds"
use writer = new StreamWriter(resultsPath, false)
writer.WriteLine("n,time_seconds")
let mutable n = startN
let mutable iteration = 0
let mutable keepGoing = true
while keepGoing do
iteration <- iteration + 1
let data = generateRandomArray (int n) 0 1_000_000
let sw = Stopwatch.StartNew()
randomizedQuicksort data 0 (data.Length - 1)
sw.Stop()
let elapsedSeconds = sw.Elapsed.TotalSeconds
let line = sprintf "%d,%s" n (elapsedSeconds.ToString("R"))
printfn "%s" line
writer.WriteLine(line)
writer.Flush()
let timeExceeded = elapsedSeconds > timeCutoffSeconds
let iterationsExhausted = iteration >= maxIterations
let nextN = n * 2L
let nextExceedsCap = nextN > nCap
if timeExceeded || iterationsExhausted || nextExceedsCap then
keepGoing <- false
else
n <- nextN
printfn "Done. Results written to %s" resultsPath
0