codexsorting-stable-insertion-compare-swap-trace.py
sorting-stable-insertion-compare-swap-trace.py
"""Draw an original adjacent-swap insertion-sort trace; write PNG to CWD.
Tested with Python 3.14.4, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media with MPLBACKEND=Agg and a writable MPLCONFIGDIR.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
def insertion_trace(entries):
"""Return complete states after each key comparison, including stopped ties."""
current = list(entries)
steps = []
for i in range(1, len(current)):
j = i
while j > 0:
left, right = current[j - 1], current[j]
swap = right[0] < left[0]
if swap:
current[j - 1], current[j] = right, left
steps.append({"pass": i, "pair": (left, right), "swap": swap,
"positions": (j - 1, j), "state": tuple(current)})
if not swap:
break
j -= 1
return tuple(current), steps
def draw():
original = ((2, "A"), (1, "B"), (2, "C"), (1, "D"))
result, steps = insertion_trace(original)
assert result == ((1, "B"), (1, "D"), (2, "A"), (2, "C"))
assert len(steps) == 5 and sum(s["swap"] for s in steps) == 3
fig, ax = plt.subplots(figsize=(9.4, 6.4), dpi=110, facecolor="white")
ax.set(xlim=(-0.1, 10.0), ylim=(-0.45, 7.15))
ax.axis("off")
ax.text(0, 6.88, "Stable insertion sort: compare keys, swap whole entries",
fontsize=15, weight="bold", color="#182f46")
ax.text(0, 6.47, "Letter = original identity / payload; letters are never compared.",
fontsize=11, color="#334b60")
states = [original] + [s["state"] for s in steps]
for row, state in enumerate(states):
y = 5.65 - row * 0.94
ax.text(0, y + 0.27, "Input" if row == 0 else f"Compare {row}",
fontsize=11, va="center", weight="bold")
for col, (key, identity) in enumerate(state):
x = 1.25 + col * 0.88
marked = row > 0 and col in steps[row - 1]["positions"]
color = "#e5f0f9" if not marked else (
"#fff0d2" if steps[row - 1]["swap"] else "#e0f2e7")
ax.add_patch(FancyBboxPatch((x, y), 0.72, 0.57,
boxstyle="round,pad=0.03", facecolor=color,
edgecolor="#647b8d", linewidth=1.2))
ax.text(x + 0.36, y + 0.285, f"{key}{identity}",
ha="center", va="center", fontsize=14, weight="bold")
if row:
step = steps[row - 1]
left, right = step["pair"]
label = f"{right[0]} < {left[0]}? "
label += "yes: swap" if step["swap"] else "no: stop this pass"
ax.text(4.98, y + 0.34, label, fontsize=12, va="center")
ax.text(4.98, y + 0.02, f"Insert original position {step['pass']}",
fontsize=10, color="#526775", va="center")
ax.text(0, -0.20, "5 key comparisons • 3 adjacent swaps • ties stay in arrival order",
fontsize=12, weight="bold", color="#285c43")
fig.subplots_adjust(left=0.035, right=0.99, bottom=0.03, top=0.99)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
plt.close(fig)
if __name__ == "__main__":
draw()