diff --git a/lib/apl/runtime.sx b/lib/apl/runtime.sx index ea26c849..ea252013 100644 --- a/lib/apl/runtime.sx +++ b/lib/apl/runtime.sx @@ -873,3 +873,91 @@ (first col) (rest col)))) (range 0 inner-size))))))))) + +(define + apl-scan + (fn + (f arr) + (let + ((shape (get arr :shape)) (ravel (get arr :ravel))) + (if + (= (len shape) 0) + arr + (if + (= (len shape) 1) + (let + ((n (first shape))) + (make-array + shape + (map + (fn + (i) + (let + ((slice (take ravel (+ i 1)))) + (reduce + (fn + (a b) + (disclose (f (apl-scalar a) (apl-scalar b)))) + (first slice) + (rest slice)))) + (range 0 n)))) + (let + ((last-dim (last shape)) + (pre-size (reduce * 1 (take shape (- (len shape) 1))))) + (make-array + shape + (flatten + (map + (fn + (i) + (let + ((start (* i last-dim)) + (row + (map + (fn (j) (nth ravel (+ start j))) + (range 0 last-dim)))) + (map + (fn + (k) + (let + ((slice (take row (+ k 1)))) + (reduce + (fn + (a b) + (disclose (f (apl-scalar a) (apl-scalar b)))) + (first slice) + (rest slice)))) + (range 0 last-dim)))) + (range 0 pre-size)))))))))) + +(define + apl-scan-first + (fn + (f arr) + (let + ((shape (get arr :shape)) (ravel (get arr :ravel))) + (if + (< (len shape) 2) + (apl-scan f arr) + (let + ((first-dim (first shape)) + (inner-size (reduce * 1 (rest shape)))) + (make-array + shape + (flatten + (map + (fn + (i) + (map + (fn + (j) + (let + ((col (map (fn (k) (nth ravel (+ j (* k inner-size)))) (range 0 (+ i 1))))) + (reduce + (fn + (a b) + (disclose (f (apl-scalar a) (apl-scalar b)))) + (first col) + (rest col)))) + (range 0 inner-size))) + (range 0 first-dim))))))))) diff --git a/lib/apl/tests/operators.sx b/lib/apl/tests/operators.sx index 7f5352b7..8b2be5cc 100644 --- a/lib/apl/tests/operators.sx +++ b/lib/apl/tests/operators.sx @@ -82,4 +82,79 @@ "reduce-first max/ matrix col maxima" (rv (apl-reduce-first apl-max (make-array (list 3 2) (list 1 9 2 8 3 7)))) - (list 3 9)) \ No newline at end of file + (list 3 9)) + +(apl-test + "scan +\\ vector" + (rv (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5)))) + (list 1 3 6 10 15)) + +(apl-test + "scan x\\ vector cumulative product" + (rv (apl-scan apl-mul (make-array (list 5) (list 1 2 3 4 5)))) + (list 1 2 6 24 120)) + +(apl-test + "scan max\\ vector running max" + (rv (apl-scan apl-max (make-array (list 5) (list 3 1 4 1 5)))) + (list 3 3 4 4 5)) + +(apl-test + "scan min\\ vector running min" + (rv (apl-scan apl-min (make-array (list 5) (list 3 1 4 1 5)))) + (list 3 1 1 1 1)) + +(apl-test + "scan +\\ single element" + (rv (apl-scan apl-add (make-array (list 1) (list 42)))) + (list 42)) + +(apl-test + "scan +\\ scalar no-op" + (rv (apl-scan apl-add (apl-scalar 7))) + (list 7)) + +(apl-test + "scan +\\ vector preserves shape" + (sh (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5)))) + (list 5)) + +(apl-test + "scan +\\ matrix preserves shape" + (sh (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6)))) + (list 2 3)) + +(apl-test + "scan +\\ matrix row-wise" + (rv (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6)))) + (list 1 3 6 4 9 15)) + +(apl-test + "scan max\\ matrix row-wise running max" + (rv (apl-scan apl-max (make-array (list 2 3) (list 3 1 4 1 5 9)))) + (list 3 3 4 1 5 9)) + +(apl-test + "scan-first +\\ vector same as scan" + (rv (apl-scan-first apl-add (make-array (list 5) (list 1 2 3 4 5)))) + (list 1 3 6 10 15)) + +(apl-test + "scan-first +\\ scalar no-op" + (rv (apl-scan-first apl-add (apl-scalar 9))) + (list 9)) + +(apl-test + "scan-first +\\ matrix preserves shape" + (sh (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6)))) + (list 2 3)) + +(apl-test + "scan-first +\\ matrix col-wise" + (rv (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6)))) + (list 1 2 3 5 7 9)) + +(apl-test + "scan-first max\\ matrix col-wise running max" + (rv (apl-scan-first apl-max (make-array (list 3 2) (list 3 1 4 1 5 9)))) + (list 3 1 4 1 5 9)) \ No newline at end of file diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index 69e14f9a..8920b8c5 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -74,7 +74,7 @@ Core mapping: ### Phase 4 — operators (THE SHOWCASE) - [x] Reduce `f/` (last axis), `f⌿` (first axis) — including `∧/`, `∨/`, `+/`, `×/`, `⌈/`, `⌊/` -- [ ] Scan `f\`, `f⍀` +- [x] Scan `f\`, `f⍀` - [ ] Each `f¨` — applies `f` to each scalar/element - [ ] Outer product `∘.f` — `1 2 3 ∘.× 1 2 3` ↦ multiplication table - [ ] Inner product `f.g` — `+.×` is matrix multiply @@ -118,6 +118,7 @@ data; format for string templating. _Newest first._ +- 2026-05-06: Phase 4 step 2 — scan f\ (last axis) + f⍀ (first axis); apl-scan/apl-scan-first; 125/125 tests - 2026-05-06: Phase 4 step 1 — reduce f/ (last axis) + f⌿ (first axis); apl-reduce/apl-reduce-first; 110/110 tests - 2026-05-06: Phase 3 complete — membership ∊, dyadic ⍳ (index-of), without ~ (index-of returns nil for not-found); 94/94 tests - 2026-05-06: Phase 3 step 6 — enclose ⊂ / disclose ⊃ (box/unbox, rank-0 detect via type-of); 82/82 tests