Julia 教程 / 字典、集合与命名元组
字典、集合与命名元组
1. Dict — 字典
创建字典
# 字面量创建
scores = Dict("Alice" => 95, "Bob" => 87, "Charlie" => 92)
typeof(scores) # Dict{String, Int64}
# 使用 Pair
d = Dict(:name => "Julia", :version => 1.10)
# 从元组数组创建
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = Dict(pairs)
# 空字典
d = Dict{String, Int}() # 指定类型
d = Dict() # Dict{Any, Any}
# 使用构造函数
d = Dict(zip(["a", "b", "c"], [1, 2, 3]))
# 从数组推导式创建
d = Dict(string(i) => i^2 for i in 1:5)
# Dict("5" => 25, "4" => 16, "2" => 4, "3" => 9, "1" => 1)
基本操作(增删改查)
d = Dict("Alice" => 95, "Bob" => 87)
# 查询
d["Alice"] # 95
# d["Eve"] # KeyError(不存在)
# 安全查询(返回默认值)
get(d, "Eve", 0) # 0(不存在时返回默认值)
get(d, "Alice", 0) # 95
# 使用 haskey 先检查
haskey(d, "Alice") # true
haskey(d, "Eve") # false
# 添加/修改
d["Charlie"] = 92 # 添加新键
d["Alice"] = 98 # 修改已有键
# 删除
delete!(d, "Bob") # 删除键值对并返回字典
pop!(d, "Charlie") # 删除并返回值
pop!(d, "Eve", 0) # 不存在时返回默认值
# 合并(Julia 1.9+)
d1 = Dict("a" => 1, "b" => 2)
d2 = Dict("b" => 3, "c" => 4)
merge(d1, d2) # Dict("a" => 1, "b" => 3, "c" => 4)(后者覆盖)
mergewith(+, d1, d2) # Dict("a" => 1, "b" => 5, "c" => 4)(合并函数)
# 原地合并
merge!(d1, d2)
⚠️ 注意:
Dict的键顺序是不确定的,不要依赖插入顺序。如需有序字典,使用OrderedDict(来自DataStructures.jl)。
遍历字典
d = Dict("Alice" => 95, "Bob" => 87, "Charlie" => 92)
# 遍历键值对
for (name, score) in d
println("$name: $score")
end
# 遍历键
for key in keys(d)
println(key)
end
# 遍历值
for val in values(d)
println(val)
end
# 使用 pairs 函数
for (k, v) in pairs(d)
println("$k => $v")
end
# 收集为数组
collect(keys(d)) # ["Alice", "Bob", "Charlie"]
collect(values(d)) # [95, 87, 92]
字典推导式
# 生成字典
squares = Dict(i => i^2 for i in 1:10)
# Dict(10 => 100, 4 => 16, 9 => 81, 5 => 25, ...)
# 过滤
evens = Dict(k => v for (k, v) in squares if v > 20)
# 转换
upper = Dict(uppercase(k) => v * 2 for (k, v) in Dict("a" => 1, "b" => 2))
2. DefaultDict
标准 Dict 在访问不存在的键时会报错。使用 DefaultDict(来自 DataStructures.jl)可设置默认值:
using Pkg; Pkg.add("DataStructures")
using DataStructures
# 带默认值的字典
dd = DefaultDict(0) # 默认值为 0
dd["Alice"] += 10 # 10(不存在时自动创建为 0)
dd["Bob"] += 5 # 5
# 默认值为数组
groups = DefaultDict(Vector{String})
push!(groups["A"], "item1") # 自动创建空数组
push!(groups["A"], "item2")
push!(groups["B"], "item3")
# 统计词频的经典模式
function word_count(text)
counts = DefaultDict(0)
for word in split(text)
counts[word] += 1
end
return counts
end
wc = word_count("the cat sat on the mat the cat")
# Dict("the" => 3, "cat" => 2, "sat" => 1, "on" => 1, "mat" => 1)
3. Set — 集合
创建集合
# 字面量
s = Set([1, 2, 3, 4, 5])
typeof(s) # Set{Int64}
# 自动去重
s = Set([1, 2, 2, 3, 3, 3]) # Set([1, 2, 3])
# 空集合
s = Set{Int}()
s = Set{String}()
# 从字符串创建
s = Set("abcde") # Set(['a', 'b', 'c', 'd', 'e'])
集合操作
a = Set([1, 2, 3, 4, 5])
b = Set([3, 4, 5, 6, 7])
# 并集
union(a, b) # Set([1, 2, 3, 4, 5, 6, 7])
a ∪ b # 同上(\cup + Tab)
# 交集
intersect(a, b) # Set([3, 4, 5])
a ∩ b # 同上(\cap + Tab)
# 差集
setdiff(a, b) # Set([1, 2])(a 中有 b 中没有)
setdiff(b, a) # Set([6, 7])(b 中有 a 中没有)
# 对称差集
symdiff(a, b) # Set([1, 2, 6, 7])
# 子集与超集
issubset(Set([1, 2]), a) # true
Set([1, 2]) ⊆ a # true
a ⊇ Set([1, 2]) # true
# 元素检查
in(3, a) # true
3 ∈ a # true
6 ∉ a # true
# 集合大小
length(a) # 5
isempty(Set()) # true
集合的增删改
s = Set{Int}()
# 添加
push!(s, 1)
push!(s, 2)
push!(s, 1) # 已存在,不变
# 删除
pop!(s, 1) # 删除并返回 1
pop!(s, 99, nothing) # 不存在时返回 nothing
# 对称差(原地)
a = Set([1, 2, 3])
b = Set([3, 4, 5])
symdiff!(a, b) # a 变为 Set([1, 2, 4, 5])
| 操作 | 函数 | 运算符 | 说明 |
|---|---|---|---|
| 并集 | union(a, b) | a ∪ b | 合并两个集合 |
| 交集 | intersect(a, b) | a ∩ b | 共同元素 |
| 差集 | setdiff(a, b) | a 中独有 | |
| 对称差 | symdiff(a, b) | 仅在一方中 | |
| 子集 | issubset(a, b) | a ⊆ b | a 是 b 的子集 |
| 包含 | in(x, s) | x ∈ s | x 在 s 中 |
4. Tuple — 元组
元组是不可变的、异构的、固定长度的序列:
# 创建元组
t = (1, "hello", 3.14, true)
typeof(t) # Tuple{Int64, String, Float64, Bool}
# 单元素元组(注意逗号)
t1 = (42,) # Tuple{Int64}
t1 = (42) # Int64(不是元组!)
# 空元组
t0 = () # Tuple{}
# 访问元素
t[1] # 1
t[2] # "hello"
# 不可变!
# t[1] = 2 # ERROR
# 元组解包
a, b, c, d = t
# 交换变量
x, y = 1, 2
x, y = y, x # x=2, y=1
# 元组拼接
(1, 2, 3)..., (4, 5) # (1, 2, 3, 4, 5)
⚠️ 注意:
Tuple的元素不可修改。需要可变的异构序列时,考虑使用Vector{Any}或自定义mutable struct。
元组的性能优势
# 元组是栈分配的,没有堆分配开销
t = (1, 2, 3) # 栈上
# 小元组(< 8 个元素)性能极佳
# 适合函数返回多值
minmax(a, b) = a < b ? (a, b) : (b, a)
lo, hi = minmax(5, 3) # (3, 5)
# 用于多重派发(Val 类型)
foo(::Val{:fast}) = "fast mode"
foo(::Val{:safe}) = "safe mode"
foo(Val(:fast)) # "fast mode"
5. NamedTuple — 命名元组
# 创建命名元组
nt = (name="Alice", age=30, city="Beijing")
typeof(nt) # NamedTuple{(:name, :age, :city), Tuple{String, Int64, String}}
# 按名称访问
nt.name # "Alice"
nt.age # 30
# 按索引访问
nt[1] # "Alice"
# 获取字段名
keys(nt) # (:name, :age, :city)
values(nt) # ("Alice", 30, "Beijing")
# 从字典转换
d = Dict("x" => 1, "y" => 2)
nt = (; (Symbol(k) => v for (k, v) in pairs(d))...)
# 函数返回命名元组
function person(name, age)
return (name=name, age=age, is_adult=age >= 18)
end
p = person("Bob", 25)
p.name # "Bob"
p.is_adult # true
# 合并命名元组
a = (x=1, y=2)
b = (z=3, w=4)
merge(a, b) # (x=1, y=2, z=3, w=4)
💡 提示:
NamedTuple是轻量级的结构体替代品。适合一次性传递少量参数,无需定义struct。
6. pairs 迭代
# 字典的 pairs
d = Dict("a" => 1, "b" => 2)
for (k, v) in pairs(d)
println("$k => $v")
end
# 数组的 pairs(索引 => 值)
v = [10, 20, 30]
for (i, val) in pairs(v)
println("v[$i] = $val")
end
# v[1] = 10
# v[2] = 20
# v[3] = 30
# NamedTuple 的 pairs
nt = (x=1, y=2, z=3)
for (k, v) in pairs(nt)
println("$k = $v")
end
# Dict from pairs
d = Dict(pairs([10, 20, 30]))
# Dict(1 => 10, 2 => 20, 3 => 30)
7. 字典与 JSON 转换
using Pkg; Pkg.add("JSON3")
using JSON3
# 字典 → JSON 字符串
d = Dict("name" => "Alice", "age" => 30, "scores" => [95, 87, 92])
json_str = JSON3.write(d)
# "{\"name\":\"Alice\",\"age\":30,\"scores\":[95,87,92]}"
# JSON 字符串 → 字典
d2 = JSON3.read(json_str, Dict{String, Any})
# 更复杂的结构
data = Dict(
"users" => [
Dict("name" => "Alice", "active" => true),
Dict("name" => "Bob", "active" => false)
],
"total" => 2
)
JSON3.write(data)
# 不使用第三方包的简单方式
# 使用 Julia 内置的字符串处理
function simple_json(d::Dict)
pairs = ["\"$k\": $(json_val(v))" for (k, v) in d]
return "{" * join(pairs, ",") * "}"
end
json_val(v::String) = "\"$v\""
json_val(v::Number) = string(v)
json_val(v::Bool) = string(v)
json_val(v::Vector) = "[" * join(json_val.(v), ",") * "]"
json_val(v::Dict) = simple_json(v)
8. 数据结构选择指南
| 需求 | 推荐类型 | 说明 |
|---|---|---|
| 有序键值对 | Dict | 通用哈希表 |
| 带默认值的字典 | DefaultDict | 自动创建默认值 |
| 去重元素集合 | Set | 快速查找 |
| 固定长度序列 | Tuple | 轻量、栈分配 |
| 轻量结构体 | NamedTuple | 无需定义类型 |
| 动态数组 | Vector | 有序、可变 |
| 不可变序列 | Tuple | 不可变、栈分配 |
| 频率统计 | DefaultDict(0) | 自动计数 |
| 分组归类 | DefaultDict(Vector) | 自动分组 |
| 配置参数 | NamedTuple | 轻量、按名访问 |
9. 性能对比表
| 操作 | Dict | Set | Tuple | NamedTuple | Vector |
|---|---|---|---|---|---|
| 创建 | O(1) | O(1) | O(1) | O(1) | O(1) |
| 查找 | O(1) | O(1) | O(1) | O(1) | O(n) |
| 插入 | O(1) | O(1) | N/A | N/A | O(1)* |
| 删除 | O(1) | O(1) | N/A | N/A | O(n) |
| 内存 | 较大 | 较大 | 极小 | 小 | 较小 |
| 可变性 | ✅ | ✅ | ❌ | ❌ | ✅ |
| 异构 | ✅ | ❌ | ✅ | ✅ | ❌ |
💡 提示: 小键值对(< 10 个)考虑使用
NamedTuple,内存更小、访问更快。超过 10 个字段时Dict更灵活。
10. 业务场景:电商订单统计
using DataStructures
# 模拟订单数据
orders = [
(user="Alice", product="iPhone", amount=999.0, category="electronics"),
(user="Bob", product="T-shirt", amount=29.99, category="clothing"),
(user="Alice", product="Charger", amount=19.99, category="electronics"),
(user="Charlie", product="iPhone", amount=999.0, category="electronics"),
(user="Bob", product="Jeans", amount=59.99, category="clothing"),
(user="Alice", product="Dress", amount=89.99, category="clothing"),
]
# 1. 用户消费统计
user_total = DefaultDict(0.0)
for order in orders
user_total[order.user] += order.amount
end
# Dict("Alice" => 1108.98, "Bob" => 89.98, "Charlie" => 999.0)
# 2. 品类销售统计
category_sales = DefaultDict(0.0)
category_count = DefaultDict(0)
for order in orders
category_sales[order.category] += order.amount
category_count[order.category] += 1
end
# 3. 热门商品排名
product_count = DefaultDict(0)
for order in orders
product_count[order.product] += 1
end
# 按销量排序
sorted_products = sort(collect(product_count), by=x->x[2], rev=true)
# 4. 用户购买品类
user_categories = DefaultDict(Set{String})
for order in orders
push!(user_categories[order.user], order.category)
end
println("=== 用户消费统计 ===")
for (user, total) in sort(collect(user_total), by=x->x[2], rev=true)
println(" $user: \$$(round(total, digits=2))")
end
println("\n=== 品类销售统计 ===")
for (cat, sales) in sort(collect(category_sales), by=x->x[2], rev=true)
println(" $cat: \$$(round(sales, digits=2)) ($(category_count[cat]) 笔)")
end
扩展阅读
- Julia 官方文档 — Collections
- Julia 官方文档 — Dict
- Julia 官方文档 — Set
- DataStructures.jl 文档
- JSON3.jl 文档
- Julia 官方文档 — NamedTuple
📌 本章小结:
Dict是通用的哈希表,支持 O(1) 查找。DefaultDict提供默认值,适合计数和分组。Set支持集合运算(并/交/差/子集)。Tuple是不可变的轻量序列,NamedTuple是按名访问的元组,适合替代简单结构体。选择数据结构时考虑可变性、大小和访问模式。