Elm-spreadsheet provides the means to represent and compute with spreadsheets in Elm. A spreadsheet is a two-dimensional array of cells, each of which may contain either data (values) or formulas. To evaluate a spreadsheet is to apply the formulas to create a new spreadsheet. If the new spreadsheet contains no formulas, we say that it is fully evaluated.
Spreadsheets can be represented in a variety of ways.
As a String
100.0 1.1 row * 0 1
120.0 1.4 row * 0 1
140.0 0.9 row * 0 1
- col sum 0 2 col sum 0 2As a List (List String)
> TestData.text
[
["100.0","120.0","140.0","-"]
,["1.1","1.4","0.9","col sum 0 2"]
,["row * 0 1","row * 0 1","row * 0 1","col sum 0 2"]
]As a List Cell
> parse TestData.text
[[Right (Real 100),Right (Real 120),Right (Real 140),Right Undefined],[Right (Real 1.1),Right (Real 1.4),Right (Real 0.9),Left (ColOp "sum" 0 2)],[Left (RowOp "*" 0 1),Left (RowOp "*" 0 1),Left (RowOp "*" 0 1),Left (ColOp "sum" 0 2)]]where
type alias Cell =
Either Formula Value
type Formula
= RowOp String Col Col
| ColOp String Row Row
type Value
= Integer Int
| Real Float
| Boolean Bool
| String String
| UndefinedOne can convert this format back into a text represention:
render : List Cell -> List (List String)
render sheet =
List.map renderColumn sheetThe function eval carries out the computations specified in the
formula cells. Thus the sequence
TestData.text |> parse |> eval |> render |> Pretty.printyields the data
100 1.1 110.0
120 1.4 168.0
140 0.9 126.0
- 3.4 404.0The eval
function always produces a transformed spreadsheet. When fully
evaluated, all cells are of typeRight Value. In this early version
of the package, not all valid spreadsheets are fully evaluated.
evalThe current strategy is to evaluate row formulas from left to right and then evaluate column formulas from top to bottom. This strategy always succeeds in that it yields a spreadsheet. However, the result is fully evaluated only if certain restrictions hold:
A row operation in a cell at (row, col) must refer only to columns i, j where i < col, j < col. Moreover, the cells at (row, i) and (row, j) must contain values.
A column operation in a cell at (row, col) must refer only to rows i, j where i < row and j < row. Moreover after all row operations have been carried out as in (1), the cells referred to must contain values.
To lift these restrictions, we must "solve" the spreadsheet
by finding an order in which to evaluate the operations which
results in a fully evaluated spreadsheet. To do this,
consider the dependencies of the operations. If an operation
in cell A, which we label as "op A", depends on an operation in
cell B, then we say that op B is a child of op A:
op A -> op BThus the operations constitute the nodes of a graph in which the edges represent dependencies. A depth-first traversal of the graph, assuming no cyclic dependencies, will result in full evaluation.
Thus it remains to (a) derive dependency graphs from spreadsheets, (b) traverse the graph depth-first, performing the indicated computations as each node is visited.
Implement a depth-first evaluation strategy
Detect cyclic dependencies
Add formulas