Level 1 First-fit allocate and free
Build MemoryAllocator(total_capacity, strategy="first"). It hands out byte ranges of the address space [0, total_capacity), which starts out as a single free gap. You'll need strategy in level 2.
- The constructor raises
ValueErroriftotal_capacity <= 0. allocate(size) -> int: find the lowest-address free gap that holds at leastsizebytes (this is first fit). Hand out the front of it, so the gap shrinks from the left, or disappears on an exact fit. Return the block's start address. RaiseValueErrorifsize <= 0or if no single gap is big enough, even when the total free memory would be.free(address, size) -> None: give back a block thatallocatehanded out, passing the same size. The freed range merges with a free gap that ends right where it starts and with one that starts right where it ends. So it can join the left gap, the right gap, both, or neither. Free gaps are never left touching each other.- Raise
ValueErrorand change nothing ifaddressisn't the start of a live allocation (never allocated, or already freed), ifsizedoesn't match what was allocated there, or if the range falls outside[0, total_capacity).
- Raise
free_blocks() -> list[tuple[int, int]]: every free gap as(start, size), sorted by start.get_free_memory() -> intis the total free bytes.get_largest_free_block() -> intis the size of the largest gap (0 if memory is full).
m = MemoryAllocator(100)
m.allocate(20) # 0
m.allocate(30) # 20
m.allocate(40) # 50 free: [(90, 10)]
m.free(20, 30) # free: [(20, 30), (90, 10)]
m.allocate(25) # 20 free: [(45, 5), (90, 10)]
m.free(0, 20) # free: [(0, 20), (45, 5), (90, 10)]
m.free(20, 25) # free: [(0, 50), (90, 10)] merged on both sides
m.allocate(60) # raises ValueError: 60 bytes are free, but not in one piece