强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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

扩展阅读


📌 本章小结: Dict 是通用的哈希表,支持 O(1) 查找。DefaultDict 提供默认值,适合计数和分组。Set 支持集合运算(并/交/差/子集)。Tuple 是不可变的轻量序列,NamedTuple 是按名访问的元组,适合替代简单结构体。选择数据结构时考虑可变性、大小和访问模式。