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

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 ⊆ ba 是 b 的子集
包含in(x, s)x ∈ sx 在 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. 性能对比表

操作DictSetTupleNamedTupleVector
创建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/AN/AO(1)*
删除O(1)O(1)N/AN/AO(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 是按名访问的元组,适合替代简单结构体。选择数据结构时考虑可变性、大小和访问模式。