123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- //
- // Quiz Time!
- //
- // Let's revisit the Hermit's Map from Quiz 7.
- //
- // Oh, don't worry, it's not nearly as big without all the
- // explanatory comments. And we're only going to change one part
- // of it.
- //
- const print = @import("std").debug.print;
- const TripError = error{ Unreachable, EatenByAGrue };
- const Place = struct {
- name: []const u8,
- paths: []const Path = undefined,
- };
- var a = Place{ .name = "Archer's Point" };
- var b = Place{ .name = "Bridge" };
- var c = Place{ .name = "Cottage" };
- var d = Place{ .name = "Dogwood Grove" };
- var e = Place{ .name = "East Pond" };
- var f = Place{ .name = "Fox Pond" };
- // Remember how we didn't have to declare the numeric type of the
- // place_count because it is only used at compile time? That
- // probably makes a lot more sense now. :-)
- const place_count = 6;
- const Path = struct {
- from: *const Place,
- to: *const Place,
- dist: u8,
- };
- // Okay, so as you may recall, we had to create each Path struct
- // by hand and each one took 5 lines of code to define:
- //
- // Path{
- // .from = &a, // from: Archer's Point
- // .to = &b, // to: Bridge
- // .dist = 2,
- // },
- //
- // Well, armed with the knowledge that we can run code at compile
- // time, we can perhaps shorten this a bit with a simple function
- // instead.
- //
- // Please fill in the body of this function!
- fn makePath(from: *Place, to: *Place, dist: u8) Path {
- }
- // Using our new function, these path definitions take up considerably less
- // space in our program now!
- const a_paths = [_]Path{ makePath(&a, &b, 2) };
- const b_paths = [_]Path{ makePath(&b, &a, 2), makePath(&b, &d, 1) };
- const c_paths = [_]Path{ makePath(&c, &d, 3), makePath(&c, &e, 2) };
- const d_paths = [_]Path{ makePath(&d, &b, 1), makePath(&d, &c, 3), makePath(&d, &f, 7) };
- const e_paths = [_]Path{ makePath(&e, &c, 2), makePath(&e, &f, 1) };
- const f_paths = [_]Path{ makePath(&f, &d, 7) };
- //
- // But is it more readable? That could be argued either way.
- //
- // We've seen that it is possible to parse strings at compile
- // time, so the sky's really the limit on how fancy we could get
- // with this.
- //
- // For example, we could create our own "path language" and
- // create Paths from that. Something like this, perhaps:
- //
- // a -> (b[2])
- // b -> (a[2] d[1])
- // c -> (d[3] e[2])
- // ...
- //
- // Feel free to implement something like that as a SUPER BONUS EXERCISE!
- const TripItem = union(enum) {
- place: *const Place,
- path: *const Path,
- fn print(self: TripItem) void {
- switch (self) {
- .place => |p| print("{s}", .{p.name}),
- .path => |p| print("--{}->", .{p.dist}),
- }
- }
- };
- const NotebookEntry = struct {
- place: *const Place,
- coming_from: ?*const Place,
- via_path: ?*const Path,
- dist_to_reach: u16,
- };
- const HermitsNotebook = struct {
- entries: [place_count]?NotebookEntry = .{null} ** place_count,
- next_entry: u8 = 0,
- end_of_entries: u8 = 0,
- fn getEntry(self: *HermitsNotebook, place: *const Place) ?*NotebookEntry {
- for (self.entries) |*entry, i| {
- if (i >= self.end_of_entries) break;
- if (place == entry.*.?.place) return &entry.*.?;
- }
- return null;
- }
- fn checkNote(self: *HermitsNotebook, note: NotebookEntry) void {
- var existing_entry = self.getEntry(note.place);
- if (existing_entry == null) {
- self.entries[self.end_of_entries] = note;
- self.end_of_entries += 1;
- } else if (note.dist_to_reach < existing_entry.?.dist_to_reach) {
- existing_entry.?.* = note;
- }
- }
- fn hasNextEntry(self: *HermitsNotebook) bool {
- return self.next_entry < self.end_of_entries;
- }
- fn getNextEntry(self: *HermitsNotebook) *const NotebookEntry {
- defer self.next_entry += 1;
- return &self.entries[self.next_entry].?;
- }
- fn getTripTo(self: *HermitsNotebook, trip: []?TripItem, dest: *Place) TripError!void {
- const destination_entry = self.getEntry(dest);
- if (destination_entry == null) {
- return TripError.Unreachable;
- }
- var current_entry = destination_entry.?;
- var i: u8 = 0;
- while (true) : (i += 2) {
- trip[i] = TripItem{ .place = current_entry.place };
- if (current_entry.coming_from == null) break;
- trip[i + 1] = TripItem{ .path = current_entry.via_path.? };
- const previous_entry = self.getEntry(current_entry.coming_from.?);
- if (previous_entry == null) return TripError.EatenByAGrue;
- current_entry = previous_entry.?;
- }
- }
- };
- pub fn main() void {
- const start = &a; // Archer's Point
- const destination = &f; // Fox Pond
- // TODO: can we neaten this up????
- a.paths = a_paths[0..];
- b.paths = b_paths[0..];
- c.paths = c_paths[0..];
- d.paths = d_paths[0..];
- e.paths = e_paths[0..];
- f.paths = f_paths[0..];
- var notebook = HermitsNotebook{};
- var working_note = NotebookEntry{
- .place = start,
- .coming_from = null,
- .via_path = null,
- .dist_to_reach = 0,
- };
- notebook.checkNote(working_note);
- while (notebook.hasNextEntry()) {
- var place_entry = notebook.getNextEntry();
- for (place_entry.place.paths) |*path| {
- working_note = NotebookEntry{
- .place = path.to,
- .coming_from = place_entry.place,
- .via_path = path,
- .dist_to_reach = place_entry.dist_to_reach + path.dist,
- };
- notebook.checkNote(working_note);
- }
- }
- var trip = [_]?TripItem{null} ** (place_count * 2);
- notebook.getTripTo(trip[0..], destination) catch |err| {
- print("Oh no! {}\n", .{err});
- return;
- };
- printTrip(trip[0..]);
- }
- fn printTrip(trip: []?TripItem) void {
- var i: u8 = @intCast(u8, trip.len);
- while (i > 0) {
- i -= 1;
- if (trip[i] == null) continue;
- trip[i].?.print();
- }
- print("\n", .{});
- }
|