codex/interval-scheduling-six-jobs-finish-versus-start.py

interval-scheduling-six-jobs-finish-versus-start.py
"""Original six-job comparison; Python 3.14, matplotlib 3.10.7.

Run from _media; writes this script's basename with a .png extension.
"""
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Patch


JOBS = [("A", 0, 6), ("B", 1, 3), ("C", 3, 5),
        ("D", 5, 7), ("E", 6, 9), ("F", 8, 10)]


def scan(key):
    chosen = []
    finish = None
    for name, start, end in sorted(JOBS, key=key):
        if finish is None or start >= finish:
            chosen.append(name)
            finish = end
    return chosen


def main():
    finish = scan(lambda job: job[2])
    start = scan(lambda job: job[1])
    assert finish == ["B", "C", "D", "F"]
    assert start == ["A", "E"]
    plt.rcParams.update({"font.size": 12, "axes.titlesize": 15})
    fig, axes = plt.subplots(1, 2, figsize=(11, 6.1), dpi=110,
                             sharex=True, sharey=True, facecolor="white")
    for ax, accepted, color, title in zip(
        axes, [finish, start], ["#087e8b", "#b86118"],
        ["Earliest finish: 4 jobs", "Earliest start: 2 jobs"]
    ):
        ax.set_facecolor("white")
        for row, (name, left, right) in enumerate(JOBS):
            y = 5 - row
            shade = color if name in accepted else "#d4dbe1"
            ax.plot([left, right], [y, y], color=shade, lw=15,
                    solid_capstyle="butt", zorder=2)
            ax.plot(left, y, "o", ms=7, color=shade, mec="#334155", zorder=3)
            ax.plot(right, y, "o", ms=7, mfc="white", mec="#334155", zorder=3)
            ax.text((left + right) / 2, y + .25,
                    f"{name} [{left}, {right})", ha="center", fontsize=11)
        ax.set_title(title, color=color, fontweight="bold", pad=37)
        ax.set_xlim(-.4, 10.4)
        ax.set_ylim(-.55, 5.7)
        ax.set_xticks(range(11))
        ax.set_yticks([])
        ax.set_xlabel("Time (arbitrary units)")
        ax.grid(axis="x", color="#edf0f3", zorder=0)
        ax.spines[["left", "right", "top"]].set_visible(False)
        ax.legend(handles=[Patch(facecolor=color, label="Accepted"),
                           Patch(facecolor="#d4dbe1", label="Rejected")],
                  ncol=2, frameon=False, loc="lower center",
                  bbox_to_anchor=(.5, 1.005), fontsize=11)
        ax.text(.5, -.22, "Chosen: " + ", ".join(accepted),
                transform=ax.transAxes, ha="center", fontweight="bold",
                color=color)
    fig.suptitle("Finishing sooner can leave room for more jobs", y=.97,
                 fontsize=18, color="#172b3a")
    fig.text(.5, .035, "Filled start, hollow finish: [start, finish)  •  Meeting endpoints are compatible",
             ha="center", fontsize=11, color="#334155")
    fig.subplots_adjust(left=.045, right=.98, top=.77, bottom=.25, wspace=.14)
    fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()