Yog.FSharp
Getting Started Examples API Reference GitHub

Task Scheduling with Topological Sort

This example demonstrates using topological sorting to determine the correct order to execute tasks with dependencies. A common problem in build systems, project management, and workflow automation.

Problem

Given a set of tasks where some must be completed before others, determine a valid execution order. If there's a circular dependency, detect it.

#I "../../src/Yog.FSharp/bin/Debug/net10.0"
#r "Yog.FSharp.dll"

open Yog.Model
open Yog.Traversal

Modeling Tasks as a DAG

We model tasks as a Directed Acyclic Graph (DAG) where: - Nodes represent tasks - Edges represent dependencies (task A must complete before task B)

let tasks =
    empty Directed
    |> addNode 1 "Design"
    |> addNode 2 "Implement"
    |> addNode 3 "Test"
    |> addNode 4 "Deploy"
    |> addEdge 1 2 ()  // Design before Implement
    |> addEdge 2 3 ()  // Implement before Test
    |> addEdge 3 4 ()  // Test before Deploy

Finding Valid Execution Order

Use topological sort to determine the order:

match topologicalSort tasks with
| Ok order ->
    printfn "=== Task Execution Order ==="
    printfn "Execute tasks in order: %A" order
    printfn ""
    printfn "Execution plan:"
    order
    |> List.iteri (fun i taskId ->
        printfn "%d. Task %d" (i + 1) taskId)
| Error () ->
    printfn "Error: Circular dependency detected!"
    printfn "Cannot create a valid execution order."

Example with Circular Dependency

Let's see what happens when we add a circular dependency:

let tasksWithCycle =
    tasks
    |> addEdge 4 1 ()  // Deploy depends on Design - creates a cycle!

match topologicalSort tasksWithCycle with
| Ok order ->
    printfn "\nUnexpected: Found order %A" order
| Error () ->
    printfn "\n=== Circular Dependency Detected ==="
    printfn "Cannot execute tasks: Deploy depends on Design,"
    printfn "but Design (transitively) depends on Deploy!"

Output

=== Task Execution Order ===
Execute tasks in order: [1; 2; 3; 4]

Execution plan:
1. Design (task 1)
2. Implement (task 2)
3. Test (task 3)
4. Deploy (task 4)

=== Circular Dependency Detected ===
Cannot execute tasks: Deploy depends on Design,
but Design (transitively) depends on Deploy!

Use Cases

Topological sorting is useful for:

  1. Build Systems: Compile files in dependency order
  2. Package Managers: Install dependencies before dependents
  3. Workflow Automation: Execute steps in correct sequence
  4. Course Prerequisites: Schedule classes respecting prerequisites
  5. Data Processing Pipelines: Process data transformations in order

Algorithm Details

Yog.FSharp uses Kahn's algorithm for topological sorting: - Maintains a count of incoming edges for each node - Repeatedly removes nodes with no incoming edges - If all nodes are removed, a valid order exists - Otherwise, a cycle is detected

Time Complexity: O(V + E)

Running This Example

dotnet fsi docs/examples/task-scheduling.fsx
val tasks: obj
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
val order: int list
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
Multiple items
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T with get member IsEmpty: bool with get member Item: index: int -> 'T with get ...
val iteri: action: (int -> 'T -> unit) -> list: 'T list -> unit
val i: int
val taskId: int
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
val tasksWithCycle: obj
val order: obj
val task: TaskBuilder