Ziglings 笔记 58: 阶段性测验 7 - 隐士的地图 (图算法)
穿越森林
这是 Ziglings 的第七次阶段性测验(Quiz 7)。我们面对的是一个相当复杂的程序:一个为隐士规划森林旅行路线的导航系统。
从计算机科学的角度看,这实际上是在一个有向加权图 (Directed Weighted Graph) 上运行最短路径算法。
挑战:修复导航系统
虽然代码结构已经搭好,但在关键的逻辑部分留下了几个“坑”,主要涉及复杂的指针操作和类型解包。
解决方案
以下是修复后的关键代码片段:
1. 联合体的 Switch 捕获
在 TripItem 中,我们需要正确打印 Place 或 Path。
const TripItem = union(enum) {
place: *const Place,
path: *const Path,
fn printMe(self: TripItem) void {
switch (self) {
// 修复点:添加 |p| 来捕获 payload
.place => |p| print("{s}", .{p.name}),
.path => |p| print("--{}->", .{p.dist}),
}
}
};
2. 指针解引用迷宫
在 getEntry 中,我们需要在一个可选指针数组中查找特定地点。
fn getEntry(self: *HermitsNotebook, place: *const Place) ?*NotebookEntry {
for (&self.entries, 0..) |*entry, i| {
if (i >= self.end_of_entries) break;
// 修复点:entry 是指向 ?NotebookEntry 的指针
// 1. entry.* 解引用拿到 ?NotebookEntry
// 2. .? 确保它不为 null
// 3. .place 访问字段
if (place == entry.*.?.place) return &entry.*.?;
}
return null;
}
3. 函数错误返回
在 getTripTo 中,我们可能会抛出错误,所以必须修改函数签名。
// 修复点:返回类型改为 !void (Error Union)
fn getTripTo(self: *HermitsNotebook, trip: []?TripItem, dest: *Place) !void {
const destination_entry = self.getEntry(dest);
// 如果找不到目的地,返回错误
if (destination_entry == null) {
return TripError.Unreachable;
}
// ...
}
核心知识点回顾
1. 图的表示
程序使用了 邻接表 (Adjacency List) 的变体。
Place 结构体包含一个 paths 切片,列出了所有从该地点出发的路径。这是一种非常高效的图存储方式。
2. 算法逻辑 (SPFA / BFS)
隐士的笔记本其实就是一个优先队列的简化版。
- 将起点加入笔记本。
- 取出当前最近的地点。
- 检查它的所有邻居:如果通过当前地点去邻居更近,就更新邻居的距离。
- 重复直到队列为空。
3. 指针的威力
整个程序大量使用了指针(*const Place, *const Path)。这意味着我们在传递数据时没有任何拷贝开销。无论地图有多大,地点和路径结构体在内存中都只有一份,我们只是在传递它们的地址。这是高性能系统编程的典型特征。
后续预告:我们已经完成了所有关于基础类型的学习。接下来的练习将进入一个全新的领域:如何在运行时请求更多的内存?下一篇我们将探讨堆分配 (Heap Allocation) 和内存分配器 (Allocators)。