# TSL 数组、键表、矩阵样数据与集合运算

这一篇收拢 `array(...)` 的扩展用法：顺序数组、字符串键表、嵌套数组、本页明确的矩阵样比较，以及 `in` / `sqlin` / `union2` / `intersect` / `minus` / `outersect` 这类去重型集合关系。

## 本篇职责

<!-- section-id: syntax-11-001 -->

回答“`array(...)` 在 TSL 里除了最普通的一维数组，还能怎样组织数据；哪些矩阵样写法属于本页文档明确形态；元素、子集、整行和行集合关系应该怎样判断”。

## 核心规则

<!-- section-id: syntax-11-002 -->

- `array(...)` 既可以写顺序数组，也可以写字符串键表。
- 顺序数组下标从 `0` 开始；字符串仍然从 `1` 开始。
- `array(...)` 可以继续嵌套，形成二维或矩阵样数据。
- 矩阵链式比较 `::>`、`::<`、`::<>`、`::==`、`::>=`、`::<=` 属于本页文档明确形态。
- `in` / `not in` 处理的是元素存在关系，以及左侧为数组时的子集关系。
- `sqlin` / `not sqlin` 处理的是行存在关系；左侧要当成一整行去匹配右侧结果集。
- `union2`、`intersect`、`minus`、`outersect` 都按“行”运算，而不是按单元格逐个运算。
- 集合运算结果会折叠重复行；保留重复记录的过滤规则见 [12_resultset_and_filters.md](12_resultset_and_filters.md)。
- 当数据本身就是一维数组时，按行集合运算和按元素集合运算是一致的。
- 左侧是数组时，先判定需求语义：子集关系用 `in`，整行存在关系用 `sqlin`。

## 可直接照写示例

<!-- section-id: syntax-11-003 -->

### 基础数组与键表

<!-- section-id: syntax-11-004 -->

<!-- tags: 建数组, 字典, 键值对, 二维数组, 嵌套数组, 按名字取值 -->

顺序数组与字符串键表：

代码块身份：可直接照写示例

```tsl
arr := array(10, 20, 30);
hash := array("Code": "0001", "Price": 12.3);
writeLn("ARR0=", arr[0]);
writeLn("ARR1=", arr[1]);
writeLn("HASH=", hash["Code"]);
```

结果说明：

- `arr[0] = 10`
- `arr[1] = 20`
- `hash["Code"] = "0001"`

嵌套数组：

代码块身份：可直接照写示例

```tsl
[r1, r2] := array((1, 2), (3, 4));
writeLn(r1[0]);
writeLn(r1[1]);
writeLn(r2[0]);
writeLn(r2[1]);
```

结果说明：

- 依次输出 `1`、`2`、`3`、`4`
- 说明 `array((1, 2), (3, 4))` 这种嵌套数组写法可以按普通数组继续读取

代码块身份：输出片段

```text
1
2
3
4
```

### 矩阵样比较

<!-- section-id: syntax-11-005 -->

<!-- tags: 数组比大小, 链式比较, 整体比较 -->

矩阵链式比较：

代码块身份：可直接照写示例

```tsl
r := array(1, 2, -1) ::< array(2, 1, 0) ::< array(3, 2, 1);
writeLn(r[0]);
writeLn(r[1]);
writeLn(r[2]);
s := array(1, 2, -1) ::< 2 ::< array(3, 2, 1);
writeLn(s[0]);
writeLn(s[1]);
writeLn(s[2]);
```

结果说明：

- `array(1, 2, -1) ::< array(2, 1, 0) ::< array(3, 2, 1)` 的三个元素依次输出 `1`、`0`、`1`
- `array(1, 2, -1) ::< 2 ::< array(3, 2, 1)` 的三个元素依次输出 `1`、`0`、`0`
- 说明矩阵链式比较会按元素位置分别得到结果数组，并且可以和标量混用

### `in`、`not in`、`sqlin`、`not sqlin`

<!-- section-id: syntax-11-006 -->

<!-- tags: 判断存在, 是否包含, 在不在里面, 子集判断 -->

`in` 既可以判断单个元素是否存在，也可以判断左侧数组是否是右侧结果集的子集：

代码块身份：可直接照写示例

```tsl
writeLn(1 in array(1, 2, 2));
writeLn(1 in array(0, 2));
writeLn(1 in array((1), (2)));
writeLn(array(1, 2) in array(1, 2, 3, 4));
writeLn(array(1, 3) in array((1, 2), (3, 4)));
writeLn(array(1, 2) in array(1));
writeLn(1 not in array(0, 2));
```

结果说明：

- `1 in array(1, 2, 2)` 输出 `1`
- `1 in array(0, 2)` 输出 `0`
- `1 in array((1), (2))` 输出 `1`
- `array(1, 2) in array(1, 2, 3, 4)` 输出 `1`
- `array(1, 3) in array((1, 2), (3, 4))` 输出 `1`
- `array(1, 2) in array(1)` 输出 `0`
- `1 not in array(0, 2)` 输出 `1`

代码块身份：输出片段

```text
1
0
1
1
1
0
1
```

