from enum import Enum
import math
from typing import Tuple, Union
class CellType(Enum):
CONST = "CONST"
SUM = "SUM"
class Cell:
def __init__(self, cell_type: CellType, value: Union[float, Tuple[float, float]]):
self.cell_type = cell_type
if cell_type == CellType.CONST:
self.value = value
elif cell_type == CellType.SUM:
if not isinstance(value, tuple) or len(value) != 2:
raise ValueError("SUM cell must contain a tuple of two integer references")
self.references = value
else:
raise ValueError(f"Invalid cell type: {cell_type}")
def __str__(self):
if self.cell_type == CellType.CONST:
return str(self.value)
else:
return f"=({self.references[0]},{self.references[1]})"
def evaluate(self, spreadsheet) -> float:
# Check for CONST cells first (optimization)
if self.cell_type == CellType.CONST:
return self.value
# Check cache for previously calculated value
key = self
if key in spreadsheet.cache:
return spreadsheet.cache[key]
# Handle SUM cells with memoization
try:
ref1_value = spreadsheet.get_cell(self.references[0]).evaluate(spreadsheet)
ref2_value = spreadsheet.get_cell(self.references[1]).evaluate(spreadsheet)
result = ref1_value + ref2_value
except (IndexError, RecursionError):
# Handle both IndexError and RecursionError with NaN
result = math.nan
# Store evaluated value in cache for future use
spreadsheet.cache[key] = result
return result
class Spreadsheet:
def __init__(self):
self.cells = [Cell(CellType.CONST, 0) for _ in range(1000)] # Initialize all cells with 0s
self.cache = {}
def set_cell(self, index: int, cell: Cell) -> None:
if not isinstance(cell, Cell):
raise ValueError("Cell must be an instance of the Cell class")
if index < 0 or index >= 1000:
raise IndexError("Index out of bounds")
self.cells[index] = cell
def get_cell(self, index: int) -> float:
if index < 0 or index >= 1000:
raise IndexError("Index out of bounds")
return self.cells[index]
def evaluate(self) -> None:
# Clear the cache for a fresh evaluation
self.cache = {}
# Evaluate all cells in the spreadsheet
for cell in self.cells:
if cell is not None:
cell.evaluate(self) # Pass the spreadsheet instance for memoization
# Example usage
spreadsheet = Spreadsheet()
spreadsheet.set_cell(0, Cell(CellType.CONST, 5)) # Cell 0
spreadsheet.set_cell(1, Cell(CellType.CONST, 10)) # Cell 1
spreadsheet.set_cell(2, Cell(CellType.SUM, (0, 1))) # Cell 2 sums cell 0 and cell 1
spreadsheet.set_cell(3, Cell(CellType.SUM, (4, 0))) # Cell 3 (circular reference)
spreadsheet.set_cell(4, Cell(CellType.SUM, (3, 0))) # Cell 4 (circular reference)
spreadsheet.set_cell(5, Cell(CellType.SUM, (1000, 1001))) # Cell 5 with out of bounds references
print(spreadsheet.get_cell(0).evaluate(spreadsheet)) # Output: 5
print(spreadsheet.get_cell(1).evaluate(spreadsheet)) # Output: 10
print(spreadsheet.get_cell(2).evaluate(spreadsheet)) # Output: 15
print(spreadsheet.get_cell(3).evaluate(spreadsheet)) # Output: nan (circular reference)
print(spreadsheet.get_cell(4).evaluate(spreadsheet)) # Output: nan (circular reference)
print(spreadsheet.get_cell(5).evaluate(spreadsheet)) # Output: nan (out of bounds reference)