playbook/docs/tsl/syntax/22_matrix_deep_dive.md

558 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TSL 矩阵深水专题
文档类型:语法深水专题
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
遇到不确定时:先按本页候选页继续判断;[12_matrix_and_collections.md](12_matrix_and_collections.md)、[23_fmarray.md](23_fmarray.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md)
这一篇只讲矩阵专用语法主干:矩阵初始化、数列构造、矩阵逆/广义逆、矩阵尺寸与索引、矩阵遍历、子矩阵和 `mfind` 查找。它和 [12_matrix_and_collections.md](12_matrix_and_collections.md) 的分工是:`12` 讲普通数组与集合关系,这一篇讲矩阵专用构造、遍历、子矩阵和矩阵查找接口。
## 本篇职责
回答“怎样直接构造全零矩阵、全一矩阵、随机矩阵、单位矩阵、空矩阵和数列数组,怎样写矩阵逆/广义逆,怎样拿到矩阵的行数、列数、行索引和列索引,怎样遍历矩阵、取/改子矩阵,以及怎样用 `mfind` 找到或替换符合条件的单元格”。
## 智能体矩阵深水判断流程
1. 先判断要写矩阵初始化、数列构造、矩阵逆/广义逆、矩阵尺寸与索引读取、矩阵遍历、子矩阵,还是 `mfind` 查找/替换。
2. 基础数组和矩阵样比较先回看 `12_matrix_and_collections.md`
3. `mrows` / `mcols` / `msize` 等函数只照文档返回形态写。
4. 需要逐单元执行语句块时用 `matrix::begin ... end`;需要把表达式结果写回每个单元时用 `matrix ::= expression`
5. 需要遍历到嵌套数组最深层时用 `matrix:.begin ... end``matrix:.= expression`
6. 子矩阵范围使用 `row_start:row_end`、`:`、下标数组和列范围组合;不要把字符串键当作子矩阵范围序号。
7. `mfind(...)` 用于把符合条件的单元格转换成下标列表,或同时返回原值/替换原值。
8. 不要把列索引数组误当成单个数字。
9. 没有对应代码块时不要发明矩阵深水写法。
## 核心规则
- 矩阵初始化函数的参数规格见 [../reference/catalog/math.md](../reference/catalog/math.md);本页只保留矩阵行为示例和返回形态边界。
- `zeros(...)`、`ones(...)`、`rand(...)`、`nils(...)`、`eye(...)` 都可以直接用于矩阵初始化。
- `zeros(3)`、`ones(3)`、`nils(2)` 这类单参数写法可以直接生成一维结果。
- `zeros(2, 3)`、`rand(2, 3)` 这类双参数写法可以直接生成二维矩阵。
- `zeros(2, array("A", "B"))` 这种写法可以直接生成带列名的二维结果。
- `eye(3)` 生成的是 `3 x 3` 单位矩阵,不是一维数组。
- `->` 用来生成数列;默认步长是 `1`,也可以显式传入步长和索引数组。
- 在矩阵语境里,`!A` 是一元倒数运算符作用于矩阵的形态,用于矩阵逆/广义逆;非方阵输入可以返回行列数互换后的广义逆结果。
- `msize(...)`、`mrows(...)`、`mcols(...)` 的参数规格见 [../reference/catalog/system.md](../reference/catalog/system.md)。
- `msize(matrix_value)` 返回 `array(行数, 列数)`
- `msize(matrix_value, 1)` 返回行索引数组和列索引数组。
- `mrows(matrix_value)` / `mcols(matrix_value)` 默认返回数量;第二个参数写成 `1` 时返回索引数组。
- `mrows(matrix_value, 1)` / `mcols(matrix_value, 1)` 的返回值可用于索引匹配;不要把它们当成数量。
- `matrix::begin ... end` 是矩阵遍历语句块,最多按二维遍历;语句块里可用 `mcell`、`mrow`、`mcol`。
- `matrix ::= expression` 是矩阵遍历赋值,把表达式结果逐单元写回原矩阵;它不是语句块。
- `matrix:.begin ... end` 是深度遍历语句块;对嵌套数组会遍历到最深节点。
- `matrix:.= expression` 是深度遍历赋值;需要对不规则嵌套数组逐叶子节点写回时使用。
- 遍历时需要维度信息可用 `mIndexCount`;需要第 `n` 维下标可用 `mIndex(n)`
- 子矩阵提取可以写 `matrix[row_start:row_end, col_start:col_end]`、`matrix[:, col_range]`、`matrix[row_index_array, col_index_array]`。
- 子矩阵赋值可以写成单个标量,也可以写成同结构矩阵;赋值矩阵应和目标子矩阵形状匹配。
- 子矩阵也可以接 `::=` 做逐单元赋值。
- `mfind(matrix)` 返回真值单元格下标;一维数组的无条件 `mfind` 返回一维下标数组。
- `mfind(matrix, condition)` 返回符合条件的下标行;二维矩阵下每行形如 `array(row_index, col_index)`
- `mfind(matrix, condition, 1)` 在每个下标行末尾追加原单元值。
- `mfind(matrix, condition, ret_value, replacement)` 会把符合条件的单元格替换成 `replacement`,并返回替换前的匹配信息。
- `mfindSparse(matrix, condition)` 用于嵌套数组或稀疏结构,返回包含完整深层路径的下标行。
## 可直接照写示例
### 矩阵初始化
代码块身份:可直接照写示例
```tsl
zeros_1d := zeros(3);
zeros_2d := zeros(2, 3);
ones_1d := ones(3);
nils_1d := nils(2);
eye_2d := eye(3);
rand_2d := rand(2, 3);
named_zeros := zeros(2, array("A", "B"));
writeLn(length(zeros_1d));
writeLn(mrows(zeros_2d));
writeLn(mcols(zeros_2d));
```
结果说明:
- `zeros(3)` 的长度是 `3`,前三个元素依次是 `0`、`0`、`0`
- `zeros(2, 3)` 的行数是 `2`、列数是 `3`,第一行前三个元素是 `0`、`0`、`0`
- `ones(3)` 的前三个元素依次是 `1`、`1`、`1`
- `nils(2)` 可直接生成长度为 `2` 的结果
- `eye(3)` 的行数是 `3`、列数是 `3`,并且 `(0,0)`、`(1,1)`、`(2,2)` 为 `1``(0,1)`、`(1,0)` 为 `0`
- `rand(2, 3)` 的行数是 `2`、列数是 `3`
- `zeros(2, array("A", "B"))` 的行数是 `2`、列数是 `2`,并且 `named_zeros[0]["A"]`、`named_zeros[0]["B"]`、`named_zeros[1]["A"]`、`named_zeros[1]["B"]` 都是 `0`
代码块身份:输出片段
```text
3
2
3
```
### `->` 数列数组初始化
默认步长为 `1`
代码块身份:可直接照写示例
```tsl
seq_default := 1 -> 5;
```
结果说明:
- `seq_default``array(1, 2, 3, 4, 5)`
显式指定步长:
代码块身份:可直接照写示例
```tsl
seq_step := array(2.5, 0.5) -> 5;
```
结果说明:
- `seq_step` 的长度是 `6`
- 六个元素依次是 `2.5`、`3`、`3.5`、`4`、`4.5`、`5`
显式指定索引数组:
代码块身份:可直接照写示例
```tsl
seq_indexed := array(0, 1, array("A", "B", "C", "D", "E", "F")) -> 5;
```
结果说明:
- `seq_indexed` 的长度是 `6`
- `seq_indexed["A"]``seq_indexed["F"]` 依次是 `0`、`1`、`2`、`3`、`4`、`5`
### 矩阵一元倒数 / 逆 / 广义逆:`!A`
方阵输入返回普通矩阵逆:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2), (3, 4));
inverse_value := !matrix_value;
writeLn(mrows(inverse_value));
writeLn(mcols(inverse_value));
writeLn(inverse_value[0][0]);
writeLn(inverse_value[0][1]);
writeLn(inverse_value[1][0]);
writeLn(inverse_value[1][1]);
```
代码块身份:输出片段
```text
2
2
-2
1
1.5
-0.5
```
非方阵输入返回广义逆:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2, 3), (4, 5, 6));
inverse_value := !matrix_value;
writeLn(mrows(inverse_value));
writeLn(mcols(inverse_value));
writeLn(inverse_value[0][0]);
writeLn(inverse_value[0][1]);
writeLn(inverse_value[1][0]);
writeLn(inverse_value[1][1]);
writeLn(inverse_value[2][0]);
writeLn(inverse_value[2][1]);
```
代码块身份:输出片段
```text
3
2
-0.944444444444444
0.444444444444444
-0.111111111111111
0.111111111111111
0.722222222222222
-0.222222222222222
```
说明:
- `array((1, 2, 3), (4, 5, 6))``2 x 3` 矩阵样数组。
- `!matrix_value` 返回的是 `3 x 2` 广义逆结果。
- 生成矩阵逆/广义逆时写 `!matrix_value`;不要把它改写成 `1 / matrix_value`
- `!` 不表示逻辑非;逻辑非回 [06_expressions_and_operators.md](06_expressions_and_operators.md) 使用 `not`
### `msize`、`mrows`、`mcols`
代码块身份:可直接照写示例
```tsl
matrix_rows := array(
("A": 1, "B": 2),
("A": 11, "B": 22),
("A": 21, "B": 32)
);
size_info := msize(matrix_rows);
size_index := msize(matrix_rows, 1);
row_count := mrows(matrix_rows);
row_index := mrows(matrix_rows, 1);
col_count := mcols(matrix_rows);
col_index := mcols(matrix_rows, 1);
```
结果说明:
- `msize(matrix_rows)` 返回 `array(3, 2)`
- `msize(matrix_rows, 1)` 的第一项是 `array(0, 1, 2)`,第二项是 `array("A", "B")`
- `mrows(matrix_rows)` 返回 `3`
- `mrows(matrix_rows, 1)` 返回 `array(0, 1, 2)`
- `mcols(matrix_rows)` 返回 `2`
- `mcols(matrix_rows, 1)` 返回 `array("A", "B")`
### 矩阵遍历:`::` 与 `::=`
`::` 执行语句块:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2, 3), (4, 5, 6));
sum_value := 0;
matrix_value::begin
sum_value += mcell;
end
writeLn(sum_value);
```
代码块身份:输出片段
```text
21
```
`::=` 把表达式结果逐单元写回:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2), (3, 4));
matrix_value ::= mrow * 10 + mcol;
writeLn(matrix_value[0][0]);
writeLn(matrix_value[0][1]);
writeLn(matrix_value[1][0]);
writeLn(matrix_value[1][1]);
```
代码块身份:输出片段
```text
0
1
10
11
```
说明:
- `mcell` 是当前单元格值。
- `mrow` 是当前行下标。
- `mcol` 是当前列下标。
- `::=` 右侧写表达式,不写 `begin ... end` 语句块。
### 深度遍历:`:.` 与 `:.=`
`:.` 会遍历到嵌套数组的最深节点:
代码块身份:可直接照写示例
```tsl
nested_value := array(10, 12, ("A": array(30), "B": 22), ("A": array(31, 32), "B": 23));
deep_count := 0;
nested_value:.begin
deep_count += 1;
end
writeLn(deep_count);
```
代码块身份:输出片段
```text
7
```
`:.=` 会把表达式结果写回最深节点:
代码块身份:可直接照写示例
```tsl
values := array(-1, 2, -3);
values:.= abs(mcell);
writeLn(values[0]);
writeLn(values[1]);
writeLn(values[2]);
```
代码块身份:输出片段
```text
1
2
3
```
### 子矩阵
按行列范围提取:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2, 3), (4, 5, 6), (7, 8, 9));
sub_value := matrix_value[1:2, 0:1];
writeLn(mrows(sub_value));
writeLn(mcols(sub_value));
writeLn(sub_value[0][0]);
writeLn(sub_value[0][1]);
writeLn(sub_value[1][0]);
writeLn(sub_value[1][1]);
```
代码块身份:输出片段
```text
2
2
4
5
7
8
```
按下标数组提取:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2, 3), (4, 5, 6), (7, 8, 9));
sub_value := matrix_value[array(0, 2), array(1, 2)];
writeLn(sub_value[0][0]);
writeLn(sub_value[0][1]);
writeLn(sub_value[1][0]);
writeLn(sub_value[1][1]);
```
代码块身份:输出片段
```text
2
3
8
9
```
子矩阵赋值:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2, 3), (4, 5, 6), (7, 8, 9));
matrix_value[0:1, 1:2] := array((10, 11), (12, 13));
writeLn(matrix_value[0][1]);
writeLn(matrix_value[0][2]);
writeLn(matrix_value[1][1]);
writeLn(matrix_value[1][2]);
```
代码块身份:输出片段
```text
10
11
12
13
```
子矩阵逐单元赋值:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 2), (3, 4));
matrix_value[0:1, 0:1] ::= mrow * 10 + mcol;
writeLn(matrix_value[0][0]);
writeLn(matrix_value[0][1]);
writeLn(matrix_value[1][0]);
writeLn(matrix_value[1][1]);
```
代码块身份:输出片段
```text
0
1
10
11
```
### `mfind` 与 `mfindSparse`
一维数组无条件查找会返回一维下标数组:
代码块身份:可直接照写示例
```tsl
values := array(1, 0, 2, 0, 3);
indexes := mfind(values);
writeLn(length(indexes));
writeLn(indexes[0]);
writeLn(indexes[1]);
writeLn(indexes[2]);
```
代码块身份:输出片段
```text
3
0
2
4
```
二维矩阵按条件查找:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 0), (2, 3));
indexes := mfind(matrix_value, mcell >= 2);
writeLn(length(indexes));
writeLn(indexes[0][0]);
writeLn(indexes[0][1]);
writeLn(indexes[1][0]);
writeLn(indexes[1][1]);
```
代码块身份:输出片段
```text
2
1
0
1
1
```
第三个参数写成 `1` 时,每行末尾追加原单元值:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 0), (2, 3));
matches := mfind(matrix_value, mcell >= 2, 1);
writeLn(matches[0][0]);
writeLn(matches[0][1]);
writeLn(matches[0][2]);
writeLn(matches[1][0]);
writeLn(matches[1][1]);
writeLn(matches[1][2]);
```
代码块身份:输出片段
```text
1
0
2
1
1
3
```
第四个参数用于替换符合条件的单元格:
代码块身份:可直接照写示例
```tsl
matrix_value := array((1, 0), (2, 3));
matches := mfind(matrix_value, mcell >= 2, 1, 100);
writeLn(matches[0][2]);
writeLn(matches[1][2]);
writeLn(matrix_value[1][0]);
writeLn(matrix_value[1][1]);
```
代码块身份:输出片段
```text
2
3
100
100
```
`mfindSparse` 返回深层路径:
代码块身份:可直接照写示例
```tsl
nested_value := array(10, ("A": 0, "B": array(2)), 0);
indexes := mfindSparse(nested_value, mcell = 2);
writeLn(length(indexes));
writeLn(length(indexes[0]));
writeLn(indexes[0][0]);
writeLn(indexes[0][1]);
writeLn(indexes[0][2]);
```
代码块身份:输出片段
```text
1
3
1
B
0
```
## 默认生成模板
需要矩阵构造时,优先从这个最短模板开始:
代码块身份:可直接照写示例
```tsl
matrix_value := zeros(2, 3);
```
## 决策边界和禁止项
-`eye(3)` 当成一维数组。
-`!A` 当成逻辑非表达式。
- 以为 `mrows(matrix_value, 1)``mcols(matrix_value, 1)` 返回的还是数量。
- 写带步长的 `->` 时,漏掉外层 `array(...)`
- 还在普通数组页里硬塞矩阵专用大小接口。
-`::=` 写成带 `begin ... end` 的语句块。
-`::` 期待遍历到任意深度;深度遍历使用 `:.`
- 子矩阵赋值时用形状不匹配的矩阵硬塞。
代码块身份:反例 / 不可照写
```text
seq_value := 2.5, 0.5 -> 5;
```
上面这种写法不对。显式步长模式需要写成 `array(2.5, 0.5) -> 5`