`sqlin` 按整行判断左侧是否存在于右侧结果集中：

代码块身份：可直接照写示例

```tsl
writeLn(1 sqlin array(1, 2));
writeLn(array(1, 2) sqlin array(1, 2, 3));
writeLn(array(1, 2) sqlin array((1, 2), (3, 4)));
writeLn(array(5, 6) not sqlin array((1, 2), (3, 4)));
```

结果说明：

- `1 sqlin array(1, 2)` 输出 `1`
- `array(1, 2) sqlin array(1, 2, 3)` 输出 `0`
- `array(1, 2) sqlin array((1, 2), (3, 4))` 输出 `1`
- `array(5, 6) not sqlin array((1, 2), (3, 4))` 输出 `1`
- `in` 看元素或子集
- `sqlin` 看整行

### 行集合并、交、差、对称差

<!-- section-id: syntax-11-007 -->

<!-- tags: 交集, 并集, 差集, 合并两个数组, 去重合并, 共同部分 -->

下面这组最小例子展示“按行运算”和“结果会折叠重复行”：

代码块身份：可直接照写示例

```tsl
left_rows := array((1, 2), (1, 2), (2, 3));
right_rows := array((1, 2), (3, 4));
union_rows := left_rows union2 right_rows;
intersect_rows := left_rows intersect right_rows;
minus_rows := left_rows minus right_rows;
outersect_rows := left_rows outersect right_rows;
```

结果说明：

- `left_rows union2 right_rows` 共有三行：`(1,2)`、`(2,3)`、`(3,4)`
- `left_rows intersect right_rows` 只有一行：`(1,2)`
- `left_rows minus right_rows` 只有一行：`(2,3)`
- `left_rows outersect right_rows` 有两行：`(2,3)`、`(3,4)`
- `left_rows` 原本有两行相同的 `(1,2)`，但 `union2` / `intersect` 的结果都只保留一份，说明集合运算会折叠重复行

更大的四列结果集文档结果：

- `array((1,2,3,4),(2,3,4,5),(1,1,1,1)) union2 array((1,2,3,4),(3,4,5,6),(2,2,2,2))`
  返回 `array((1,2,3,4),(2,3,4,5),(1,1,1,1),(3,4,5,6),(2,2,2,2))`
- 同一组输入下：
  `intersect` 返回 `array((1,2,3,4))`
- 同一组输入下：
  `minus` 返回 `array((2,3,4,5),(1,1,1,1))`
- 同一组输入下：
  `outersect` 返回 `array((2,3,4,5),(1,1,1,1),(3,4,5,6),(2,2,2,2))`

## 和过滤运算的区别

<!-- section-id: syntax-11-008 -->

<!-- tags: 集合还是过滤, 重复行怎么办, 两种运算怎么选 -->

- 集合运算先把数据当成“行集合”来看，再做包含、并交差。
- 过滤运算先保留“原结果集里的每一条命中记录”；因此重复行会保留下来。
- 需要“集合关系”时留在本页。
- “从原表里筛出哪些行”的过滤规则见 [12_resultset_and_filters.md](12_resultset_and_filters.md)。

## 默认生成模板

<!-- section-id: syntax-11-009 -->

按需求语义从下面两种模板中选择：

代码块身份：可直接照写示例

```tsl
matched := 1 in array(1, 2, 3);
row_matched := array(1, 2) sqlin array((1, 2), (3, 4));
```

## 点前缀比较算符产生逻辑数组

<!-- section-id: syntax-11-010 -->

<!-- tags: 逐元素比较, 得到真假数组, 批量判断, 掩码 -->

点前缀比较（`.=`、`.<>`、`.>`、`.>=`、`.<`、`.<=`）对数组/矩阵逐元素比较，返回真假值数组：

代码块身份：可直接照写示例

```tsl
a := array(0.3, 0.6, 0.9);
result := a .> 0.5;
writeLn("result[0]:", result[0]);
writeLn("result[1]:", result[1]);
writeLn("result[2]:", result[2]);
```

代码块身份：输出片段

```text
result[0]: 0
result[1]: 1
result[2]: 1
```

说明：

- `.>` 对数组的每个元素逐一比较，返回 `0`（假）或 `1`（真）
- `a .> 0.5` 返回 `array(0, 1, 1)`，对应 `0.3 > 0.5` 为假、`0.6 > 0.5` 为真、`0.9 > 0.5` 为真
- 其他点前缀比较同理：`.=`（等于）、`.<>`（不等于）、`.>=`、`.<`、`.<=`

配合 `mfind` 转换为下标数组，可用于取子集：

代码块身份：可直接照写示例

```tsl
a := array((0.3, 10), (0.6, 20), (0.9, 30));
logical_array := a[:, 0] .> 0.5;
indexes := mfind(logical_array);
subset := a[indexes];
writeLn("逻辑数组:", tostn(logical_array));
writeLn("下标数组:", tostn(indexes));
writeLn("子集行数:", mrows(subset));
writeLn("子集 (0,0):", subset[0][0]);
writeLn("子集 (0,1):", subset[0][1]);
writeLn("子集 (1,0):", subset[1][0]);
```

