Spreadsheet Engine: References, Ranges, Cycles and Scale
HardAI-assisted coding · AI-Assisted Optimization
Spreadsheet Engine: References, Ranges, Cycles and Scale
You've joined the team that owns the calculation engine behind a lightweight spreadsheet product. A sheet is a grid of cells; a cell holds a number, some text, or a formula such as =A1+B2*2. Customers are reporting wrong totals, and three roadmap items are queued behind that: range functions, circular-reference detection, and making big sheets fast.
Codebase
| File | What it does |
|---|---|
sheet/api.py | run(operations) entry point and the Dispatcher that routes each operation |
sheet/sheet.py | Sheet: cell storage, set / clear / get / values / fill_down, recalculation |
sheet/evaluator.py | Evaluator: computes a cell's value from its formula |
sheet/parser.py | Recursive-descent parser: formula text -> node tree |
sheet/lexer.py, sheet/nodes.py | Tokens and the node dataclasses (Num, Ref, Neg, BinOp, Call) |
sheet/functions.py | The FUNCTIONS registry (ABS, MIN, MAX today) |
sheet/cells.py | Cell, classifying what a user typed (parse_content), and normalize for output |
sheet/refs.py | parse_ref, parse_range, cells_in: "B3" <-> (column, row) |
sheet/fill.py | shift_rows: moves a formula's references down for fill_down |
sheet/errors.py | CellError values (#DIV/0, ...) and the edit exceptions |
sheet/graph.py | Placeholder for dependency tracking (empty today) |
tests/ | Existing tests (some of them currently fail) |
How your code is run
run(operations: list[list]) -> list executes the operations in order against an empty sheet and returns exactly one result per operation. A request that cannot be carried out comes back as an "error: <reason>" string and changes nothing. Problems *inside* a formula are not request errors: they become cell values such as "#DIV/0".
| Operation | Arguments | Returns | Errors |
|---|---|---|---|
set | ref, content | true | invalid cell, invalid formula |
get | ref | the cell's value | invalid cell |
clear | ref | true | invalid cell |
fill_down | range, content | true | invalid range, invalid formula |
values | range | list of values, one row at a time, left to right | invalid range |
- All arguments are strings. A wrong number or type of arguments returns
"error: invalid arguments", an unknown operation name"error: unknown operation". - A ref is one capital letter
A-Zfollowed by a row1-9999with no leading zeros (B3). Anything else, including lower case, isinvalid cell. A range is two refs joined by:(A1:C3); it covers the rectangle between the corners, which may be written in either order. - Content.
""clears the cell (same asclear). Text starting with=is a formula. A whole string of the form-?digitsor-?digits.digitsis a number. Anything else is text, kept exactly as typed (" 5"and"5."are text). - Values are returned as: a number (an integer when it is a whole number, otherwise rounded to 6 decimal places), the text,
nullfor a blank cell, or an error string such as"#DIV/0". - Formulas may contain numbers, cell refs,
+ - * /, unary minus, parentheses, function calls and spaces.*and/bind tighter than+and-; operators of the same precedence group left to right (10-3-2is 5,8/4/2is 1). A formula that cannot be parsed makessetreturnerror: invalid formulaand leaves the cell as it was. - In arithmetic a blank cell counts as 0, text gives
#VALUE, and dividing by zero gives#DIV/0. A formula that is just a ref shows the referenced value as is (text stays text, a blank cell shows 0). An unknown function name gives#NAME.ABStakes exactly one argument (otherwise#VALUE);MIN/MAXtake any number of arguments, skip blank and text values, and give 0 when there is no number. - Errors propagate. If an operand is an error, so is the result; when both operands of an operator are errors the left one wins. For function arguments the first error from left to right wins.
Worked example
[["set", "A1", "10"], ["set", "B2", "5"], ["set", "C3", "=A1+B2"], ["get", "C3"],
["values", "A1:C3"]]returns
[true, true, true, 15, [10, null, null, null, 5, null, null, null, 15]]Part 1 — Wrong totals
Goal. Customers see wrong numbers in three situations. Find the causes and fix them.
Rules
- A reference in a formula reads exactly the cell it names (
=A2+B1reads A2 and B1, whichever rows and columns they are in). - Operators of the same precedence group left to right.
- Clearing a cell (
clear, orsetwith"") makes every formula that depends on it recalculate as if the cell were blank. valuesreports the cells of its range row by row, left to right.
Part 2 — Ranges and aggregate functions
Goal. Support A1:B3 ranges and the functions customers asked for.
Rules
- A range may be written wherever an expression is allowed, but it only has a value as a function argument; used any other way (
=A1:A3,=A1:A3+1,=ABS(A1:A3)) the formula's value is#VALUE. Both ends of a range must be valid refs, otherwiseerror: invalid formula. - New functions
SUM,AVERAGEandCOUNT;MINandMAXnow accept ranges. Each takes any number of arguments; each argument is an expression, a cell ref or a range. They look at the numbers among all arguments (the cells of a range in row-major order) and skip blank and text cells, whether they arrive through a range or a single ref.SUMof nothing is 0,AVERAGEof nothing is#DIV/0,COUNTis how many numbers there were,MIN/MAXof nothing is 0. - If any argument value is an error the function returns it (the first one, in argument order and row-major order within a range). That includes
COUNT. - A cell that has not been filled in yet, or is filled in or cleared later, is still part of the range: formulas that use the range update when any cell inside it changes.
fill_downmoves both corners of a range down, like any other reference.
Example. With A1=1, A2=2, B1=3, B3=10, the formula =SUM(A1:B3) is 16 (blanks skipped). =SUM(B3:A1) is the same range.
Part 3 — Circular references
Goal. A formula that (indirectly) depends on itself must produce #CYCLE instead of misbehaving.
Rules
- A cell is in a cycle if following the references in its formula (a range refers to every cell inside it) can lead back to the cell. A formula that refers to itself, or whose range includes itself, is a cycle.
- Every cell in a cycle has the value
#CYCLE. - Every cell that depends on a cycle cell, directly or through other cells or ranges, also has the value
#CYCLE, whatever else its formula contains.#CYCLEtakes priority over every other error:=1/0+B1is#CYCLEif B1 is. setreturnstrueeven when it creates a cycle. Editing or clearing a cell so the cycle no longer exists restores normal values everywhere.- Long dependency chains that are *not* cycles must work: a chain of 100 cells, each
=the one above+ 1, shows the right number at the bottom.
Example. With A1: =B1+1, B1: =A1+1 and C1: =A1*2, all three cells show #CYCLE. After set B1 "5", A1 is 6 and C1 is 12.
Part 4 — Big sheets (stretch)
Goal. Finance models fill thousands of cells with chained formulas and then change inputs over and over. Make that fast without changing any result.
Rules
- Every result stays exactly as in Parts 1-3.
- A
fill_downthat creates a chain of 2,500 formulas (each referring to the cell above) must work, and so must changing the top of that chain and reading the bottom. - With about 4,000 formula cells in the sheet (long independent chains), hundreds of single-cell edits must stay fast: an edit has to recalculate only the edited cell and the cells that depend on it, not the whole sheet.
- A formula that is replaced must stop depending on the cells its old version read.
Notes
refs.py,lexer.py,errors.pyandcells.pyare known-good.- You may use the AI assistant freely. Later parts must not break earlier ones; the hidden tests re-check every part. Parts 1-3 is a strong result; Part 4 is a stretch.
Example 1
- Input:
- [["set","A1","10"],["set","B2","5"],["set","C3","=A1+B2"],["get","C3"],["values","A1:C3"]]
- Output:
- [true,true,true,15,[10,null,null,null,5,null,null,null,15]]
- Explanation
- C3 reads A1 and B2. values lists A1:C3 one row at a time, left to right; blank cells are null.
Example 2
- Input:
- [["set","A1","1"],["set","A2","2"],["set","B1","3"],["set","B3","10"],["set","C1","=SUM(A1:B3)"],["get","C1"]]
- Output:
- [true,true,true,true,true,16]
- Explanation
- SUM adds the numbers in the rectangle A1:B3 and skips its two blank cells.
Example 3
- Input:
- [["set","A1","=B1+1"],["set","B1","=A1+1"],["set","C1","=A1*2"],["get","C1"],["set","B1","5"],["get","A1"],["get","C1"]]
- Output:
- [true,true,true,"#CYCLE",true,6,12]
- Explanation
- A1 and B1 refer to each other, and C1 depends on A1, so all show #CYCLE. Replacing B1 with a number breaks the cycle.
Guidelines
- Python 3 standard library only
- Every operation returns exactly one value; request errors are returned as "error: <reason>" strings, formula problems are cell values like "#DIV/0"
- Cell refs are a capital letter A-Z plus a row 1-9999; cells are keyed internally as (column, row)
- Numbers are returned as ints when whole, otherwise rounded to 6 decimal places
- Part 4 inputs contain chains of thousands of formulas, deeper than Python's default recursion limit