codexcircular-buffer-queue-trace-and-lifetimes.py
circular-buffer-queue-trace-and-lifetimes.py
"""Draw sequential queue states and compare membership lifetimes at overflow."""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
def trace():
h, n, slots = 0, 0, [None] * 5
rows = [(0, "start", h, n, 0, "∅", "—")]
calls = [("+", "A"), ("+", "B"), ("−", None), ("+", "C"),
("+", "D"), ("−", None), ("+", "E"), ("+", "F"), ("+", "G")]
for t, (op, item) in enumerate(calls, 1):
result = "—"
if op == "+":
slots[(h + n) % 5] = item
n += 1
else:
result = slots[h]
h = (h + 1) % 5
n -= 1
q = " ".join(slots[(h + i) % 5] for i in range(n))
rows.append((t, f"enqueue {item}" if op == "+" else "dequeue", h, n,
(h + n) % 5, q or "∅", result))
return rows
def main():
fig = plt.figure(figsize=(9, 8.5), dpi=120, facecolor="white")
fig.text(0.5, 0.962, "A full slot can end an item's membership", ha="center",
fontsize=18, weight="bold")
fig.text(0.5, 0.928, "Capacity 5 • states after completed calls • no elapsed-time scale",
ha="center", fontsize=11)
ax = fig.add_axes((0.045, 0.50, 0.91, 0.40))
ax.axis("off")
table = ax.table(cellText=trace(), colLabels=["Step", "Call", "h", "n", "w", "Logical queue", "Returned"],
cellLoc="center", colWidths=[0.07, 0.23, 0.06, 0.06, 0.06, 0.30, 0.12],
bbox=(0, 0, 1, 1))
table.auto_set_font_size(False)
table.set_fontsize(10.5)
for (r, c), cell in table.get_celld().items():
cell.set_edgecolor("#c5ced3")
cell.set_linewidth(0.7)
if r == 0:
cell.set_facecolor("#e5edf2")
cell.get_text().set_weight("bold")
elif r in (9, 10):
cell.set_facecolor("#e9f3f8" if r == 9 else "#fff0d8")
fig.text(0.5, 0.475, "Step 9 is full: h = w = 2, n = 5. Now propose H.",
ha="center", fontsize=11, weight="bold")
for left, title, lines, color in [
(0.06, "Reject full: try_enqueue(H)", ["10: full; C D E F G", "11: dequeue → C; D E F G"], "#e5eef5"),
(0.53, "Overwrite: append_latest(H)", ["10: discard C; D E F G H", "11: dequeue → D; E F G H"], "#fff0d8"),
]:
box = fig.add_axes((left, 0.353, 0.41, 0.096), facecolor=color)
box.set(xticks=[], yticks=[])
for spine in box.spines.values():
spine.set_color("#b1bdc5")
box.text(0.5, 0.80, title, ha="center", fontsize=10.5, weight="bold")
for y, line in zip([0.48, 0.17], lines):
box.text(0.5, y, line, ha="center", fontsize=10)
life = fig.add_axes((0.26, 0.07, 0.68, 0.22))
life.set(xlim=(0, 12), ylim=(-0.65, 3.65), xticks=range(13),
yticks=[3, 2, 1, 0],
yticklabels=["C • reject", "C • overwrite", "H • reject", "H • overwrite"])
life.set_xlabel("Completed operation number", fontsize=10)
life.set_title("Membership: present after entry, absent after removal/discard", fontsize=11, pad=10)
life.grid(axis="x", color="#e1e6e8", lw=0.7)
life.set_axisbelow(True)
for y, start, end, color in [(3, 4, 11, "#76afce"), (2, 4, 10, "#d7a34c"),
(0, 10, 12, "#d7a34c")]:
life.add_patch(Rectangle((start, y - 0.18), end - start, 0.36,
facecolor=color, edgecolor="none"))
life.plot(start, y, "o", ms=5, color="#35434c")
if end < 12:
life.plot(end, y, "o", ms=5, mfc="white", mec="#35434c")
else:
life.annotate("", (12, y), (11.5, y),
arrowprops={"arrowstyle": "->", "color": "#35434c"})
life.text(6.3, 1, "never accepted", ha="center", va="center", fontsize=10, color="#6b747a")
for spine in ["top", "right", "left"]:
life.spines[spine].set_visible(False)
life.tick_params(axis="y", length=0)
fig.savefig(Path(__file__).stem + ".png", facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()