058_quiz7.zig 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. //
  2. // We've absorbed a lot of information about the variations of types
  3. // we can use in Zig. Roughly, in order we have:
  4. //
  5. // u8 single item
  6. // *u8 single-item pointer
  7. // []u8 slice (size known at runtime)
  8. // [5]u8 array of 5 u8s
  9. // [*]u8 many-item pointer (zero or more)
  10. // enum {a, b} set of unique values a and b
  11. // error {e, f} set of unique error values e and f
  12. // struct {y: u8, z: i32} group of values y and z
  13. // union(enum) {a: u8, b: i32} single value either u8 or i32
  14. //
  15. // Values of any of the above types can be assigned as "var" or "const"
  16. // to allow or disallow changes (mutability) via the assigned name:
  17. //
  18. // const a: u8 = 5; // immutable
  19. // var b: u8 = 5; // mutable
  20. //
  21. // We can also make error unions or optional types from any of
  22. // the above:
  23. //
  24. // var a: E!u8 = 5; // can be u8 or error from set E
  25. // var b: ?u8 = 5; // can be u8 or null
  26. //
  27. // Knowing all of this, maybe we can help out a local hermit. He made
  28. // a little Zig program to help him plan his trips through the woods,
  29. // but it has some mistakes.
  30. //
  31. const print = @import("std").debug.print;
  32. // The grue is a nod to Zork.
  33. const TripError = error{ Unreachable, EatenByAGrue };
  34. // Let's start with the Places on the map. Each has a name and a
  35. // distance or difficulty of travel (as judged by the hermit).
  36. //
  37. // Note that we declare the places as mutable (var) because we need to
  38. // assign the paths later. And why is that? Because paths contain
  39. // pointers to places and assigning them now would create a dependency
  40. // loop!
  41. const Place = struct {
  42. name: []const u8,
  43. paths: []const Path = undefined,
  44. };
  45. var a = Place{ .name = "Archer's Point" };
  46. var b = Place{ .name = "Bridge" };
  47. var c = Place{ .name = "Cottage" };
  48. var d = Place{ .name = "Dogwood Grove" };
  49. var e = Place{ .name = "East Pond" };
  50. var f = Place{ .name = "Fox Pond" };
  51. // The hermit's hand-drawn ASCII map
  52. // +---------------------------------------------------+
  53. // | * Archer's Point ~~~~ |
  54. // | ~~~ ~~~~~~~~ |
  55. // | ~~~| |~~~~~~~~~~~~ ~~~~~~~ |
  56. // | Bridge ~~~~~~~~ |
  57. // | ^ ^ ^ |
  58. // | ^ ^ / \ |
  59. // | ^ ^ ^ ^ |_| Cottage |
  60. // | Dogwood Grove |
  61. // | ^ <boat> |
  62. // | ^ ^ ^ ^ ~~~~~~~~~~~~~ ^ ^ |
  63. // | ^ ~~ East Pond ~~~ |
  64. // | ^ ^ ^ ~~~~~~~~~~~~~~ |
  65. // | ~~ ^ |
  66. // | ^ ~~~ <-- short waterfall |
  67. // | ^ ~~~~~ |
  68. // | ~~~~~~~~~~~~~~~~~ |
  69. // | ~~~~ Fox Pond ~~~~~~~ ^ ^ |
  70. // | ^ ~~~~~~~~~~~~~~~ ^ ^ |
  71. // | ~~~~~ |
  72. // +---------------------------------------------------+
  73. //
  74. // We'll be reserving memory in our program based on the number of
  75. // places on the map. Note that we do not have to specify the type of
  76. // this value because we don't actually use it in our program once
  77. // it's compiled! (Don't worry if this doesn't make sense yet.)
  78. const place_count = 6;
  79. // Now let's create all of the paths between sites. A path goes from
  80. // one place to another and has a distance.
  81. const Path = struct {
  82. from: *const Place,
  83. to: *const Place,
  84. dist: u8,
  85. };
  86. // By the way, if the following code seems like a lot of tedious
  87. // manual labor, you're right! One of Zig's killer features is letting
  88. // us write code that runs at compile time to "automate" repetitive
  89. // code (much like macros in other languages), but we haven't learned
  90. // how to do that yet!
  91. const a_paths = [_]Path{
  92. Path{
  93. .from = &a, // from: Archer's Point
  94. .to = &b, // to: Bridge
  95. .dist = 2,
  96. },
  97. };
  98. const b_paths = [_]Path{
  99. Path{
  100. .from = &b, // from: Bridge
  101. .to = &a, // to: Archer's Point
  102. .dist = 2,
  103. },
  104. Path{
  105. .from = &b, // from: Bridge
  106. .to = &d, // to: Dogwood Grove
  107. .dist = 1,
  108. },
  109. };
  110. const c_paths = [_]Path{
  111. Path{
  112. .from = &c, // from: Cottage
  113. .to = &d, // to: Dogwood Grove
  114. .dist = 3,
  115. },
  116. Path{
  117. .from = &c, // from: Cottage
  118. .to = &e, // to: East Pond
  119. .dist = 2,
  120. },
  121. };
  122. const d_paths = [_]Path{
  123. Path{
  124. .from = &d, // from: Dogwood Grove
  125. .to = &b, // to: Bridge
  126. .dist = 1,
  127. },
  128. Path{
  129. .from = &d, // from: Dogwood Grove
  130. .to = &c, // to: Cottage
  131. .dist = 3,
  132. },
  133. Path{
  134. .from = &d, // from: Dogwood Grove
  135. .to = &f, // to: Fox Pond
  136. .dist = 7,
  137. },
  138. };
  139. const e_paths = [_]Path{
  140. Path{
  141. .from = &e, // from: East Pond
  142. .to = &c, // to: Cottage
  143. .dist = 2,
  144. },
  145. Path{
  146. .from = &e, // from: East Pond
  147. .to = &f, // to: Fox Pond
  148. .dist = 1, // (one-way down a short waterfall!)
  149. },
  150. };
  151. const f_paths = [_]Path{
  152. Path{
  153. .from = &f, // from: Fox Pond
  154. .to = &d, // to: Dogwood Grove
  155. .dist = 7,
  156. },
  157. };
  158. // Once we've plotted the best course through the woods, we'll make a
  159. // "trip" out of it. A trip is a series of Places connected by Paths.
  160. // We use a TripItem union to allow both Places and Paths to be in the
  161. // same array.
  162. const TripItem = union(enum) {
  163. place: *const Place,
  164. path: *const Path,
  165. // This is a little helper function to print the two different
  166. // types of item correctly.
  167. fn printMe(self: TripItem) void {
  168. switch (self) {
  169. // Oops! The hermit forgot how to capture the union values
  170. // in a switch statement. Please capture both values as
  171. // 'p' so the print statements work!
  172. .place => print("{s}", .{p.name}),
  173. .path => print("--{}->", .{p.dist}),
  174. }
  175. }
  176. };
  177. // The Hermit's Notebook is where all the magic happens. A notebook
  178. // entry is a Place discovered on the map along with the Path taken to
  179. // get there and the distance to reach it from the start point. If we
  180. // find a better Path to reach a Place (lower distance), we update the
  181. // entry. Entries also serve as a "todo" list which is how we keep
  182. // track of which paths to explore next.
  183. const NotebookEntry = struct {
  184. place: *const Place,
  185. coming_from: ?*const Place,
  186. via_path: ?*const Path,
  187. dist_to_reach: u16,
  188. };
  189. // +------------------------------------------------+
  190. // | ~ Hermit's Notebook ~ |
  191. // +---+----------------+----------------+----------+
  192. // | | Place | From | Distance |
  193. // +---+----------------+----------------+----------+
  194. // | 0 | Archer's Point | null | 0 |
  195. // | 1 | Bridge | Archer's Point | 2 | < next_entry
  196. // | 2 | Dogwood Grove | Bridge | 1 |
  197. // | 3 | | | | < end_of_entries
  198. // | ... |
  199. // +---+----------------+----------------+----------+
  200. //
  201. const HermitsNotebook = struct {
  202. // Remember the array repetition operator `**`? It is no mere
  203. // novelty, it's also a great way to assign multiple items in an
  204. // array without having to list them one by one. Here we use it to
  205. // initialize an array with null values.
  206. entries: [place_count]?NotebookEntry = .{null} ** place_count,
  207. // The next entry keeps track of where we are in our "todo" list.
  208. next_entry: u8 = 0,
  209. // Mark the start of empty space in the notebook.
  210. end_of_entries: u8 = 0,
  211. // We'll often want to find an entry by Place. If one is not
  212. // found, we return null.
  213. fn getEntry(self: *HermitsNotebook, place: *const Place) ?*NotebookEntry {
  214. for (self.entries) |*entry, i| {
  215. if (i >= self.end_of_entries) break;
  216. // Here's where the hermit got stuck. We need to return
  217. // an optional pointer to a NotebookEntry.
  218. //
  219. // What we have with "entry" is the opposite: a pointer to
  220. // an optional NotebookEntry!
  221. //
  222. // To get one from the other, we need to dereference
  223. // "entry" (with .*) and get the non-null value from the
  224. // optional (with .?) and return the address of that. The
  225. // if statement provides some clues about how the
  226. // dereference and optional value "unwrapping" look
  227. // together. Remember that you return the address with the
  228. // "&" operator.
  229. if (place == entry.*.?.place) return entry;
  230. // Try to make your answer this long:__________;
  231. }
  232. return null;
  233. }
  234. // The checkNote() method is the beating heart of the magical
  235. // notebook. Given a new note in the form of a NotebookEntry
  236. // struct, we check to see if we already have an entry for the
  237. // note's Place.
  238. //
  239. // If we DON'T, we'll add the entry to the end of the notebook
  240. // along with the Path taken and distance.
  241. //
  242. // If we DO, we check to see if the path is "better" (shorter
  243. // distance) than the one we'd noted before. If it is, we
  244. // overwrite the old entry with the new one.
  245. fn checkNote(self: *HermitsNotebook, note: NotebookEntry) void {
  246. var existing_entry = self.getEntry(note.place);
  247. if (existing_entry == null) {
  248. self.entries[self.end_of_entries] = note;
  249. self.end_of_entries += 1;
  250. } else if (note.dist_to_reach < existing_entry.?.dist_to_reach) {
  251. existing_entry.?.* = note;
  252. }
  253. }
  254. // The next two methods allow us to use the notebook as a "todo"
  255. // list.
  256. fn hasNextEntry(self: *HermitsNotebook) bool {
  257. return self.next_entry < self.end_of_entries;
  258. }
  259. fn getNextEntry(self: *HermitsNotebook) *const NotebookEntry {
  260. defer self.next_entry += 1; // Increment after getting entry
  261. return &self.entries[self.next_entry].?;
  262. }
  263. // After we've completed our search of the map, we'll have
  264. // computed the shortest Path to every Place. To collect the
  265. // complete trip from the start to the destination, we need to
  266. // walk backwards from the destination's notebook entry, following
  267. // the coming_from pointers back to the start. What we end up with
  268. // is an array of TripItems with our trip in reverse order.
  269. //
  270. // We need to take the trip array as a parameter because we want
  271. // the main() function to "own" the array memory. What do you
  272. // suppose could happen if we allocated the array in this
  273. // function's stack frame (the space allocated for a function's
  274. // "local" data) and returned a pointer or slice to it?
  275. //
  276. // Looks like the hermit forgot something in the return value of
  277. // this function. What could that be?
  278. fn getTripTo(self: *HermitsNotebook, trip: []?TripItem, dest: *Place) void {
  279. // We start at the destination entry.
  280. const destination_entry = self.getEntry(dest);
  281. // This function needs to return an error if the requested
  282. // destination was never reached. (This can't actually happen
  283. // in our map since every Place is reachable by every other
  284. // Place.)
  285. if (destination_entry == null) {
  286. return TripError.Unreachable;
  287. }
  288. // Variables hold the entry we're currently examining and an
  289. // index to keep track of where we're appending trip items.
  290. var current_entry = destination_entry.?;
  291. var i: u8 = 0;
  292. // At the end of each looping, a continue expression increments
  293. // our index. Can you see why we need to increment by two?
  294. while (true) : (i += 2) {
  295. trip[i] = TripItem{ .place = current_entry.place };
  296. // An entry "coming from" nowhere means we've reached the
  297. // start, so we're done.
  298. if (current_entry.coming_from == null) break;
  299. // Otherwise, entries have a path.
  300. trip[i + 1] = TripItem{ .path = current_entry.via_path.? };
  301. // Now we follow the entry we're "coming from". If we
  302. // aren't able to find the entry we're "coming from" by
  303. // Place, something has gone horribly wrong with our
  304. // program! (This really shouldn't ever happen. Have you
  305. // checked for grues?)
  306. // Note: you do not need to fix anything here.
  307. const previous_entry = self.getEntry(current_entry.coming_from.?);
  308. if (previous_entry == null) return TripError.EatenByAGrue;
  309. current_entry = previous_entry.?;
  310. }
  311. }
  312. };
  313. pub fn main() void {
  314. // Here's where the hermit decides where he would like to go. Once
  315. // you get the program working, try some different Places on the
  316. // map!
  317. const start = &a; // Archer's Point
  318. const destination = &f; // Fox Pond
  319. // Store each Path array as a slice in each Place. As mentioned
  320. // above, we needed to delay making these references to avoid
  321. // creating a dependency loop when the compiler is trying to
  322. // figure out how to allocate space for each item.
  323. a.paths = a_paths[0..];
  324. b.paths = b_paths[0..];
  325. c.paths = c_paths[0..];
  326. d.paths = d_paths[0..];
  327. e.paths = e_paths[0..];
  328. f.paths = f_paths[0..];
  329. // Now we create an instance of the notebook and add the first
  330. // "start" entry. Note the null values. Read the comments for the
  331. // checkNote() method above to see how this entry gets added to
  332. // the notebook.
  333. var notebook = HermitsNotebook{};
  334. var working_note = NotebookEntry{
  335. .place = start,
  336. .coming_from = null,
  337. .via_path = null,
  338. .dist_to_reach = 0,
  339. };
  340. notebook.checkNote(working_note);
  341. // Get the next entry from the notebook (the first being the
  342. // "start" entry we just added) until we run out, at which point
  343. // we'll have checked every reachable Place.
  344. while (notebook.hasNextEntry()) {
  345. var place_entry = notebook.getNextEntry();
  346. // For every Path that leads FROM the current Place, create a
  347. // new note (in the form of a NotebookEntry) with the
  348. // destination Place and the total distance from the start to
  349. // reach that place. Again, read the comments for the
  350. // checkNote() method to see how this works.
  351. for (place_entry.place.paths) |*path| {
  352. working_note = NotebookEntry{
  353. .place = path.to,
  354. .coming_from = place_entry.place,
  355. .via_path = path,
  356. .dist_to_reach = place_entry.dist_to_reach + path.dist,
  357. };
  358. notebook.checkNote(working_note);
  359. }
  360. }
  361. // Once the loop above is complete, we've calculated the shortest
  362. // path to every reachable Place! What we need to do now is set
  363. // aside memory for the trip and have the hermit's notebook fill
  364. // in the trip from the destination back to the path. Note that
  365. // this is the first time we've actually used the destination!
  366. var trip = [_]?TripItem{null} ** (place_count * 2);
  367. notebook.getTripTo(trip[0..], destination) catch |err| {
  368. print("Oh no! {}\n", .{err});
  369. return;
  370. };
  371. // Print the trip with a little helper function below.
  372. printTrip(trip[0..]);
  373. }
  374. // Remember that trips will be a series of alternating TripItems
  375. // containing a Place or Path from the destination back to the start.
  376. // The remaining space in the trip array will contain null values, so
  377. // we need to loop through the items in reverse, skipping nulls, until
  378. // we reach the destination at the front of the array.
  379. fn printTrip(trip: []?TripItem) void {
  380. // We convert the usize length to a u8 with @intCast(), a
  381. // builtin function just like @import(). We'll learn about
  382. // these properly in a later exercise.
  383. var i: u8 = @intCast(u8, trip.len);
  384. while (i > 0) {
  385. i -= 1;
  386. if (trip[i] == null) continue;
  387. trip[i].?.printMe();
  388. }
  389. print("\n", .{});
  390. }
  391. // Going deeper:
  392. //
  393. // In computer science terms, our map places are "nodes" or "vertices" and
  394. // the paths are "edges". Together, they form a "weighted, directed
  395. // graph". It is "weighted" because each path has a distance (also
  396. // known as a "cost"). It is "directed" because each path goes FROM
  397. // one place TO another place (undirected graphs allow you to travel
  398. // on an edge in either direction).
  399. //
  400. // Since we append new notebook entries at the end of the list and
  401. // then explore each sequentially from the beginning (like a "todo"
  402. // list), we are treating the notebook as a "First In, First Out"
  403. // (FIFO) queue.
  404. //
  405. // Since we examine all closest paths first before trying further ones
  406. // (thanks to the "todo" queue), we are performing a "Breadth-First
  407. // Search" (BFS).
  408. //
  409. // By tracking "lowest cost" paths, we can also say that we're
  410. // performing a "least-cost search".
  411. //
  412. // Even more specifically, the Hermit's Notebook most closely
  413. // resembles the Shortest Path Faster Algorithm (SPFA), attributed to
  414. // Edward F. Moore. By replacing our simple FIFO queue with a
  415. // "priority queue", we would basically have Dijkstra's algorithm. A
  416. // priority queue retrieves items sorted by "weight" (in our case, it
  417. // would keep the paths with the shortest distance at the front of the
  418. // queue). Dijkstra's algorithm is more efficient because longer paths
  419. // can be eliminated more quickly. (Work it out on paper to see why!)