import sys from typing import NamedTuple, Callable # Folding function. def fold[T, U](f: Callable[[U, T], U], z: U, l: list[T]) -> U: # This is a simple for loop that accumulates a value and then returns it. for x in l: z = f(z, x) return z # This is the state of the memory. class State(NamedTuple): faults: int hits: int order: dict[int, list[int]] page_table: set[int] physical_capacity: int # Increase hits by 1. def hit(s): return State(s.faults, s.hits + 1, s.order, s.page_table, s.physical_capacity) # Increase faults by 1. def fault(s): return State(s.faults + 1, s.hits, s.order, s.page_table, s.physical_capacity) # Updates the indices in the state. def update(s): # This is the recursive function that decreases the indices. def rec(order: dict[int, list[int]], page: int) -> dict[int, list[int]]: # If the page appears again: if order[page] != []: # Decrease all of the indices tmp = [x - 1 for x in order[page]] # If the first index is 0, skip it if tmp[0] == 0: return order | {page: tmp[1:]} # Else, just use the decreased indices else: return order | {page: tmp} # In the case where the page doesn't appear again, just return the # order. else: return order return State(s.faults, s.hits, fold(rec, s.order.copy(), s.order), s.page_table, s.physical_capacity) # Frees up a spot in the page table depending on the order. def evict(s): # These are the candidates to get removed. opts = [(page, s.order[page]) for page in s.page_table] # If some of the candidates don't appear again, simply remove them from # the page table. empty = [a for a in opts if a[1] == []] if empty != []: page = empty[0][0] return State(s.faults, s.hits, s.order, s.page_table - {page}, s.physical_capacity) else: # Otherwise, pick the candidate that appears last. page, lst = sorted([a for a in opts if a[1] != []], key = lambda x: x[1], reverse = True)[0] return State(s.faults, s.hits, s.order | {page: lst[1:]}, s.page_table - {page}, s.physical_capacity) # Inserts a page into the page table. def insert(s, page: int): return State(s.faults, s.hits, s.order, s.page_table | {page}, s.physical_capacity) # This is the algorithm that is used to calculate the faults and hits. def optimal(state: State, access: tuple[str,int]) -> State: # The read page = access[1] # If the page is in the page table, then this is a page hit. if page in state.page_table: return state.update().hit() else: # Otherwise this is a page fault, and depending on whether the page # table is full or not, some space has to be made. if len(state.page_table) == state.physical_capacity: return state.evict().insert(page).update().fault() else: return state.insert(page).update().fault() # This function calculates when they next page will be loaded into physical # memory. The existence and reliance on this function in order to execute the # rest of the algorithm is what make this algorithm impossible to implement. def preprocess(accesses: list[tuple[str,int]]) -> dict[int,list[int]]: # The accumulating function: if the page is not in the pages that have been # seen, initialize it with 0. Otherwise, add the newest index to it. def f(seen, x): index, (_, page) = x if page not in seen: return seen | {page: []} else: return seen | {page: seen[page] + [index]} # Fold over the accesses with the accumulating function. return fold(f, dict(), list(enumerate(accesses))) def main(): # Please provide the correct number of arguments. if len(sys.argv) != 3: print("USAGE:", sys.argv[0], "number_of_pages", "file_path") print("\twhere:\n\t\tnumber_of_pages : int\n\t\tfile_path : string") # The file path is the second argument. file_path = sys.argv[2] # This opens the file and parses the accesses. accesses = [] with open(file_path) as f: accesses = [(x.split(":")[0], int(x.split(":")[1])) for x in f.read().split()] # The limit is the first argument. limit = int(sys.argv[1]) # The result of this computation will be a fold over the accesses, with the # the following state and using the optimal algorithm. res = fold(optimal, State(faults = 0, hits = 0, order = preprocess(accesses), page_table = set(), physical_capacity = limit), accesses) # Print the results! print("Faults:", res.faults, "\nHits:", res.hits) main()