basedpl 0.1.7__tar.gz → 0.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {basedpl-0.1.7 → basedpl-0.1.8}/Cargo.lock +1 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/Cargo.toml +1 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/DEV.md +2 -2
- {basedpl-0.1.7 → basedpl-0.1.8}/PKG-INFO +4 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/colon.md +10 -7
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/dot.md +1 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/execute.md +4 -3
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/iota.md +3 -2
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/mix.md +3 -3
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/parentheses.md +0 -7
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/index.md +5 -5
- basedpl-0.1.8/docs/keyed.md +158 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/processes.md +1 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/python.md +4 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/pyproject.toml +2 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/__init__.py +23 -6
- {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/apltests.py +2 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/reference.py +1 -1
- basedpl-0.1.8/src/agreement.rs +109 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/array.rs +149 -42
- {basedpl-0.1.7 → basedpl-0.1.8}/src/display.rs +4 -3
- {basedpl-0.1.7 → basedpl-0.1.8}/src/eval.rs +123 -137
- basedpl-0.1.8/src/keyed.rs +174 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/number_theory.rs +8 -6
- {basedpl-0.1.7 → basedpl-0.1.8}/src/polynomial.rs +25 -15
- {basedpl-0.1.7 → basedpl-0.1.8}/src/primitive.rs +324 -300
- {basedpl-0.1.7 → basedpl-0.1.8}/src/protocol.rs +20 -7
- {basedpl-0.1.7 → basedpl-0.1.8}/src/python.rs +28 -4
- {basedpl-0.1.7 → basedpl-0.1.8}/src/selection.rs +8 -3
- {basedpl-0.1.7 → basedpl-0.1.8}/src/symbols.rs +1 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/syntax.rs +3 -29
- {basedpl-0.1.7 → basedpl-0.1.8}/src/system.rs +3 -2
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/core.rs +12 -4
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/README.md +1 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/aplcart.apl +17 -6
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/april.apl +4 -5
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/core.apl +366 -272
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/aplcart.jsonl +3 -3
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference.rs +1 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_api.py +21 -6
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_apltests.py +5 -1
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_worker.py +9 -8
- basedpl-0.1.7/docs/keyed.md +0 -145
- basedpl-0.1.7/src/agreement.rs +0 -55
- basedpl-0.1.7/src/keyed.rs +0 -132
- {basedpl-0.1.7 → basedpl-0.1.8}/.cargo/config.toml +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/.github/workflows/ci.yml +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/.gitignore +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/LICENSE +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/README.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/_config.yml +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/_layouts/default.html +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/cli.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/getting-started.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/agenda.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/alpha-underbar.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/alpha.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/alphabet.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/and.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/assign.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/at.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/backslash-bar.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/backslash.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/behind.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/braces.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/brackets.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/bullet.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/case.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/ceiling.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/circle.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/comma.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/comment.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/commute.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/compose.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/decode.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/del-diaeresis.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/del.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/derivative.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/diamond.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/digits.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/divide.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/domino.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/drop.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/each.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/enclose.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/encode.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/equal.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/error-guard.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/factor.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/factorial.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/find.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/floor.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/format.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/grade-down.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/grade-up.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/greater-or-equal.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/greater.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/infinity.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/intersection.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/inverse-pair.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/key.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/left.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/less-or-equal.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/less.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/load.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/log.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/match.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/member.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/minus.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/nand.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/nest.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/nor.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/not-equal.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/omega-underbar.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/omega.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/or.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/outer-product.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/over.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/overbar.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/pi.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/pipe.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/plus.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/polynomial.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/power.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/prime.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/quad.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/question.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/quote.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/rank.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/reverse-first.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/reverse.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/rho.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/right.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/root.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/semicolon.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/slash-bar.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/slash.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/squad.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/star.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/stencil.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/stile.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/strand.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/table.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/take.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/tally.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/tilde.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/times.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/transpose.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/under.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/unicode.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/union.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/where.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/windows.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/zilde.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/keyboard.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/repl.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/docs/rules.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/editors/vim/ftdetect/apl.vim +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/editors/vim/syntax/apl.vim +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/editors/vim/test.vim +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/LICENSE-april +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/README.md +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/array.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/dyalog.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/graph.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/numeric.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/power.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/string.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/lib/tree.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/_cli.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/functions.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/keyboard.json +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/worker.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/rustfmt.toml +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/scripts/reference.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/cli.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/editor.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/error.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/execution.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/kernel.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/lib.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/main.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/number.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/reference.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/src/worker.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/cli.rs +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-aplcart +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-april +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-dyalog +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-ngn +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/dyalog.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/april.jsonl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/core.jsonl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/dyalog.jsonl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/ngn.jsonl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/ngn.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/slow.apl +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_basic.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_kernel.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_reference.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_repl.py +0 -0
- {basedpl-0.1.7 → basedpl-0.1.8}/wheel/data/share/jupyter/kernels/apl/kernel.json +0 -0
|
@@ -34,8 +34,8 @@ CI uses the development profile for Rust and Python tests. Distribution wheels u
|
|
|
34
34
|
- `lib/*.apl`: Dyalog dfns adapted from April and the Dyalog dfns workspace. Reference cases load these shared definitions with `•LOAD`; case-specific setup stays in each test. See `lib/README.md` for usage and provenance.
|
|
35
35
|
- `array.rs`: ordinary `Value` atoms and immutable shared arrays; checked construction, recursive prototypes, axis offsets, direct/mapped result frames, cell descriptors, padded cell assembly and row-based display. Arrays retain function handles and cache lexical dependencies without owning lexical frames or Python objects.
|
|
36
36
|
- `number.rs`: Integer/Exact/Float/Complex values, normalization, checked arithmetic, promotion, comparison and structural conversion. `i64` and BigRational share one exact domain. Checked integer overflow falls back to BigRational; integral rational results return to `i64` when they fit. Representation is private. Complex values use `num_complex::Complex64`, normalizing exactly zero imaginary parts to Float.
|
|
37
|
-
- `agreement.rs`:
|
|
38
|
-
- `keyed.rs`:
|
|
37
|
+
- `agreement.rs`: positional broadcasting and key union through one output layout and two index maps. Scalar functions, Each, rank frames, native mathematical functions and explicit scalar axes use this path. Missing entries use prototype fill. Compact numeric kernels read the maps directly. Equal-shape and repeated-block mappings avoid coordinate calculations and expanded input copies. Contracted-axis maps require equal key sets and retain the first axis's order.
|
|
38
|
+
- `keyed.rs`: axis-label construction, lookup and named updates. Each immutable `Keys` holds unique strings and a name-to-position hash. `Layout` in `array.rs` owns dimensions and keys together. Its axis selection, concatenation and replacement operations describe structural results. Frames and cells carry layouts into assembly, which retains cell-axis labels shared by all result cells. Dot access is a binder rewrite to Pick in `eval.rs`. The design is in `meta/axiskey.md`.
|
|
39
39
|
- `primitive.rs`: primitive identities/glyphs, valences and array-level implementations. Replicate, products and assignment retain their own agreement rules. Allocation caps are separate from numeric-to-integer conversion.
|
|
40
40
|
- `number_theory.rs`: segmented prime enumeration, Miller–Rabin primality and Brent/Pollard–rho factorisation; exact integer results and scalar-cell assembly.
|
|
41
41
|
- `system.rs`: case-insensitive `•Name` table for constant arrays and native functions. System functions use ordinary function nodes and application. `primitive.rs` contains single-character primitives.
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: basedpl
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Requires-Dist: fastship>=0.0.11 ; extra == 'dev'
|
|
7
7
|
Requires-Dist: maturin>=1.0,<2.0 ; extra == 'dev'
|
|
8
8
|
Requires-Dist: pytest ; extra == 'dev'
|
|
9
9
|
Requires-Dist: numpy ; extra == 'dev'
|
|
10
|
+
Requires-Dist: pandas ; extra == 'dev'
|
|
10
11
|
Requires-Dist: conkernelclient>=0.0.23 ; extra == 'dev'
|
|
12
|
+
Requires-Dist: pandas ; extra == 'pandas'
|
|
11
13
|
Provides-Extra: dev
|
|
14
|
+
Provides-Extra: pandas
|
|
12
15
|
License-File: LICENSE
|
|
13
16
|
Summary: bAsedPL is an APL-derived array language, borrowing ideas from J and BQN, with an emphasis on simple, consistent notation.
|
|
14
17
|
Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
# `:` — Guard
|
|
1
|
+
# `:` — Unkey / Key / Guard
|
|
2
2
|
|
|
3
|
-
`
|
|
3
|
+
`:Y` removes axis keys. `K:Y` labels leading axes. `K:[axes]Y` labels selected axes. See [axis keys](../keyed.md).
|
|
4
4
|
|
|
5
5
|
```apl
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
:('aa' 'bb':1 2) ⍝ 1 2
|
|
7
|
+
'bb'⊃'aa' 'bb':1 2 ⍝ 2
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Inside a dfn, group functional uses: `(K:Y)` or `(:Y)`.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
`condition:result` returns `result` when the condition is true. Otherwise execution continues. Only the chosen result evaluates.
|
|
13
13
|
|
|
14
14
|
```apl
|
|
15
|
-
|
|
15
|
+
{⍵<0:-⍵ ⋄ ⍵}¯4 ⍝ 4
|
|
16
|
+
{⍵=0:0 ⋄ ÷⍵}0 ⍝ 0
|
|
16
17
|
```
|
|
18
|
+
|
|
19
|
+
Condition: Boolean singleton. An empty selected result returns no value. See [error guards](error-guard.md).
|
|
@@ -12,7 +12,7 @@ Singleton contraction axes extend. Empty contractions use the reduction identity
|
|
|
12
12
|
After an array, `.name` reads a [keyed array](../keyed.md) value: `T.name` is `'name'⊃T`. It also works as an assignment target.
|
|
13
13
|
|
|
14
14
|
```apl
|
|
15
|
-
T←('n':1
|
|
15
|
+
T←('n':1),('addr':'city':'Paris')
|
|
16
16
|
T.addr.city ⍝ 'Paris'
|
|
17
17
|
T.n+←1 ⋄ T.n ⍝ 2
|
|
18
18
|
```
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
x←7 ⋄ ⍎'x+1' ⍝ 8
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
`X⍎Y`
|
|
10
|
+
`X⍎Y` selects `Y⊃X`, using numeric coordinates or axis keys.
|
|
11
11
|
|
|
12
12
|
```apl
|
|
13
|
-
T←
|
|
14
|
-
T⍎'
|
|
13
|
+
T←'aa' 'bb':10 20
|
|
14
|
+
T⍎'bb' ⍝ 20
|
|
15
|
+
10 20⍎2 ⍝ 20
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
Assignments and explicit output take effect in the current session.
|
|
@@ -13,10 +13,11 @@ A shape vector generates an array of coordinate vectors.
|
|
|
13
13
|
⍳2 2 ⍝ 2 2⍴(1 1⋄ 1 2⋄ 2 1⋄ 2 2)
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
`⍳[axis]Y` returns that axis's keys, or `1…length` on an unkeyed axis. Several axes return a vector of selector vectors.
|
|
17
17
|
|
|
18
18
|
```apl
|
|
19
|
-
⍳('price'
|
|
19
|
+
⍳[1]('price' 'qty':1 2) ⍝ 'price' 'qty'
|
|
20
|
+
⍳[2]2 3⍴0 ⍝ 1x 2x 3x
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
`X⍳Y` finds the first matching major cell in `X` for each cell of `Y`. Not found: `1+≢X`. Uses tolerant matching and returns exact positions.
|
|
@@ -22,12 +22,12 @@ Array-valued coordinate fields pair using leading-axis agreement:
|
|
|
22
22
|
(1 3)˘(2 4)⊃3 4⍴⍳12 ⍝ 2 12
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Coordinate fields may use strings on [keyed axes](../keyed.md).
|
|
26
26
|
|
|
27
27
|
```apl
|
|
28
|
-
T←
|
|
28
|
+
T←'price' 'qty':(1 2 3 ⋄ 4 5 6)
|
|
29
29
|
'qty'⊃T ⍝ 4 5 6
|
|
30
|
-
('qty' 'price')⊃T
|
|
30
|
+
(⊂'qty' 'price')⊃T ⍝ (4 5 6 ⋄ 1 2 3)
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
On Mix, axes place cell dimensions among frame dimensions; fractional positions insert before an axis. INDEX: position outside the array. Index 1 on an empty axis gives fill, as does [First](take.md).
|
|
@@ -14,13 +14,6 @@ Separators construct nested vectors. A trailing separator makes a singleton.
|
|
|
14
14
|
(1 2 ⋄ 3 4) ⍝ (1 2)(3 4)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`key:value` entries construct a [keyed array](../keyed.md). `()` is the empty keyed vector.
|
|
18
|
-
|
|
19
|
-
```apl
|
|
20
|
-
≢('a':1 ⋄ 'b':2) ⍝ 2x
|
|
21
|
-
≢() ⍝ 0x
|
|
22
|
-
```
|
|
23
|
-
|
|
24
17
|
Two functions form an atop: `(f g)Y` is `f(g Y)`.
|
|
25
18
|
|
|
26
19
|
```apl
|
|
@@ -22,7 +22,7 @@ bAsedPL distinguishes two application rules. Structural mapping (arithmetic, Eac
|
|
|
22
22
|
- [Python](python.md): sessions, arrays, NumPy and callable APL functions.
|
|
23
23
|
- [Process interfaces](processes.md): JSON lines and interruptible workers.
|
|
24
24
|
- [Language rules](rules.md): evaluation, numbers, agreement, axes and fill.
|
|
25
|
-
- [
|
|
25
|
+
- [Axis keys](keyed.md): named rows and columns, lookup, agreement, assignment and Python conversion.
|
|
26
26
|
|
|
27
27
|
## Glyph reference
|
|
28
28
|
|
|
@@ -75,7 +75,7 @@ Each name below links to its definitions and examples. Examples show an equivale
|
|
|
75
75
|
| `⊆` [Nest](glyphs/nest.md) | Nest | Partition | |
|
|
76
76
|
| `⊃` [Mix](glyphs/mix.md) | Mix | Pick | Monad as Dyalog `⎕ML≥2`. Pick takes cells: `2⊃2 3⍴⍳6` is `4 5 6`. A string picks by [key](keyed.md) |
|
|
77
77
|
| `⌷` [Squad](glyphs/squad.md) | Identity | Index | |
|
|
78
|
-
| `⍳` [Iota](glyphs/iota.md) | Index generator | Index of |
|
|
78
|
+
| `⍳` [Iota](glyphs/iota.md) | Index generator | Index of | `⍳[axes]Y` returns axis selectors |
|
|
79
79
|
| `⍸` [Where](glyphs/where.md) | Where | Interval index | |
|
|
80
80
|
| `∊` [Member](glyphs/member.md) | Enlist | Membership | |
|
|
81
81
|
| `∪` [Union](glyphs/union.md) | Unique | Union | |
|
|
@@ -129,14 +129,14 @@ Each name below links to its definitions and examples. Examples show an equivale
|
|
|
129
129
|
| `˘` [Strand](glyphs/strand.md) | Form a vector of values: `1˘+˘'abc'` |
|
|
130
130
|
| `←` [Assign](glyphs/assign.md) | Assignment, including modified/indexed/selective forms |
|
|
131
131
|
| `→` [Pipe](glyphs/pipe.md) | Left-to-right function application |
|
|
132
|
-
| `(…)` [Parentheses](glyphs/parentheses.md) | Grouping / nested array literals /
|
|
132
|
+
| `(…)` [Parentheses](glyphs/parentheses.md) | Grouping / nested array literals / trains |
|
|
133
133
|
| `[…]` [Brackets](glyphs/brackets.md) | Enclosure, array literals, indexing and axes |
|
|
134
134
|
| `;` [Semicolon](glyphs/semicolon.md) | Index-axis separator |
|
|
135
135
|
| `{…}` [Braces](glyphs/braces.md) | Defined function or operator |
|
|
136
136
|
| `⍺` [Alpha](glyphs/alpha.md), `⍵` [Omega](glyphs/omega.md) | Left / right argument |
|
|
137
137
|
| `⍶` [Alpha underbar](glyphs/alpha-underbar.md), `⍹` [Omega underbar](glyphs/omega-underbar.md) | Left / right operand |
|
|
138
138
|
| `∇` [Del](glyphs/del.md), `⍢` [Del diaeresis](glyphs/del-diaeresis.md) | Function / operator self-reference |
|
|
139
|
-
| `:` [Colon](glyphs/colon.md), `::` [Error guard](glyphs/error-guard.md) |
|
|
139
|
+
| `:` [Colon](glyphs/colon.md), `::` [Error guard](glyphs/error-guard.md) | Unkey / key axes; Boolean guard / error guard inside dfns |
|
|
140
140
|
| `⋄` [Diamond](glyphs/diamond.md) | Statement or literal separator |
|
|
141
141
|
| `⍝` [Comment](glyphs/comment.md) | Comment |
|
|
142
142
|
| `⎕←` [Quad](glyphs/quad.md) | Explicit output |
|
|
@@ -169,4 +169,4 @@ Index origin: 1. Comparison tolerance: `1E¯14`.
|
|
|
169
169
|
- [Numbers](rules.md#numbers): bare numbers are approximate. `x` and `r` mark exact integers and rationals, as J: `1x÷3x` is `1r3`. Predicates, positions, tally and shape are exact.
|
|
170
170
|
- [Agreement](rules.md#agreement-and-pervasion): scalar functions, Each and Rank align leading axes, and unit dimensions expand: `(2 3⍴⍳6)+10 20` is `2 3⍴11 12 13 24 25 26`.
|
|
171
171
|
- [System names](#system-names): written `•NAME`, as BQN, not `⎕NAME`. Most Dyalog system functions and variables are not included.
|
|
172
|
-
-
|
|
172
|
+
- [Axis keys](keyed.md) name data. `K:Y` evaluates its keys. `T.a` is `'a'⊃T`, and `X⍎Y` is `Y⊃X`.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Axis keys
|
|
2
|
+
|
|
3
|
+
[Glyph index](index.md#glyph-reference)
|
|
4
|
+
|
|
5
|
+
An array can have string keys on any of its axes. Keys name positions on that axis. Numeric coordinates remain 1-origin. Array operations retain their ordinary meaning.
|
|
6
|
+
|
|
7
|
+
## Construction
|
|
8
|
+
|
|
9
|
+
One string names the whole right value. Several strings name its leading-axis cells.
|
|
10
|
+
|
|
11
|
+
```apl
|
|
12
|
+
T←'price' 'qty':(1 2 3 ⋄ 4 5 6)
|
|
13
|
+
≢T ⍝ 2x
|
|
14
|
+
⍴('ab':11 12 13) ⍝ ,1x
|
|
15
|
+
'price' 'qty':2 3⍴⍳6 ⍝ ('price' 'qty':[1]2 3⍴⍳6)
|
|
16
|
+
≢⍬:⍬ ⍝ 0x
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A character atom or vector is one string key. Use `,¨'ab'` for two one-character keys.
|
|
20
|
+
|
|
21
|
+
```apl
|
|
22
|
+
⍳[1]('a' 'b':1 2) ⍝ ,⊂'ab'
|
|
23
|
+
⍳[1]((,¨'ab'):1 2) ⍝ ,¨'ab'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Several key vectors label successive axes. `[axes]` chooses which axes to label and always attaches to existing dimensions.
|
|
27
|
+
|
|
28
|
+
```apl
|
|
29
|
+
M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
|
|
30
|
+
⍴M ⍝ 2x 2x
|
|
31
|
+
⍳[1]M ⍝ 'alice' 'bob'
|
|
32
|
+
⍳[2]M ⍝ 'price' 'qty'
|
|
33
|
+
:M ⍝ 2 2⍴10 2 20 4
|
|
34
|
+
⍳[2](:[2]M) ⍝ 1x 2x
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Monadic `:` removes axis keys. Inside dfns, group functional colon: `(X:Y)` or `(:Y)`. A direct colon separates a guard.
|
|
38
|
+
|
|
39
|
+
Keys must be unique strings on each axis: DOMAIN. A key-list length must match its axis: LENGTH.
|
|
40
|
+
|
|
41
|
+
## Selection and assignment
|
|
42
|
+
|
|
43
|
+
A scalar selector removes its axis. A vector selector retains it. A character vector selects one string key.
|
|
44
|
+
|
|
45
|
+
```apl
|
|
46
|
+
M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
|
|
47
|
+
M['bob'] ⍝ 'price' 'qty':20 4
|
|
48
|
+
M[;'qty'] ⍝ 'alice' 'bob':2 4
|
|
49
|
+
M['alice';'qty'] ⍝ 2
|
|
50
|
+
M[,⊂'alice'] ⍝ ('alice':[1]('price' 'qty':[2]1 2⍴10 2))
|
|
51
|
+
('bob' 'qty')⊃M ⍝ 4
|
|
52
|
+
('bob' 'price')⌷M ⍝ 20
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Dot names are literal keys. `T.a.b` is `'b'⊃'a'⊃T`.
|
|
56
|
+
|
|
57
|
+
```apl
|
|
58
|
+
T←('n':1),('addr':'city':'Paris')
|
|
59
|
+
T.addr.city ⍝ 'Paris'
|
|
60
|
+
T.n+←1 ⋄ T.n ⍝ 2
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Plain named assignment appends missing rows or columns. New cells receive the array's prototype before assignment.
|
|
64
|
+
|
|
65
|
+
```apl
|
|
66
|
+
M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
|
|
67
|
+
M['cara']←30 5
|
|
68
|
+
M['cara';'qty'] ⍝ 5
|
|
69
|
+
M[;'tax']←1 2 3
|
|
70
|
+
M['alice';'tax'] ⍝ 1
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Missing keys on read or modified assignment: INDEX. Repeated selections on a keyed axis: DOMAIN. Numeric out-of-range assignment: INDEX.
|
|
74
|
+
|
|
75
|
+
Scalar selection stores the whole RHS. A vector selection aligns its retained axis.
|
|
76
|
+
|
|
77
|
+
```apl
|
|
78
|
+
T←'aa' 'bb':1 2 ⋄ U←'bb' 'aa':8 9
|
|
79
|
+
T['aa']←U ⋄ T['aa'] ⍝ 'bb' 'aa':8 9
|
|
80
|
+
T[,⊂'aa']←U ⋄ T['aa'] ⍝ 9
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Agreement
|
|
84
|
+
|
|
85
|
+
Corresponding keyed axes align by union: left keys, then right-only keys. An absent counterpart receives the present value's prototype.
|
|
86
|
+
|
|
87
|
+
```apl
|
|
88
|
+
('aa':2)+('bb':3) ⍝ 'aa' 'bb':2 3
|
|
89
|
+
('aa':2)×('bb':3) ⍝ 'aa' 'bb':0 0
|
|
90
|
+
('aa':0)=('bb':0) ⍝ 'aa' 'bb':1x 1x
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With one axis unkeyed, agreement is positional. Expanding a keyed singleton drops that axis's labels. Enclosure broadcasts within the named value.
|
|
94
|
+
|
|
95
|
+
```apl
|
|
96
|
+
('base':5)+10 20 30 ⍝ 15 25 35
|
|
97
|
+
('base':5)+⊂10 20 30 ⍝ 'base':15 25 35
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Explicit axes choose the correspondence.
|
|
101
|
+
|
|
102
|
+
```apl
|
|
103
|
+
M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
|
|
104
|
+
M+[2]('qty' 'tax':10 2) ⍝ ('alice' 'bob' ⋄ 'price' 'qty' 'tax'):2 3⍴10 12 2 20 14 2
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Array operations
|
|
108
|
+
|
|
109
|
+
Transpose moves axes with their labels. Reverse, rotate and filtering move labels with their positions. Reduction removes the reduced axis. Scan retains it. Shape-changing reshape drops keys.
|
|
110
|
+
|
|
111
|
+
```apl
|
|
112
|
+
M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
|
|
113
|
+
+/M ⍝ 'alice' 'bob':12 24
|
|
114
|
+
+⌿M ⍝ 'price' 'qty':30 6
|
|
115
|
+
4⍴'aa' 'bb':1 2 ⍝ 1 2 1 2
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Rank preserves frame labels. Assembled cell axes keep labels shared by every result cell in the same order. Outer product retains both arguments' axes.
|
|
119
|
+
|
|
120
|
+
Per-cell rotation drops labels on the rotated axis when its cells use different rotations. Other axes retain their labels.
|
|
121
|
+
|
|
122
|
+
Inner product, Decode and matrix solve align keyed contracted axes by name. Key sets must match: LENGTH. With either axis unkeyed, contraction is positional.
|
|
123
|
+
|
|
124
|
+
```apl
|
|
125
|
+
('hi' 'lo':1 2)+.×('lo' 'hi':10 20) ⍝ 40
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Catenate joins positions. Duplicate joined keys give DOMAIN. Replicate may filter a keyed axis, but repetition or padding on that axis gives DOMAIN.
|
|
129
|
+
|
|
130
|
+
## Match and search
|
|
131
|
+
|
|
132
|
+
Match aligns keys independently on every keyed axis. Shapes and unkeyed axes must match positionally. Searches compare values or cells. Found positions return axis keys.
|
|
133
|
+
|
|
134
|
+
```apl
|
|
135
|
+
V←'low' 'mid' 'high':10 20 30
|
|
136
|
+
V≡⌽V ⍝ 1x
|
|
137
|
+
V⍳20 99 ⍝ ('mid' ⋄ 4x)
|
|
138
|
+
V⍸5 15 25 40 ⍝ (0x ⋄ 'low' ⋄ 'mid' ⋄ 'high')
|
|
139
|
+
⍒V ⍝ 'high' 'mid' 'low'
|
|
140
|
+
('aa':7)∊('bb':7) ⍝ 'aa':1x
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Monadic Key passes named positions to its operand. Dyadic Key preserves labels on the grouped values.
|
|
144
|
+
|
|
145
|
+
```apl
|
|
146
|
+
V←'aa' 'bb' 'cc':1 2 1
|
|
147
|
+
{⊂⍵}⌸V ⍝ (('aa' 'cc') ⋄ ,⊂'bb')
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Python and JSON
|
|
151
|
+
|
|
152
|
+
Python dicts become keyed vectors recursively. `Array(data, axis_keys=[rows, cols])` attaches labels to existing axes. Use `None` for an unkeyed axis.
|
|
153
|
+
|
|
154
|
+
`.axis_keys` returns a tuple of label tuples or `None`. `.np` copies values to an ndarray. `.py` returns a dict for a keyed vector and a pandas DataFrame for keyed arrays of rank ≥2.
|
|
155
|
+
|
|
156
|
+
`.df` converts any rank to a DataFrame. The last axis supplies columns. Earlier axes supply rows, using a MultiIndex above rank 2. Unkeyed axes use 1-origin labels. Pandas is an optional, lazy dependency: `pip install 'basedpl[pandas]'`.
|
|
157
|
+
|
|
158
|
+
The [process protocol](processes.md) adds `axis_keys`, e.g. `[null, ["price", "qty"]]`. All-unkeyed arrays omit it.
|
|
@@ -19,7 +19,7 @@ The second response is:
|
|
|
19
19
|
|
|
20
20
|
Use `json.dumps(code)` or `JSON.stringify(code)`, followed by a newline. Quotes are required; source newlines are escaped inside the string. Stdout contains only responses. The session remains usable after errors. EOF ends it.
|
|
21
21
|
|
|
22
|
-
Atoms are encoded directly. Arrays carry shape, row-major data and prototype, including rank-zero arrays. Nested elements use the same encoding. A [keyed array](keyed.md) adds `
|
|
22
|
+
Atoms are encoded directly. Arrays carry shape, row-major data and prototype, including rank-zero arrays. Nested elements use the same encoding. A [keyed array](keyed.md) adds `axis_keys`: one string list or `null` per axis. All-unkeyed arrays omit it.
|
|
23
23
|
|
|
24
24
|
| Element | JSON |
|
|
25
25
|
|---|---|
|
|
@@ -38,6 +38,10 @@ Use `with Session() as apl:` or call `apl.close()` when finished. Returned array
|
|
|
38
38
|
|
|
39
39
|
`Array` retains atoms, shape, nesting, numeric domain and empty prototypes. `.is_atom` distinguishes an atom from a rank-zero array; both have `.shape == ()`. `.apl` is APL display. `.py` converts atoms to Python values and character vectors to strings. Other arrays become NumPy arrays, including rank-zero arrays. `.np` always returns an ndarray.
|
|
40
40
|
|
|
41
|
+
For [axis-keyed arrays](keyed.md), `.py` returns a dict at rank 1 and a pandas DataFrame at higher ranks. `.np` copies the values. Attach labels with `Array(data, axis_keys=[rows, cols])`; use `None` for an unkeyed axis. `.axis_keys` returns a tuple of label tuples or `None`.
|
|
42
|
+
|
|
43
|
+
`.df` converts any array to a DataFrame. The last axis supplies columns; earlier axes supply rows, using a MultiIndex above rank 2. Unkeyed axes have 1-origin labels. Install `basedpl[pandas]` for this conversion. Pandas is imported on demand.
|
|
44
|
+
|
|
41
45
|
```python
|
|
42
46
|
from fractions import Fraction
|
|
43
47
|
from basedpl import Array
|
|
@@ -17,7 +17,8 @@ classifiers = [
|
|
|
17
17
|
]
|
|
18
18
|
|
|
19
19
|
[project.optional-dependencies]
|
|
20
|
-
|
|
20
|
+
pandas = ["pandas"]
|
|
21
|
+
dev = ["fastship>=0.0.11", "maturin>=1.0,<2.0", "pytest", "numpy", "pandas", "conkernelclient>=0.0.23"]
|
|
21
22
|
|
|
22
23
|
[project.scripts]
|
|
23
24
|
bapl = "basedpl._cli:main"
|
|
@@ -24,10 +24,11 @@ def _value(raw, as_array=False, session=None):
|
|
|
24
24
|
if not as_array: return result
|
|
25
25
|
import numpy as np
|
|
26
26
|
return np.array(result, dtype=_dtype([result]))
|
|
27
|
+
if not as_array and 'axis_keys' in raw and len(raw['shape']) > 1: return _dataframe(raw, session)
|
|
27
28
|
shape, data = tuple(raw['shape']), [item(o) for o in raw['data']]
|
|
28
29
|
items = raw['data'] or [raw['prototype']]
|
|
29
30
|
nested = any(isinstance(o, dict) and 'shape' in o for o in items)
|
|
30
|
-
if not as_array and '
|
|
31
|
+
if not as_array and 'axis_keys' in raw: return dict(zip(raw['axis_keys'][0], data))
|
|
31
32
|
if not as_array:
|
|
32
33
|
if len(shape) == 1 and all(isinstance(o, str) for o in items): return ''.join(data)
|
|
33
34
|
import numpy as np
|
|
@@ -37,6 +38,17 @@ def _value(raw, as_array=False, session=None):
|
|
|
37
38
|
for i,o in enumerate(data): result[i] = o
|
|
38
39
|
return result.reshape(shape)
|
|
39
40
|
|
|
41
|
+
def _dataframe(raw, session=None):
|
|
42
|
+
try: import pandas as pd
|
|
43
|
+
except ImportError: raise ImportError('DataFrame conversion requires pandas: pip install "basedpl[pandas]"') from None
|
|
44
|
+
data = _value(raw, as_array=True, session=session)
|
|
45
|
+
shape = data.shape
|
|
46
|
+
keys = raw.get('axis_keys', [None] * len(shape))
|
|
47
|
+
labels = [list(range(1, n+1)) if k is None else k for n,k in zip(shape, keys)]
|
|
48
|
+
if len(shape) < 2: return pd.DataFrame(data.reshape(-1, 1), index=labels[0] if labels else [1], columns=[1])
|
|
49
|
+
index = labels[0] if len(shape) == 2 else pd.MultiIndex.from_product(labels[:-1])
|
|
50
|
+
return pd.DataFrame(data.reshape(math.prod(shape[:-1]), shape[-1]), index=index, columns=labels[-1])
|
|
51
|
+
|
|
40
52
|
def _element(value, seen):
|
|
41
53
|
np = sys.modules.get('numpy')
|
|
42
54
|
if np is not None and isinstance(value, np.generic): value = value.item()
|
|
@@ -69,7 +81,7 @@ def _array(value, seen=None):
|
|
|
69
81
|
if isinstance(value, dict):
|
|
70
82
|
if not all(isinstance(k, str) for k in value): raise TypeError('keyed arrays need string keys')
|
|
71
83
|
seen.add(id(value))
|
|
72
|
-
try: return _Array(dict(shape=[len(value)], data=[_element(o, seen) for o in value.values()], prototype=prototype,
|
|
84
|
+
try: return _Array(dict(shape=[len(value)], data=[_element(o, seen) for o in value.values()], prototype=prototype, axis_keys=[list(value)]))
|
|
73
85
|
finally: seen.remove(id(value))
|
|
74
86
|
if isinstance(value, str): return _Array(dict(shape=[len(value)], data=list(value), prototype=' '))
|
|
75
87
|
if np is not None and isinstance(value, np.ndarray):
|
|
@@ -127,9 +139,9 @@ def _array_repr(raw):
|
|
|
127
139
|
def cells(dims):
|
|
128
140
|
if not dims: return item(next(data))
|
|
129
141
|
return '[' + ', '.join(cells(dims[1:]) for _ in range(dims[0])) + ']'
|
|
130
|
-
if '
|
|
131
|
-
|
|
132
|
-
return
|
|
142
|
+
if 'axis_keys' in raw:
|
|
143
|
+
if len(shape) == 1: return '{' + ', '.join(f'{k!r}: {item(o)}' for k,o in zip(raw['axis_keys'][0], raw['data'])) + '}'
|
|
144
|
+
return f'Array({cells(shape)}, axis_keys={raw["axis_keys"]!r})'
|
|
133
145
|
if math.prod(shape) == 0: return f'Array([], shape={tuple(shape)})'
|
|
134
146
|
return cells(shape)
|
|
135
147
|
|
|
@@ -157,18 +169,23 @@ def _context(*values):
|
|
|
157
169
|
class Array(_Operators):
|
|
158
170
|
"An immutable native APL value. Conversion to Python or NumPy makes a copy."
|
|
159
171
|
__slots__ = ('_inner', '_session')
|
|
160
|
-
def __init__(self, value):
|
|
172
|
+
def __init__(self, value, *, axis_keys=None):
|
|
161
173
|
self._inner = _array(value)
|
|
174
|
+
if axis_keys is not None: self._inner = self._inner.with_axis_keys(axis_keys)
|
|
162
175
|
self._session = _context(value) if self._inner.needs_session else None
|
|
163
176
|
@property
|
|
164
177
|
def shape(self): return tuple(self._inner.shape)
|
|
165
178
|
@property
|
|
179
|
+
def axis_keys(self): return tuple(None if k is None else tuple(k) for k in self._inner.axis_keys)
|
|
180
|
+
@property
|
|
166
181
|
def is_atom(self): return self._inner.is_atom
|
|
167
182
|
@property
|
|
168
183
|
def py(self): return _value(self._inner.parts(), session=self._session)
|
|
169
184
|
@property
|
|
170
185
|
def np(self): return _value(self._inner.parts(), as_array=True, session=self._session)
|
|
171
186
|
@property
|
|
187
|
+
def df(self): return _dataframe(self._inner.parts(), self._session)
|
|
188
|
+
@property
|
|
172
189
|
def apl(self): return repr(self._inner)
|
|
173
190
|
def _scalar(self): return _value(self._inner.scalar(), session=self._session)
|
|
174
191
|
def __float__(self): return float(self._scalar())
|
|
@@ -211,6 +211,7 @@ def description(row):
|
|
|
211
211
|
def convert(row):
|
|
212
212
|
"Convert a prepared reference record without evaluating its program or changing its status."
|
|
213
213
|
if error := row.get('expected_error'): expect = '⍝ error: '+error
|
|
214
|
+
elif 'expected_code' in row: expect = row['expected_code']
|
|
214
215
|
elif 'expected' in row: expect = literal(row['expected'])
|
|
215
216
|
else: raise ValueError(f"{row['id']}: missing independent expectation")
|
|
216
217
|
return Case(row['code'], expect, row['id'], description(row), row.get('relative_tolerance', 0), row.get('absolute_tolerance', 0))
|
|
@@ -281,7 +282,7 @@ def add(ids, output='tests/reference', directory='tests/reference/inventory'):
|
|
|
281
282
|
case = convert(row)
|
|
282
283
|
result = json.loads(_check_reference(json.dumps(row), 2))
|
|
283
284
|
if result['status']!='pass': raise ValueError(f'{id}: {result}')
|
|
284
|
-
if not row.get('expected_error'):
|
|
285
|
+
if not row.get('expected_error') and 'expected' in row and 'expected_code' not in row:
|
|
285
286
|
result = json.loads(_check_reference(json.dumps(dict(code=case.expect, expected=row['expected'])), 2))
|
|
286
287
|
if result['status']!='pass': raise ValueError(f'{id}: converted expectation: {result}')
|
|
287
288
|
pending[id.split(':')[0].split('/')[0]].append(case)
|
|
@@ -382,7 +382,7 @@ def scan(directory='tests/reference/inventory', source='', match='', timeout=.25
|
|
|
382
382
|
if case['status'] != 'pending':
|
|
383
383
|
inventory[case['status']] += 1
|
|
384
384
|
continue
|
|
385
|
-
if 'expected' not in case and not case.get('expected_error'):
|
|
385
|
+
if 'expected' not in case and not case.get('expected_code') and not case.get('expected_error'):
|
|
386
386
|
inventory['needs expectation'] += 1
|
|
387
387
|
continue
|
|
388
388
|
clean = re.sub(r"'(?:''|[^'])*'|⍝[^\n]*", '', case['code'])
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
array::{generated_len, Layout},
|
|
3
|
+
keyed::Keys,
|
|
4
|
+
ErrorKind, Value,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
pub(crate) enum Mapping {
|
|
8
|
+
Scalar,
|
|
9
|
+
Linear(usize),
|
|
10
|
+
Positions(Vec<usize>),
|
|
11
|
+
Axes(Vec<(usize, usize, usize)>),
|
|
12
|
+
Keyed(Vec<(usize, usize, Vec<Option<usize>>)>),
|
|
13
|
+
}
|
|
14
|
+
impl Mapping {
|
|
15
|
+
pub fn index(&self, i: usize) -> usize { self.get(i).expect("positional mapping") }
|
|
16
|
+
pub fn numeric<T: Copy + Default>(&self, data: &[T], i: usize) -> T { self.get(i).map_or_else(T::default, |j| data[j]) }
|
|
17
|
+
pub fn get(&self, i: usize) -> Option<usize> {
|
|
18
|
+
Some(match self {
|
|
19
|
+
Self::Scalar => 0,
|
|
20
|
+
Self::Linear(repeat) => i / repeat,
|
|
21
|
+
Self::Positions(positions) => positions[i],
|
|
22
|
+
Self::Axes(axes) => axes.iter().map(|&(stride, len, source)| i / stride % len * source).sum(),
|
|
23
|
+
Self::Keyed(axes) => axes.iter().try_fold(0, |n, (stride, source, map)| Some(n + map[i / stride % map.len()]? * source))?,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
pub fn contract(left: &Layout, laxis: usize, right: &Layout, raxis: usize) -> Result<Self, ErrorKind> {
|
|
27
|
+
match (left.keys(laxis), right.keys(raxis)) { (Some(x), Some(y)) if x != y => Ok(Self::Positions(y.positions(x, false)?)), _ => Ok(Self::Linear(1)) }
|
|
28
|
+
}
|
|
29
|
+
fn new(input: &Layout, axes: &[usize], result: &Layout, count: usize) -> Self {
|
|
30
|
+
if count == 0 { return Self::Scalar; }
|
|
31
|
+
let (shape, output) = (input.shape(), result.shape());
|
|
32
|
+
let mut strides = vec![1; output.len()];
|
|
33
|
+
for i in (1..output.len()).rev() { strides[i - 1] = strides[i] * output[i]; }
|
|
34
|
+
if axes.iter().enumerate().any(|(a, &b)| input.keys(a).is_some() && input.keys(a) != result.keys(b)) {
|
|
35
|
+
let mut source = 1;
|
|
36
|
+
let mut map = Vec::new();
|
|
37
|
+
for (a, (&len, &b)) in shape.iter().zip(axes).enumerate().rev() {
|
|
38
|
+
let positions = (0..output[b])
|
|
39
|
+
.map(|i| match (input.keys(a), result.keys(b)) {
|
|
40
|
+
(Some(src), Some(dst)) => src.position(&dst.names()[i]),
|
|
41
|
+
_ => Some(if len == 1 { 0 } else { i }),
|
|
42
|
+
})
|
|
43
|
+
.collect();
|
|
44
|
+
map.push((strides[b], source, positions));
|
|
45
|
+
source *= len;
|
|
46
|
+
}
|
|
47
|
+
return Self::Keyed(map);
|
|
48
|
+
}
|
|
49
|
+
let size: usize = shape.iter().product();
|
|
50
|
+
if size == 1 { return Self::Scalar; }
|
|
51
|
+
let mut source = 1;
|
|
52
|
+
let mut map = Vec::new();
|
|
53
|
+
for (&len, &axis) in shape.iter().zip(axes).rev() {
|
|
54
|
+
if len != 1 { map.push((strides[axis], len, source)); }
|
|
55
|
+
source *= len;
|
|
56
|
+
}
|
|
57
|
+
let repeat = count / size;
|
|
58
|
+
if map.iter().all(|&(stride, _, source)| stride == source * repeat) { Self::Linear(repeat) } else { Self::Axes(map) }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub(crate) struct Agreement {
|
|
63
|
+
pub layout: Layout,
|
|
64
|
+
pub len: usize,
|
|
65
|
+
pub left: Mapping,
|
|
66
|
+
pub right: Mapping,
|
|
67
|
+
}
|
|
68
|
+
impl Agreement {
|
|
69
|
+
pub fn new(left: &Layout, right: &Layout) -> Result<Self, ErrorKind> {
|
|
70
|
+
Self::mapped(left, right, &(0..left.shape().len()).collect::<Vec<_>>(), &(0..right.shape().len()).collect::<Vec<_>>())
|
|
71
|
+
}
|
|
72
|
+
pub fn with_axes(left: &Layout, right: &Layout, axes: &[usize]) -> Result<Self, ErrorKind> {
|
|
73
|
+
if left.shape().len() < right.shape().len() { Self::mapped(left, right, axes, &(0..right.shape().len()).collect::<Vec<_>>()) } else { Self::mapped(left, right, &(0..left.shape().len()).collect::<Vec<_>>(), axes) }
|
|
74
|
+
}
|
|
75
|
+
fn mapped(left: &Layout, right: &Layout, xa: &[usize], ya: &[usize]) -> Result<Self, ErrorKind> {
|
|
76
|
+
let mut shape = vec![1; left.shape().len().max(right.shape().len())];
|
|
77
|
+
let mut keys = vec![None; shape.len()];
|
|
78
|
+
for axis in 0..shape.len() {
|
|
79
|
+
let (x, y) = (xa.iter().position(|&a| a == axis), ya.iter().position(|&a| a == axis));
|
|
80
|
+
let (nx, ny) = (x.map_or(1, |a| left.shape()[a]), y.map_or(1, |a| right.shape()[a]));
|
|
81
|
+
let (kx, ky) = (x.and_then(|a| left.keys(a)), y.and_then(|a| right.keys(a)));
|
|
82
|
+
if let (Some(x), Some(y)) = (kx, ky) {
|
|
83
|
+
let key = if x == y { x.clone() } else { Keys::new(x.names().iter().chain(y.names().iter().filter(|k| x.position(k).is_none())).cloned().collect())? };
|
|
84
|
+
shape[axis] = key.names().len();
|
|
85
|
+
keys[axis] = Some(key);
|
|
86
|
+
} else {
|
|
87
|
+
let n = if nx == ny || ny == 1 { nx } else if nx == 1 { ny } else { return Err(ErrorKind::Length); };
|
|
88
|
+
shape[axis] = n;
|
|
89
|
+
keys[axis] = kx.filter(|_| nx == n).or(ky.filter(|_| ny == n)).cloned();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
let len = generated_len(&shape)?;
|
|
93
|
+
let layout = Layout::from(shape).with_keys(keys)?;
|
|
94
|
+
let x = Mapping::new(left, xa, &layout, len);
|
|
95
|
+
let y = Mapping::new(right, ya, &layout, len);
|
|
96
|
+
Ok(Self { layout, len, left: x, right: y })
|
|
97
|
+
}
|
|
98
|
+
pub fn values(&self, left: Option<&Value>, right: &Value, i: usize) -> (Option<Value>, Value) {
|
|
99
|
+
if self.len == 0 {
|
|
100
|
+
let item = |a: &Value| if a.is_empty() { a.prototype() } else { a.at(0) };
|
|
101
|
+
return (left.map(item), item(right));
|
|
102
|
+
}
|
|
103
|
+
let x = left.and_then(|a| self.left.get(i).map(|j| a.at(j)));
|
|
104
|
+
let y = self.right.get(i).map(|j| right.at(j));
|
|
105
|
+
let xf = left.map(|a| x.clone().unwrap_or_else(|| y.as_ref().map_or_else(|| a.prototype(), Value::fill)));
|
|
106
|
+
let yf = y.unwrap_or_else(|| x.as_ref().map_or_else(|| right.prototype(), Value::fill));
|
|
107
|
+
(xf, yf)
|
|
108
|
+
}
|
|
109
|
+
}
|