代码块身份：输出片段

```text
逻辑数组: array(0,1,1)
下标数组: array(1,2)
子集行数: 2
子集 (0,0): 0.6
子集 (0,1): 20
子集 (1,0): 0.9
```

说明：

- `a[:, 0] .> 0.5` 提取第 0 列并逐元素比较，得到逻辑数组 `array(0, 1, 1)`
- `mfind(逻辑数组)` 把真值位置转换成下标数组 `array(1, 2)`
- `a[indexes]` 按下标提取对应行，等价于 `select * from a where [0] > 0.5 end`
- `mfind` 的完整用法见 [21_matrix_deep_dive.md](21_matrix_deep_dive.md)

## 非完全矩阵缺位当 0 处理与标量广播

<!-- section-id: syntax-11-011 -->

<!-- tags: 长度不一致, 缺位补零, 标量广播, 数组和数字运算, 按行广播, 二维矩阵乘一维数组 -->

基础算符作用于非完全矩阵（行长度不一致或字符串键不对齐的数组）时，对应位置不存在或为 `nil` 时**默认当 0 处理**：

代码块身份：可直接照写示例

```tsl
a := array("A": 1, "B": 1, "C": 1);
b := array("A": 2, "C": 2);
result := a + b;
writeLn("result['A']:", result["A"]);
writeLn("result['B']:", result["B"]);
writeLn("result['C']:", result["C"]);
```

代码块身份：输出片段

```text
result['A']: 3
result['B']: 1
result['C']: 3
```

说明：

- `a` 有键 `"A"`、`"B"`、`"C"`，`b` 只有 `"A"`、`"C"`
- `a + b` 时，`b["B"]` 不存在，按 `0` 处理
- 结果 `result["A"] = 1 + 2 = 3`、`result["B"] = 1 + 0 = 1`、`result["C"] = 1 + 2 = 3`

标量与矩阵的广播：

代码块身份：可直接照写示例

```tsl
matrix_value := array((1, 2), (3, 4));
result := matrix_value + 10;
writeLn("(0,0):", result[0][0]);
writeLn("(0,1):", result[0][1]);
writeLn("(1,0):", result[1][0]);
writeLn("(1,1):", result[1][1]);
```

代码块身份：输出片段

```text
(0,0): 11
(0,1): 12
(1,0): 13
(1,1): 14
```

说明：

- 标量 `+`、`-`、`*`、`/` 等作用于矩阵时，会广播到每个元素
- `matrix_value + 10` 每个元素都加 10
- 这些是逐元素运算（element-wise），区别于矩阵乘法 `:*`，见 [21_matrix_deep_dive.md](21_matrix_deep_dive.md)

二维矩阵与一维数组做基础算术时，一维数组按“行”广播；它的长度必须等于矩阵行数：

代码块身份：可直接照写示例

```tsl
matrix_value := array((1, 2, 3), (4, 5, 6));
row_factors := array(10, 100);
left_result := matrix_value * row_factors;
right_result := row_factors * matrix_value;
writeLn(left_result[0][0]);
writeLn(left_result[1][2]);
writeLn(right_result[0][1]);
writeLn(right_result[1][0]);
```

代码块身份：输出片段

```text
10
600
20
400
```

说明：第一行使用 `10`，第二行使用 `100`；左右操作数交换后仍按行广播。不要把这条规则误写成按列广播。

## 本页不生成的范围

<!-- section-id: syntax-11-012 -->

- 专门的结果集过滤函数
- TS-SQL 查询与写回
- 更大范围的矩阵函数族

这些名称只作为边界提示，不作为本页可生成模板。

## 禁止项

<!-- section-id: syntax-11-013 -->

- 不要把字符串下标按数组的 `0` 起始规则来写；字符串下标从 `1` 开始，详见 [03_values_and_literals.md](03_values_and_literals.md)。
- 不要把矩阵链式比较 `::...` 和标量链式比较混写成同一种语法。
- 不要把 `in` 和 `sqlin` 当成同一个概念。
- 不要期待 `union2` 保留重复行。
- 不要用集合运算去做“保留原始重复记录”的过滤任务。
- 不要把二维结果集默认当成“按元素逐个比较”的集合运算。
- 左侧数组要表达“这些值是否都属于右侧集合”时，用 `in`。
- 左侧数组要表达“这一整行是否存在于右侧结果集”时，用 `sqlin`。
- `minus` 表达集合差集；保留左侧原始重复次数的过滤规则见 [12_resultset_and_filters.md](12_resultset_and_filters.md)。
- 不要在本页发明结果集过滤、TS-SQL 查询、写回语法或更大矩阵函数族。
- 不要把普通 `array(...)` 自动升级成 `FMArray`；`FMArray` 专属事实见 [22_fmarray.md](22_fmarray.md)。
- 不要把点前缀比较 `.>` 和矩阵链式比较 `::>` 混用；`.>` 返回逻辑数组，`::>` 是链式比较。
- 不要以为非完全矩阵缺位会报错；默认当 `0` 处理。
- 二维矩阵与一维数组运算时，不要把一维数组当成按列因子；它按行广播且长度要匹配行数。
