# 集合
Deft 自带三种持久化不可变集合类型——列表(list)、映射(map)和集合(set)——外加用于紧凑循环的可变数组。所有集合字面量都产生值;操作返回新值,而不是就地修改(`<<` 和可变数组是明确的例外)。
## 字面量
| 类型 | 语法 | 示例 |
|---|---|---|
| 列表 | `@{ ... }` | `@{ 1 2 3 }` |
| 映射 | `%{ ... }` | `%{ :a 1 :b 2 }` |
| 集合 | `^{ ... }` | `^{ :a :b :c }` |
| 带标签映射 | `%Tag{ ... }` | `%User{ :name "Alice" }` |
| 数组(可变) | `[array ...]` | `[array 1 2 3]` |
映射的键通常是关键字(`:foo`),但也可以是字符串、数字或任何不可变值。
def nums @{ 1 2 3 4 5 } def user %{ :name "Alice" :roles ^{:admin :user} } def colors ^{:red :green :blue}
## 列表
列表是不可变的持久化向量。随机访问为 O(log n);头/尾操作约为 O(1)。
def xs @{ 10 20 30 } $xs(len) # => 3 $xs(first) # => 10 $xs(last) # => 30 $xs(rest) # => @{ 20 30 } $xs(nth 1) # => 20 $xs(slice 0 2) # => @{ 10 20 } $xs(1..3) # => @{ 20 30 } — range slice, same thing $xs(reverse) # => @{ 30 20 10 }
### 构造
def empty @{} def from-range [range 0 5] # => @{ 0 1 2 3 4 } def combined [append $xs @{ 40 50 }] # => @{ 10 20 30 40 50 }
### 追加
`<<` 就地修改本地集合并返回修改后的集合。`[append $xs x]` 返回新集合,原集合保持不变。
def out @{} for x in $input { $out << $x } # preferred for accumulation set fresh [append $xs 99] # pure-functional
关于完整的追加语义,参见 语法与风格。
### 排序与去重
[sort @{ 3 1 2 }] # => @{ 1 2 3 } [sort $users { ($a~name) < ($b~name) }] # with comparator [unique @{ 1 1 2 3 3 }] # => @{ 1 2 3 } [flatten @{ @{ 1 2 } @{ 3 4 } }] # => @{ 1 2 3 4 }
### 成员判断
[3 in? $xs] # => true [in? $xs 3] # => same, prefix form
### 索引修改
`set-nth`、`insert-nth` 和 `remove-nth` 返回新列表:
[set-nth $xs 0 99] # => @{ 99 20 30 } [insert-nth $xs 1 15] # => @{ 10 15 20 30 } [remove-nth $xs 1] # => @{ 10 30 }
## 映射
映射是持久化哈希映射。查找、assoc 和 dissoc 均为 O(log n)。
def user %{ :name "Alice" :age 30 :role :admin } $user~name # field access (preferred) [get $user :name] # equivalent [get $user :email "n/a"] # with default [user :name] # IFn form — map is callable
### 添加与删除
`assoc` 添加/覆盖键;`dissoc` 删除键;两者都返回新映射:
[assoc $user :email "alice@example.com"] # add a key [assoc $user :age 31] # overwrite [dissoc $user :role] # remove [assoc-in $user @{ :profile :name } "Al"] # nested update [get-in $user @{ :profile :name }] # nested lookup (nil if missing) [get-in $user @{ :profile :age } :unknown] # nested lookup with default
`merge` 合并映射,后面的映射优先:
[merge $user %{ :age 31 :email "a@x.com" }]
### 迭代
[keys $user] # => ^{:name :age :role} (as list) [values $user] # => @{ "Alice" 30 :admin } [entries $user] # => @{ @{:name "Alice"} ... } for pair [entries $user] { echo "$pair(0) = $pair(1)" }
直接迭代映射时,`for` 和管道操作符产生 `@{key value}` 对:
for entry in $user { echo "$entry(0) -> $entry(1)" } $user |> each { echo "$it(0) -> $it(1)" }
### 构造辅助函数
[from-entries @{ @{:a 1} @{:b 2} }] # => %{ :a 1 :b 2 }
[zip @{ :a :b } @{ 1 2 }] # => %{ :a 1 :b 2 }
## 集合
集合是持久化哈希集合。
def roles ^{:admin :user :guest} ($roles << :owner) # => ^{:admin :user :guest :owner} :admin in? $roles # => true [in? $roles :admin] # prefix form
### 集合运算
def a ^{ 1 2 3 } def b ^{ 2 3 4 } [union $a $b] # => ^{ 1 2 3 4 } [intersection $a $b] # => ^{ 2 3 } [difference $a $b] # => ^{ 1 } [subset? ^{1 2} $a] # => true [superset? $a ^{1 2}] # => true
## 字符串作为集合
字符串是字符的 `ISeq`。迭代产生单字符字符串:
"abc"(len) # => 3 "abc"(first) # => "a" "abc"(rest) # => "bc" for ch in "hello" { echo $ch } "hello" |> map { $it(upper) } |> join "" # => "HELLO"
`slice` 和 `nth` 同样适用于字符串:
"hello"(0..3) # => "hel" "hello"(nth 1) # => "e"
## 高阶操作
所有集合都通过 `ISeq` / `IReducible` 支持函数式三件套:
$xs |> map { $it * 2 } # lazy seq — see Lazy Sequences $xs |> filter { ($it % 2) == 0 } |> collect $xs |> reduce 0 { ($acc + $it) } $xs |> find { $it > 20 } # => first match, or nil $xs |> any? { $it > 20 } # => true if any matches $xs |> all? { $it > 0 } # => true if all match $xs |> group { ($it % 2) == 0 } # => %{ :true @{...} :false @{...} }
def arr [array 1 2 3] [aset $arr 0 99] # mutates in place, returns nil [aget $arr 0] # => 99
可变数组未实现完整的集合协议;在性能重要时使用它们,完成后用 `collect` 转换回列表。
## 美化打印
`pretty` 打印结构化视图;`pretty-str` 将其作为字符串返回:
[pretty $user] # %{ # :name "Alice" # :age 30 # :role :admin # } set text [pretty-str $user 4] # indent width 4
## 常见陷阱
- `$x(len)` 与 `[len $x]` — 两者都可用;对于单参数接收者调用,更推荐后缀形式。
- 带默认值的 `get` — `[get $m key default]`;仅当键不存在时才返回默认值。
- 映射迭代顺序 — keys/values/entries 按插入顺序返回;对于给定的操作序列,顺序是确定的,但并非按字母排序。
- 含重复键的集合字面量 — `^{ :a :a :b }` 会折叠为 `^{ :a :b }`。
- 访问缺失键的字段 — `$m~missing-key` 返回 `nil` 而不是抛出异常。
## 另见
- 惰性序列 — `map`/`filter` 的工作方式
- 转换器 — 可复用的转换流水线
- 模式匹配 — 在 `match` 中解构集合
- 标准库 — 集合操作的完整列表