codexcounting-sort-frequency-prefix-ranges.py
counting-sort-frequency-prefix-ranges.py
"""Draw original counting-sort frequencies and exclusive output ranges.
Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; writes counting-sort-frequency-prefix-ranges.png to CWD.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
def main():
records = [(2, "A"), (0, "B"), (2, "C"), (1, "D"),
(0, "E"), (3, "F"), (2, "G")]
counts = [sum(key == k for key, _ in records) for k in range(4)]
boundaries = [0]
for count in counts:
boundaries.append(boundaries[-1] + count)
colors = ["#2475ad", "#c67c15", "#23855b", "#8259aa"]
plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
fig = plt.figure(figsize=(10.5, 6.2), dpi=100, facecolor="white")
fig.suptitle("Counting sort: frequencies become output ranges", fontsize=18, y=.97)
fig.text(.5, .895, "Original input: (2,A), (0,B), (2,C), (1,D), (0,E), (3,F), (2,G)",
ha="center", fontsize=12)
ax = fig.add_axes((.085, .48, .34, .32))
ax.bar(range(4), counts, color=colors, width=.62)
for k, count in enumerate(counts):
ax.text(k, count + .12, str(count), ha="center", fontsize=14)
ax.set(xlabel="Integer key k", ylabel="Count c[k] (records)",
xticks=range(4), yticks=range(4), ylim=(0, 3.7))
ax.spines[["top", "right"]].set_visible(False)
ax.set_title("n = 7 records; K = 4 possible keys", fontsize=12, pad=14)
table_ax = fig.add_axes((.48, .45, .48, .35))
table_ax.axis("off")
table = table_ax.table(
cellText=[[k, counts[k], boundaries[k], boundaries[k + 1],
f"[{boundaries[k]}, {boundaries[k + 1]})"] for k in range(4)],
colLabels=["Key k", "c[k]", "S[k]", "P[k]", "Output range"],
colWidths=[.14, .14, .14, .14, .30], cellLoc="center", loc="center")
table.auto_set_font_size(False)
table.set_fontsize(12)
table.scale(1.1, 1.9)
for (row, col), cell in table.get_celld().items():
cell.set_edgecolor("#d7dce1")
if row == 0:
cell.set_facecolor("#eef1f4")
elif col == 0:
cell.set_facecolor(colors[row - 1])
cell.get_text().set_color("white")
slots = fig.add_axes((.085, .105, .86, .255))
slots.set(xlim=(-.25, 7.25), ylim=(-.5, 1.55))
slots.axis("off")
for k in range(4):
left, right = boundaries[k:k + 2]
for j in range(left, right):
slots.add_patch(Rectangle((j, .2), 1, .52, facecolor=colors[k],
edgecolor="white", linewidth=2))
slots.text(j + .5, .46, f"key {k}", ha="center", va="center", color="white")
slots.text(j + .5, -.02, str(j), ha="center", va="center")
slots.plot([left, right], [1.02, 1.02], color=colors[k], linewidth=3)
slots.plot([left, left], [.9, 1.12], color=colors[k], linewidth=2)
slots.plot([right, right], [.9, 1.12], color=colors[k], linewidth=2)
slots.text((left + right) / 2, 1.32, f"[{left}, {right})", ha="center", color=colors[k])
for boundary in boundaries:
slots.text(boundary, .83, str(boundary), ha="center", fontsize=10)
slots.text(3.5, -.4, "Output slot j (zero-based): 0 through 6", ha="center")
fig.text(.5, .025, "S[k] counts keys < k; P[k] counts keys ≤ k. P[k] is an endpoint, not an occupied slot.",
ha="center", fontsize=11)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()