mirror of
				https://codeberg.org/ziglings/exercises.git
				synced 2025-11-04 04:35:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			217 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
//
 | 
						|
// 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 printMe(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, 0..) |*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
 | 
						|
 | 
						|
    // We could either have this:
 | 
						|
    //
 | 
						|
    //   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..];
 | 
						|
    //
 | 
						|
    // or this comptime wizardry:
 | 
						|
    //
 | 
						|
    const letters = [_][]const u8{ "a", "b", "c", "d", "e", "f" };
 | 
						|
    inline for (letters) |letter| {
 | 
						|
        @field(@This(), letter).paths = @field(@This(), letter ++ "_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].?.printMe();
 | 
						|
    }
 | 
						|
 | 
						|
    print("\n", .{});
 | 
						|
}
 |