# 集合 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` 中解构集合 - 标准库 — 集合操作的完整列表