diff --git a/lambdas/maps/ADDRESSES.lambda b/lambdas/maps/ADDRESSES.lambda new file mode 100644 index 0000000..16e5153 --- /dev/null +++ b/lambdas/maps/ADDRESSES.lambda @@ -0,0 +1,27 @@ +/* FUNCTION NAME: ADDRESSES + DESCRIPTION:*//**Returns the A1-style addresses of cells in a range as text, shape-matching the input range.*/ +/* REVISIONS: Date Developer Description + 2026-05-20 Claude Initial version +*/ +ADDRESSES = LAMBDA( +// Parameter Declarations + [cells], +// Help + LET(Help, TEXTSPLIT( + "FUNCTION: →ADDRESSES(cells)¶" & + "DESCRIPTION: →Returns the A1-style addresses of cells in a range as text, shape-matching the input range.¶" & + "VERSION: →May 20 2026¶" & + "PARAMETERS: →¶" & + " cells →(Required) A cell or range reference. Output shape matches the input shape. Addresses are returned in relative form (no $ anchors), matching CELLTOPOS / MOVECELL / POSTOCELL conventions.¶" & + "EXAMPLE: →¶" & + "Formula →=ADDRESSES(F28:H30)¶" & + "Result →3x3 grid of ""F28"" through ""H30""", + "→", "¶" + ), + // Check inputs + Help?, ISOMITTED(cells), + // Procedure + result, ADDRESS(ROW(cells), COLUMN(cells), 4), + IF(Help?, Help, result) +) +); diff --git a/lambdas/maps/ADDRESSES.tests.yaml b/lambdas/maps/ADDRESSES.tests.yaml new file mode 100644 index 0000000..af9df43 --- /dev/null +++ b/lambdas/maps/ADDRESSES.tests.yaml @@ -0,0 +1,39 @@ +tests: + - name: single cell F28 + setup: + names: + - { name: "testRng", refers_to: "F28" } + args: ["=testRng"] + expected: "F28" + + - name: 3x3 grid F28:H30 + setup: + names: + - { name: "testRng", refers_to: "F28:H30" } + args: ["=testRng"] + expected: [["F28", "G28", "H28"], ["F29", "G29", "H29"], ["F30", "G30", "H30"]] + + - name: column B5:B10 + setup: + names: + - { name: "testRng", refers_to: "B5:B10" } + args: ["=testRng"] + expected: [["B5"], ["B6"], ["B7"], ["B8"], ["B9"], ["B10"]] + + - name: row B5:D5 + setup: + names: + - { name: "testRng", refers_to: "B5:D5" } + args: ["=testRng"] + expected: [["B5", "C5", "D5"]] + + - name: multi-letter column AA1 + setup: + names: + - { name: "testRng", refers_to: "AA1" } + args: ["=testRng"] + expected: "AA1" + + - name: help text + args: [] + expected_type: array diff --git a/lambdas/maps/CELLDIST.lambda b/lambdas/maps/CELLDIST.lambda new file mode 100644 index 0000000..b4358cc --- /dev/null +++ b/lambdas/maps/CELLDIST.lambda @@ -0,0 +1,42 @@ +/* FUNCTION NAME: CELLDIST + DESCRIPTION:*//**Returns the grid distance between two A1-style cell addresses (Manhattan, Chebyshev, or Euclidean).*/ +/* REVISIONS: Date Developer Description + 2026-05-20 Claude Initial version +*/ +CELLDIST = LAMBDA( +// Parameter Declarations + [cell1], + [cell2], + [metric], +// Help + LET(Help, TEXTSPLIT( + "FUNCTION: →CELLDIST(cell1, cell2, [metric])¶" & + "DESCRIPTION: →Returns the grid distance between two A1-style cell addresses (Manhattan, Chebyshev, or Euclidean).¶" & + "VERSION: →May 20 2026¶" & + "PARAMETERS: →¶" & + " cell1 →(Required) First cell address as text (e.g. ""F28""). Also accepts an array of addresses.¶" & + " cell2 →(Required) Second cell address as text (e.g. ""G26""). Also accepts an array of addresses. When both are arrays, Excel broadcasts naturally: same-shape inputs pair element-wise, column × row produces a 2D distance grid.¶" & + " metric →(Optional) Distance metric: 0 Manhattan (default, 4-directional), 1 Chebyshev (8-directional, diagonals count as 1), 2 Euclidean (straight-line).¶" & + "EXAMPLE: →¶" & + "Formula →=CELLDIST(""F28"", ""G26"")¶" & + "Result →3", + "→", "¶" + ), + // Check inputs + Help?, ISOMITTED(cell1), + // Procedure + safeMult, 16385, + met, IF(ISOMITTED(metric), 0, metric), + posA, CELLTOPOS(cell1, safeMult), + posB, CELLTOPOS(cell2, safeMult), + rowA, INT(posA / safeMult), + rowB, INT(posB / safeMult), + colA, MOD(posA, safeMult), + colB, MOD(posB, safeMult), + dR, ABS(rowA - rowB), + dC, ABS(colA - colB), + result, IF(met = 2, SQRT((rowA - rowB)^2 + (colA - colB)^2), + IF(met = 1, IF(dR > dC, dR, dC), dR + dC)), + IF(Help?, Help, result) +) +); diff --git a/lambdas/maps/CELLDIST.tests.yaml b/lambdas/maps/CELLDIST.tests.yaml new file mode 100644 index 0000000..36f0b27 --- /dev/null +++ b/lambdas/maps/CELLDIST.tests.yaml @@ -0,0 +1,90 @@ +tests: + - name: Manhattan F28 to G26 (default) + args: ["F28", "G26"] + expected: 3 + + - name: Manhattan AH27 to S34 + args: ["AH27", "S34"] + expected: 22 + + - name: Manhattan AL33 to N15 + args: ["AL33", "N15"] + expected: 42 + + - name: Manhattan X15 to Z4 + args: ["X15", "Z4"] + expected: 13 + + - name: Manhattan AC36 to AS42 + args: ["AC36", "AS42"] + expected: 22 + + - name: Manhattan AS7 to U16 + args: ["AS7", "U16"] + expected: 33 + + - name: same cell is zero + args: ["E5", "E5"] + expected: 0 + + - name: Chebyshev X15 to Z4 (metric 1) + args: ["X15", "Z4", "=1"] + expected: 11 + + - name: Chebyshev diagonal F5 to H7 + args: ["F5", "H7", "=1"] + expected: 2 + + - name: Chebyshev pure horizontal A1 to E1 + args: ["A1", "E1", "=1"] + expected: 4 + + - name: Euclidean A1 to A4 (metric 2) + args: ["A1", "A4", "=2"] + expected: 3 + + - name: Euclidean A1 to B2 (metric 2) + args: ["A1", "B2", "=2"] + expected: 1.4142135623730951 + + - name: Euclidean A1 to D5 (3-4-5 triangle) + args: ["A1", "D5", "=2"] + expected: 5 + + - name: handles absolute references + args: ["$F$28", "$G$26"] + expected: 3 + + - name: handles sheet prefix + args: ["Sheet1!F28", "Sheet1!G26"] + expected: 3 + + - name: cell containing address string + setup: + cells: + - address: "C3" + values: "F28" + - address: "C4" + values: "G26" + args: ["=C3", "=C4"] + expected: 3 + + - name: vertical array zip same-shape + args: ['={"F28";"AH27";"X15"}', '={"G26";"S34";"Z4"}'] + expected: [[3], [22], [13]] + + - name: horizontal array zip same-shape + args: ['={"F28","X15"}', '={"G26","Z4"}'] + expected: [[3, 13]] + + - name: cartesian column x row broadcast + args: ['={"A1";"B2"}', '={"C3","D4"}'] + expected: [[4, 6], [2, 4]] + + - name: cartesian column x row Chebyshev + args: ['={"A1";"B2"}', '={"C3","D4"}', "=1"] + expected: [[2, 3], [1, 2]] + + - name: help text + args: [] + expected_type: array diff --git a/lambdas/maps/COLNUM.lambda b/lambdas/maps/COLNUM.lambda new file mode 100644 index 0000000..878a94b --- /dev/null +++ b/lambdas/maps/COLNUM.lambda @@ -0,0 +1,28 @@ +/* FUNCTION NAME: COLNUM + DESCRIPTION:*//**Returns the column number of an A1-style cell address passed as text. Like COLUMN() but accepts strings.*/ +/* REVISIONS: Date Developer Description + 2026-05-20 Claude Initial version +*/ +COLNUM = LAMBDA( +// Parameter Declarations + [address], +// Help + LET(Help, TEXTSPLIT( + "FUNCTION: →COLNUM(address)¶" & + "DESCRIPTION: →Returns the column number of an A1-style cell address passed as text. Like COLUMN() but accepts strings. Handles multi-letter columns (AA, BZ, XFD).¶" & + "VERSION: →May 20 2026¶" & + "PARAMETERS: →¶" & + " address →(Required) A1 address text (e.g. ""AA17""), or an array/range of such. Sheet prefixes, $ anchors, and ranges are tolerated (first cell used). Result lifts element-wise.¶" & + "EXAMPLE: →¶" & + "Formula →=COLNUM(""AA17"")¶" & + "Result →27", + "→", "¶" + ), + // Check inputs + Help?, ISOMITTED(address), + // Procedure + safeMult, 100000, + result, MOD(CELLTOPOS(address, safeMult), safeMult), + IF(Help?, Help, result) +) +); diff --git a/lambdas/maps/COLNUM.tests.yaml b/lambdas/maps/COLNUM.tests.yaml new file mode 100644 index 0000000..00b44c3 --- /dev/null +++ b/lambdas/maps/COLNUM.tests.yaml @@ -0,0 +1,52 @@ +tests: + - name: simple address A1 + args: ["A1"] + expected: 1 + + - name: multi-letter column AA1 + args: ["AA1"] + expected: 27 + + - name: column Z + args: ["Z1"] + expected: 26 + + - name: max column XFD + args: ["XFD1"] + expected: 16384 + + - name: column AB + args: ["AB12"] + expected: 28 + + - name: absolute reference dollars stripped + args: ["$E$5"] + expected: 5 + + - name: sheet-prefixed address + args: ["Sheet1!AA17"] + expected: 27 + + - name: lowercase address + args: ["aa17"] + expected: 27 + + - name: cell containing address string + setup: + cells: + - address: "C3" + values: "AA17" + args: ["=C3"] + expected: 27 + + - name: vertical array of addresses lifts + args: ['={"A1";"Z1";"AA1"}'] + expected: [[1], [26], [27]] + + - name: horizontal array of addresses lifts + args: ['={"A1","Z1","AA1"}'] + expected: [[1, 26, 27]] + + - name: help text + args: [] + expected_type: array diff --git a/lambdas/maps/COLOF.lambda b/lambdas/maps/COLOF.lambda new file mode 100644 index 0000000..63d1c8c --- /dev/null +++ b/lambdas/maps/COLOF.lambda @@ -0,0 +1,30 @@ +/* FUNCTION NAME: COLOF + DESCRIPTION:*//**Extracts the column number from an encoded position integer (as produced by CELLTOPOS).*/ +/* REVISIONS: Date Developer Description + 2026-05-20 Claude Initial version +*/ +COLOF = LAMBDA( +// Parameter Declarations + [pos], + [mult], +// Help + LET(Help, TEXTSPLIT( + "FUNCTION: →COLOF(pos, [mult])¶" & + "DESCRIPTION: →Extracts the column number from an encoded position integer (as produced by CELLTOPOS).¶" & + "VERSION: →May 20 2026¶" & + "PARAMETERS: →¶" & + " pos →(Required) Encoded position (row*mult+col). Also accepts an array/range of positions — result lifts element-wise.¶" & + " mult →(Optional) Multiplier used during encoding. Default: 100000 (matches CELLTOPOS). Must match the mult used to encode pos.¶" & + "EXAMPLE: →¶" & + "Formula →=COLOF(1700017)¶" & + "Result →17", + "→", "¶" + ), + // Check inputs + Help?, ISOMITTED(pos), + // Procedure + m, IF(ISOMITTED(mult), 100000, mult), + result, MOD(pos, m), + IF(Help?, Help, result) +) +); diff --git a/lambdas/maps/COLOF.tests.yaml b/lambdas/maps/COLOF.tests.yaml new file mode 100644 index 0000000..19ce81e --- /dev/null +++ b/lambdas/maps/COLOF.tests.yaml @@ -0,0 +1,40 @@ +tests: + - name: simple position Q17 + args: ["=1700017"] + expected: 17 + + - name: column 5 from position E5 + args: ["=500005"] + expected: 5 + + - name: top-left A1 + args: ["=100001"] + expected: 1 + + - name: multi-letter column AB + args: ['=CELLTOPOS("AB12")'] + expected: 28 + + - name: composed with CELLTOPOS XFD + args: ['=CELLTOPOS("XFD16384")'] + expected: 16384 + + - name: custom mult 50 + args: ["=526", "=50"] + expected: 26 + + - name: custom mult matches CELLTOPOS custom mult + args: ['=CELLTOPOS("Z10", 50)', "=50"] + expected: 26 + + - name: vertical array of positions lifts + args: ['={100001;500005;1700017}'] + expected: [[1], [5], [17]] + + - name: horizontal array of positions lifts + args: ['={100001,500005,1700017}'] + expected: [[1, 5, 17]] + + - name: help text + args: [] + expected_type: array diff --git a/lambdas/maps/ROWNUM.lambda b/lambdas/maps/ROWNUM.lambda new file mode 100644 index 0000000..6509594 --- /dev/null +++ b/lambdas/maps/ROWNUM.lambda @@ -0,0 +1,28 @@ +/* FUNCTION NAME: ROWNUM + DESCRIPTION:*//**Returns the row number of an A1-style cell address passed as text. Like ROW() but accepts strings.*/ +/* REVISIONS: Date Developer Description + 2026-05-20 Claude Initial version +*/ +ROWNUM = LAMBDA( +// Parameter Declarations + [address], +// Help + LET(Help, TEXTSPLIT( + "FUNCTION: →ROWNUM(address)¶" & + "DESCRIPTION: →Returns the row number of an A1-style cell address passed as text. Like ROW() but accepts strings.¶" & + "VERSION: →May 20 2026¶" & + "PARAMETERS: →¶" & + " address →(Required) A1 address text (e.g. ""Q17""), or an array/range of such. Sheet prefixes, $ anchors, and ranges are tolerated (first cell used). Result lifts element-wise.¶" & + "EXAMPLE: →¶" & + "Formula →=ROWNUM(""Q17"")¶" & + "Result →17", + "→", "¶" + ), + // Check inputs + Help?, ISOMITTED(address), + // Procedure + safeMult, 100000, + result, INT(CELLTOPOS(address, safeMult) / safeMult), + IF(Help?, Help, result) +) +); diff --git a/lambdas/maps/ROWNUM.tests.yaml b/lambdas/maps/ROWNUM.tests.yaml new file mode 100644 index 0000000..6c467f0 --- /dev/null +++ b/lambdas/maps/ROWNUM.tests.yaml @@ -0,0 +1,48 @@ +tests: + - name: simple address Q17 + args: ["Q17"] + expected: 17 + + - name: top-left A1 + args: ["A1"] + expected: 1 + + - name: multi-letter column AA1 + args: ["AA1"] + expected: 1 + + - name: large row Z99 + args: ["Z99"] + expected: 99 + + - name: absolute reference dollars stripped + args: ["$E$5"] + expected: 5 + + - name: sheet-prefixed address + args: ["Sheet1!E5"] + expected: 5 + + - name: lowercase address + args: ["e5"] + expected: 5 + + - name: cell containing address string + setup: + cells: + - address: "C3" + values: "Q17" + args: ["=C3"] + expected: 17 + + - name: vertical array of addresses lifts + args: ['={"A1";"B5";"Z99"}'] + expected: [[1], [5], [99]] + + - name: horizontal array of addresses lifts + args: ['={"A1","B5","Z99"}'] + expected: [[1, 5, 99]] + + - name: help text + args: [] + expected_type: array diff --git a/lambdas/maps/ROWOF.lambda b/lambdas/maps/ROWOF.lambda new file mode 100644 index 0000000..6f236f9 --- /dev/null +++ b/lambdas/maps/ROWOF.lambda @@ -0,0 +1,30 @@ +/* FUNCTION NAME: ROWOF + DESCRIPTION:*//**Extracts the row number from an encoded position integer (as produced by CELLTOPOS).*/ +/* REVISIONS: Date Developer Description + 2026-05-20 Claude Initial version +*/ +ROWOF = LAMBDA( +// Parameter Declarations + [pos], + [mult], +// Help + LET(Help, TEXTSPLIT( + "FUNCTION: →ROWOF(pos, [mult])¶" & + "DESCRIPTION: →Extracts the row number from an encoded position integer (as produced by CELLTOPOS).¶" & + "VERSION: →May 20 2026¶" & + "PARAMETERS: →¶" & + " pos →(Required) Encoded position (row*mult+col). Also accepts an array/range of positions — result lifts element-wise.¶" & + " mult →(Optional) Multiplier used during encoding. Default: 100000 (matches CELLTOPOS). Must match the mult used to encode pos.¶" & + "EXAMPLE: →¶" & + "Formula →=ROWOF(1700017)¶" & + "Result →17", + "→", "¶" + ), + // Check inputs + Help?, ISOMITTED(pos), + // Procedure + m, IF(ISOMITTED(mult), 100000, mult), + result, INT(pos / m), + IF(Help?, Help, result) +) +); diff --git a/lambdas/maps/ROWOF.tests.yaml b/lambdas/maps/ROWOF.tests.yaml new file mode 100644 index 0000000..8260585 --- /dev/null +++ b/lambdas/maps/ROWOF.tests.yaml @@ -0,0 +1,40 @@ +tests: + - name: simple position Q17 + args: ["=1700017"] + expected: 17 + + - name: position with single-digit row + args: ["=500005"] + expected: 5 + + - name: top-left A1 + args: ["=100001"] + expected: 1 + + - name: composed with CELLTOPOS + args: ['=CELLTOPOS("Q17")'] + expected: 17 + + - name: composed with CELLTOPOS for multi-letter col + args: ['=CELLTOPOS("XFD16384")'] + expected: 16384 + + - name: custom mult 50 + args: ["=526", "=50"] + expected: 10 + + - name: custom mult matches CELLTOPOS custom mult + args: ['=CELLTOPOS("Z10", 50)', "=50"] + expected: 10 + + - name: vertical array of positions lifts + args: ['={100001;500005;1700017}'] + expected: [[1], [5], [17]] + + - name: horizontal array of positions lifts + args: ['={100001,500005,1700017}'] + expected: [[1, 5, 17]] + + - name: help text + args: [] + expected_type: array