From e0259f43a726f61da14686de802021fcdb9aacd0 Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Fri, 3 Apr 2026 13:35:56 +0200 Subject: Insert space for additional async exercises --- build.zig | 62 ++-- exercises/090_async7.zig | 2 +- exercises/091_async8.zig | 67 ++-- exercises/092_interfaces.zig | 127 -------- exercises/093_hello_c.zig | 68 ---- exercises/094_c_math.zig | 41 --- exercises/095_for3.zig | 81 ----- exercises/095_interfaces.zig | 127 ++++++++ exercises/096_hello_c.zig | 68 ++++ exercises/096_memory_allocation.zig | 78 ----- exercises/097_bit_manipulation.zig | 95 ------ exercises/097_c_math.zig | 41 +++ exercises/098_bit_manipulation2.zig | 64 ---- exercises/098_for3.zig | 81 +++++ exercises/099_formatting.zig | 140 -------- exercises/099_memory_allocation.zig | 78 +++++ exercises/100_bit_manipulation.zig | 95 ++++++ exercises/100_for4.zig | 62 ---- exercises/101_bit_manipulation2.zig | 64 ++++ exercises/101_for5.zig | 126 -------- exercises/102_formatting.zig | 140 ++++++++ exercises/102_testing.zig | 96 ------ exercises/103_for4.zig | 62 ++++ exercises/103_tokenization.zig | 148 --------- exercises/104_for5.zig | 126 ++++++++ exercises/104_threading.zig | 129 -------- exercises/105_testing.zig | 96 ++++++ exercises/105_threading2.zig | 107 ------ exercises/106_files.zig | 102 ------ exercises/106_tokenization.zig | 148 +++++++++ exercises/107_files2.zig | 61 ---- exercises/107_threading.zig | 129 ++++++++ exercises/108_labeled_switch.zig | 79 ----- exercises/108_threading2.zig | 107 ++++++ exercises/109_files.zig | 102 ++++++ exercises/109_vectors.zig | 147 --------- exercises/110_files2.zig | 61 ++++ exercises/110_quiz9.zig | 484 ---------------------------- exercises/111_labeled_switch.zig | 79 +++++ exercises/111_packed.zig | 175 ---------- exercises/112_packed2.zig | 78 ----- exercises/112_vectors.zig | 147 +++++++++ exercises/113_quiz9.zig | 484 ++++++++++++++++++++++++++++ exercises/114_packed.zig | 175 ++++++++++ exercises/115_packed2.zig | 78 +++++ patches/patches/091_async8.patch | 11 + patches/patches/092_interfaces.patch | 11 - patches/patches/093_hello_c.patch | 11 - patches/patches/094_c_math.patch | 11 - patches/patches/095_for3.patch | 20 -- patches/patches/095_interfaces.patch | 11 + patches/patches/096_hello_c.patch | 11 + patches/patches/096_memory_allocation.patch | 11 - patches/patches/097_bit_manipulation.patch | 11 - patches/patches/097_c_math.patch | 11 + patches/patches/098_bit_manipulation2.patch | 9 - patches/patches/098_for3.patch | 20 ++ patches/patches/099_formatting.patch | 11 - patches/patches/099_memory_allocation.patch | 11 + patches/patches/100_bit_manipulation.patch | 11 + patches/patches/100_for4.patch | 11 - patches/patches/101_bit_manipulation2.patch | 9 + patches/patches/101_for5.patch | 11 - patches/patches/102_formatting.patch | 11 + patches/patches/102_testing.patch | 18 -- patches/patches/103_for4.patch | 11 + patches/patches/103_tokenization.patch | 11 - patches/patches/104_for5.patch | 11 + patches/patches/104_threading.patch | 17 - patches/patches/105_testing.patch | 18 ++ patches/patches/105_threading2.patch | 13 - patches/patches/106_files.patch | 20 -- patches/patches/106_tokenization.patch | 11 + patches/patches/107_files2.patch | 26 -- patches/patches/107_threading.patch | 17 + patches/patches/108_labeled_switch.patch | 18 -- patches/patches/108_threading2.patch | 13 + patches/patches/109_files.patch | 20 ++ patches/patches/109_vectors.patch | 13 - patches/patches/110_files2.patch | 26 ++ patches/patches/110_quiz9.patch | 56 ---- patches/patches/111_labeled_switch.patch | 18 ++ patches/patches/111_packed.patch | 57 ---- patches/patches/112_packed2.patch | 32 -- patches/patches/112_vectors.patch | 13 + patches/patches/113_quiz9.patch | 56 ++++ patches/patches/114_packed.patch | 57 ++++ patches/patches/115_packed2.patch | 32 ++ 88 files changed, 2982 insertions(+), 2932 deletions(-) delete mode 100644 exercises/092_interfaces.zig delete mode 100644 exercises/093_hello_c.zig delete mode 100644 exercises/094_c_math.zig delete mode 100644 exercises/095_for3.zig create mode 100644 exercises/095_interfaces.zig create mode 100644 exercises/096_hello_c.zig delete mode 100644 exercises/096_memory_allocation.zig delete mode 100644 exercises/097_bit_manipulation.zig create mode 100644 exercises/097_c_math.zig delete mode 100644 exercises/098_bit_manipulation2.zig create mode 100644 exercises/098_for3.zig delete mode 100644 exercises/099_formatting.zig create mode 100644 exercises/099_memory_allocation.zig create mode 100644 exercises/100_bit_manipulation.zig delete mode 100644 exercises/100_for4.zig create mode 100644 exercises/101_bit_manipulation2.zig delete mode 100644 exercises/101_for5.zig create mode 100644 exercises/102_formatting.zig delete mode 100644 exercises/102_testing.zig create mode 100644 exercises/103_for4.zig delete mode 100644 exercises/103_tokenization.zig create mode 100644 exercises/104_for5.zig delete mode 100644 exercises/104_threading.zig create mode 100644 exercises/105_testing.zig delete mode 100644 exercises/105_threading2.zig delete mode 100644 exercises/106_files.zig create mode 100644 exercises/106_tokenization.zig delete mode 100644 exercises/107_files2.zig create mode 100644 exercises/107_threading.zig delete mode 100644 exercises/108_labeled_switch.zig create mode 100644 exercises/108_threading2.zig create mode 100644 exercises/109_files.zig delete mode 100644 exercises/109_vectors.zig create mode 100644 exercises/110_files2.zig delete mode 100644 exercises/110_quiz9.zig create mode 100644 exercises/111_labeled_switch.zig delete mode 100644 exercises/111_packed.zig delete mode 100644 exercises/112_packed2.zig create mode 100644 exercises/112_vectors.zig create mode 100644 exercises/113_quiz9.zig create mode 100644 exercises/114_packed.zig create mode 100644 exercises/115_packed2.zig create mode 100644 patches/patches/091_async8.patch delete mode 100644 patches/patches/092_interfaces.patch delete mode 100644 patches/patches/093_hello_c.patch delete mode 100644 patches/patches/094_c_math.patch delete mode 100644 patches/patches/095_for3.patch create mode 100644 patches/patches/095_interfaces.patch create mode 100644 patches/patches/096_hello_c.patch delete mode 100644 patches/patches/096_memory_allocation.patch delete mode 100644 patches/patches/097_bit_manipulation.patch create mode 100644 patches/patches/097_c_math.patch delete mode 100644 patches/patches/098_bit_manipulation2.patch create mode 100644 patches/patches/098_for3.patch delete mode 100644 patches/patches/099_formatting.patch create mode 100644 patches/patches/099_memory_allocation.patch create mode 100644 patches/patches/100_bit_manipulation.patch delete mode 100644 patches/patches/100_for4.patch create mode 100644 patches/patches/101_bit_manipulation2.patch delete mode 100644 patches/patches/101_for5.patch create mode 100644 patches/patches/102_formatting.patch delete mode 100644 patches/patches/102_testing.patch create mode 100644 patches/patches/103_for4.patch delete mode 100644 patches/patches/103_tokenization.patch create mode 100644 patches/patches/104_for5.patch delete mode 100644 patches/patches/104_threading.patch create mode 100644 patches/patches/105_testing.patch delete mode 100644 patches/patches/105_threading2.patch delete mode 100644 patches/patches/106_files.patch create mode 100644 patches/patches/106_tokenization.patch delete mode 100644 patches/patches/107_files2.patch create mode 100644 patches/patches/107_threading.patch delete mode 100644 patches/patches/108_labeled_switch.patch create mode 100644 patches/patches/108_threading2.patch create mode 100644 patches/patches/109_files.patch delete mode 100644 patches/patches/109_vectors.patch create mode 100644 patches/patches/110_files2.patch delete mode 100644 patches/patches/110_quiz9.patch create mode 100644 patches/patches/111_labeled_switch.patch delete mode 100644 patches/patches/111_packed.patch delete mode 100644 patches/patches/112_packed2.patch create mode 100644 patches/patches/112_vectors.patch create mode 100644 patches/patches/113_quiz9.patch create mode 100644 patches/patches/114_packed.patch create mode 100644 patches/patches/115_packed2.patch diff --git a/build.zig b/build.zig index 3dc0520..5e5d3de 100644 --- a/build.zig +++ b/build.zig @@ -1153,17 +1153,29 @@ const exercises = [_]Exercise{ }, .{ .main_file = "090_async7.zig", - .output = "Counter: 400 (expected: 400)", + .output = "Counter: 400", }, .{ .main_file = "091_async8.zig", - .output = "ABCDEF", + .output = "Sum of 1..10 = 55", + }, + .{ + .main_file = "092_async9.zig", + .output = "", + .skip = true, + }, + .{ + .main_file = "093_async10.zig", + .output = "", + .skip = true, + }, + .{ + .main_file = "094_async_quiz.zig", + .output = "", .skip = true, - .skip_hint = "async has not been implemented in the current compiler version.", }, - .{ - .main_file = "092_interfaces.zig", + .main_file = "095_interfaces.zig", .output = \\Daily Insect Report: \\Ant is alive. @@ -1172,33 +1184,33 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "093_hello_c.zig", + .main_file = "096_hello_c.zig", .output = "Hello C from Zig! - C result is 17 chars written.", .link_libc = true, }, .{ - .main_file = "094_c_math.zig", + .main_file = "097_c_math.zig", .output = "The normalized angle of 765.2 degrees is 45.2 degrees.", .link_libc = true, }, .{ - .main_file = "095_for3.zig", + .main_file = "098_for3.zig", .output = "1 2 4 7 8 11 13 14 16 17 19\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15", }, .{ - .main_file = "096_memory_allocation.zig", + .main_file = "099_memory_allocation.zig", .output = "Running Average: 0.30 0.25 0.20 0.18 0.22", }, .{ - .main_file = "097_bit_manipulation.zig", + .main_file = "100_bit_manipulation.zig", .output = "x = 1011; y = 1101", }, .{ - .main_file = "098_bit_manipulation2.zig", + .main_file = "101_bit_manipulation2.zig", .output = "Is this a pangram? true!", }, .{ - .main_file = "099_formatting.zig", + .main_file = "102_formatting.zig", .output = \\ \\ X | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @@ -1235,11 +1247,11 @@ const exercises = [_]Exercise{ , }, .{ - .main_file = "100_for4.zig", + .main_file = "103_for4.zig", .output = "Arrays match!", }, .{ - .main_file = "101_for5.zig", + .main_file = "104_for5.zig", .output = \\1. Wizard (Gold: 25, XP: 40) \\2. Bard (Gold: 11, XP: 17) @@ -1248,12 +1260,12 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "102_testing.zig", + .main_file = "105_testing.zig", .output = "", .kind = .@"test", }, .{ - .main_file = "103_tokenization.zig", + .main_file = "106_tokenization.zig", .output = \\My \\name @@ -1274,7 +1286,7 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "104_threading.zig", + .main_file = "107_threading.zig", .output = \\Starting work... \\thread 1: started. @@ -1288,32 +1300,32 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "105_threading2.zig", + .main_file = "108_threading2.zig", .output = "PI ≈ 3.14159265", }, .{ - .main_file = "106_files.zig", + .main_file = "109_files.zig", .output = "Successfully wrote 18 bytes.", }, .{ - .main_file = "107_files2.zig", + .main_file = "110_files2.zig", .output = \\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \\Successfully Read 18 bytes: It's zigling time! , // pay attention to the comma }, .{ - .main_file = "108_labeled_switch.zig", + .main_file = "111_labeled_switch.zig", .output = "The pull request has been merged.", }, .{ - .main_file = "109_vectors.zig", + .main_file = "112_vectors.zig", .output = \\Max difference (old fn): 0.014 \\Max difference (new fn): 0.014 , // pay attention to the comma }, - .{ .main_file = "110_quiz9.zig", .output = + .{ .main_file = "113_quiz9.zig", .output = \\Toggle pins with XOR on PORTB \\----------------------------- \\ 1100 // (initial state of PORTB) @@ -1345,11 +1357,11 @@ const exercises = [_]Exercise{ \\= 0110 }, .{ - .main_file = "111_packed.zig", + .main_file = "114_packed.zig", .output = "", }, .{ - .main_file = "112_packed2.zig", + .main_file = "115_packed2.zig", .output = "", }, .{ diff --git a/exercises/090_async7.zig b/exercises/090_async7.zig index f914aef..bfe6ffd 100644 --- a/exercises/090_async7.zig +++ b/exercises/090_async7.zig @@ -42,7 +42,7 @@ pub fn main(init: std.process.Init) !void { try group.await(io); - print("Counter: {} (expected: 400)\n", .{state.counter}); + print("Counter: {}\n", .{state.counter}); } fn increment(io: std.Io, state: *SharedState, times: u32) void { diff --git a/exercises/091_async8.zig b/exercises/091_async8.zig index cd9c975..10921c3 100644 --- a/exercises/091_async8.zig +++ b/exercises/091_async8.zig @@ -1,35 +1,62 @@ // -// You have doubtless noticed that 'suspend' requires a block -// expression like so: +// Tasks often need to communicate! Io provides Queue for this — +// a bounded, thread-safe channel for passing data between tasks: // -// suspend {} +// var backing: [16]u32 = undefined; +// var queue: std.Io.Queue(u32) = .init(&backing); // -// The suspend block executes when a function suspends. To get -// sense for when this happens, please make the following -// program print the string +// // Producer task: +// try queue.putOne(io, value); // blocks if queue is full // -// "ABCDEF" +// // Consumer task: +// const val = try queue.getOne(io); // blocks if queue is empty // -const print = @import("std").debug.print; +// When the producer is done, it calls queue.close(io) to signal +// that no more data is coming. After that, getOne() will return +// error.Closed once the queue is drained. +// +// This is the classic producer/consumer pattern — one task +// generates work, another processes it, and the queue handles +// all the synchronization automatically. +// +// Fix this program: the producer sends numbers 1..10, the +// consumer sums them up. The expected sum is 55. +// +const std = @import("std"); +const print = std.debug.print; -pub fn main() void { - print("A", .{}); +pub fn main(init: std.process.Init) !void { + const io = init.io; - var frame = async suspendable(); + var backing: [4]u32 = undefined; + var queue: std.Io.Queue(u32) = .init(&backing); - print("X", .{}); + var group: std.Io.Group = .init; - resume frame; + group.async(io, producer, .{ io, &queue }); + group.async(io, consumer, .{ io, &queue }); - print("F", .{}); + try group.await(io); } -fn suspendable() void { - print("X", .{}); - - suspend { - print("X", .{}); +fn producer(io: std.Io, queue: *std.Io.Queue(u32)) void { + // Send numbers 1 through 10 into the queue. + for (1..11) |i| { + // What Queue method sends a single element, blocking if full? + queue.???(io, @intCast(i)) catch return; } + // Signal that we're done sending. + queue.close(io); +} - print("X", .{}); +fn consumer(io: std.Io, queue: *std.Io.Queue(u32)) void { + var sum: u32 = 0; + while (true) { + const value = queue.getOne(io) catch |err| switch (err) { + error.Closed => break, + error.Canceled => return, + }; + sum += value; + } + print("Sum of 1..10 = {}\n", .{sum}); } diff --git a/exercises/092_interfaces.zig b/exercises/092_interfaces.zig deleted file mode 100644 index 7775dd5..0000000 --- a/exercises/092_interfaces.zig +++ /dev/null @@ -1,127 +0,0 @@ -// -// Remember our ant and bee simulator constructed with unions -// back in exercises 55 and 56? There, we demonstrated that -// unions allow us to treat different data types in a uniform -// manner. -// -// One neat feature was using tagged unions to create a single -// function to print a status for ants *or* bees by switching: -// -// switch (insect) { -// .still_alive => ... // (print ant stuff) -// .flowers_visited => ... // (print bee stuff) -// } -// -// Well, that simulation was running just fine until a new insect -// arrived in the virtual garden, a grasshopper! -// -// Doctor Zoraptera started to add grasshopper code to the -// program, but then she backed away from her keyboard with an -// angry hissing sound. She had realized that having code for -// each insect in one place and code to print each insect in -// another place was going to become unpleasant to maintain when -// the simulation expanded to hundreds of different insects. -// -// Thankfully, Zig has another comptime feature we can use -// to get out of this dilemma called the 'inline else'. -// -// We can replace this redundant code: -// -// switch (thing) { -// .a => |a| special(a), -// .b => |b| normal(b), -// .c => |c| normal(c), -// .d => |d| normal(d), -// .e => |e| normal(e), -// ... -// } -// -// With: -// -// switch (thing) { -// .a => |a| special(a), -// inline else => |t| normal(t), -// } -// -// We can have special handling of some cases and then Zig -// handles the rest of the matches for us. -// -// With this feature, you decide to make an Insect union with a -// single uniform 'print()' function. All of the insects can -// then be responsible for printing themselves. And Doctor -// Zoraptera can calm down and stop gnawing on the furniture. -// -const std = @import("std"); - -const Ant = struct { - still_alive: bool, - - pub fn print(self: Ant) void { - std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"}); - } -}; - -const Bee = struct { - flowers_visited: u16, - - pub fn print(self: Bee) void { - std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited}); - } -}; - -// Here's the new grasshopper. Notice how we've also added print -// methods to each insect. -const Grasshopper = struct { - distance_hopped: u16, - - pub fn print(self: Grasshopper) void { - std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped}); - } -}; - -const Insect = union(enum) { - ant: Ant, - bee: Bee, - grasshopper: Grasshopper, - - // Thanks to 'inline else', we can think of this print() as - // being an interface method. Any member of this union with - // a print() method can be treated uniformly by outside - // code without needing to know any other details. Cool! - pub fn print(self: Insect) void { - switch (self) { - inline else => |case| return case.print(), - } - } -}; - -pub fn main() !void { - const my_insects = [_]Insect{ - Insect{ .ant = Ant{ .still_alive = true } }, - Insect{ .bee = Bee{ .flowers_visited = 17 } }, - Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } }, - }; - - std.debug.print("Daily Insect Report:\n", .{}); - for (my_insects) |insect| { - // Almost done! We want to print() each insect with a - // single method call here. - ??? - } -} - -// Our print() method in the Insect union above demonstrates -// something very similar to the object-oriented concept of an -// abstract data type. That is, the Insect type doesn't contain -// the underlying data, and the print() function doesn't -// actually do the printing. -// -// The point of an interface is to support generic programming: -// the ability to treat different things as if they were the -// same to cut down on clutter and conceptual complexity. -// -// The Daily Insect Report doesn't need to worry about *which* -// insects are in the report - they all print the same way via -// the interface! -// -// Doctor Zoraptera loves it. diff --git a/exercises/093_hello_c.zig b/exercises/093_hello_c.zig deleted file mode 100644 index 182e0b0..0000000 --- a/exercises/093_hello_c.zig +++ /dev/null @@ -1,68 +0,0 @@ -// -// When Andrew Kelley announced the idea of a new programming language -// - namely Zig - in his blog on February 8, 2016, he also immediately -// stated his ambitious goal: to replace the C language! -// -// In order to be able to achieve this goal at all, Zig should be -// as compatible as possible with its "predecessor". -// Only if it is possible to exchange individual modules in existing -// C programs without having to use complicated wrappers, -// the undertaking has a chance of success. -// -// So it is not surprising that calling C functions and vice versa -// is extremely "smooth". -// -// To call C functions in Zig, you only need to specify the library -// that contains said function. For this purpose there is a built-in -// function corresponding to the well-known @import(): -// -// @cImport() -// -// All required libraries can now be included in the usual Zig notation: -// -// const c = @cImport({ -// @cInclude("stdio.h"); -// @cInclude("..."); -// }); -// -// Now a function can be called via the (in this example) constant 'c': -// -// c.puts("Hello world!"); -// -// By the way, most C functions have return values in the form of an -// integer value. Errors can then be evaluated (return < 0) or other -// information can be obtained. For example, 'puts' returns the number -// of characters output. -// -// So that all this does not remain a dry theory now, let's just start -// and call a C function out of Zig. - -// our well-known "import" for Zig -const std = @import("std"); - -// and here the new import for C -const c = @cImport({ - @cInclude("unistd.h"); -}); - -pub fn main() void { - - // In order to output text that can be evaluated by the - // Zig Builder, we need to write it to the Error output. - // In Zig, we do this with "std.debug.print" and in C we can - // specify a file descriptor i.e. 2 for error console. - // - // In this exercise we use 'write' to output 17 chars, - // but something is still missing... - const c_res = write(2, "Hello C from Zig!", 17); - - // let's see what the result from C is: - std.debug.print(" - C result is {d} chars written.\n", .{c_res}); -} -// -// Something must be considered when compiling with C functions. -// Namely that the Zig compiler knows that it should include -// corresponding libraries. For this purpose we call the compiler -// with the parameter "lc" for such a program, -// e.g. "zig run -lc hello_c.zig". -// diff --git a/exercises/094_c_math.zig b/exercises/094_c_math.zig deleted file mode 100644 index ec59a86..0000000 --- a/exercises/094_c_math.zig +++ /dev/null @@ -1,41 +0,0 @@ -// -// Often, C functions are used where no equivalent Zig function exists -// yet. Okay, that's getting less and less. ;-) -// -// Since the integration of a C function is very simple, as already -// seen in the last exercise, it naturally offers itself to use the -// very large variety of C functions for our own programs. -// As an example: -// -// Let's say we have a given angle of 765.2 degrees. If we want to -// normalize that, it means that we have to subtract X * 360 degrees -// to get the correct angle. -// How could we do that? A good method is to use the modulo function. -// But if we write "765.2 % 360", it only works with float values -// that are known at compile time. -// In Zig, we would use @mod(a, b) instead. -// -// Let us now assume that we cannot do this in Zig, but only with -// a C function from the standard library. In the library "math", -// there is a function called "fmod"; the "f" stands for floating -// and means that we can solve modulo for real numbers. With this -// function, it should be possible to normalize our angle. -// Let's go. - -const std = @import("std"); - -const c = @cImport({ - // What do we need here? - ??? -}); - -pub fn main() !void { - const angle = 765.2; - const circle = 360; - - // Here we call the C function 'fmod' to get our normalized angle. - const result = c.fmod(angle, circle); - - // We use formatters for the desired precision and to truncate the decimal places - std.debug.print("The normalized angle of {d: >3.1} degrees is {d: >3.1} degrees.\n", .{ angle, result }); -} diff --git a/exercises/095_for3.zig b/exercises/095_for3.zig deleted file mode 100644 index 77a1b56..0000000 --- a/exercises/095_for3.zig +++ /dev/null @@ -1,81 +0,0 @@ -// -// The Zig language is in rapid development and continuously -// improves the language constructs. Ziglings evolves with it. -// -// Until version 0.11, Zig's 'for' loops did not directly -// replicate the functionality of the C-style: "for(a;b;c)" -// which are so well suited for iterating over a numeric -// sequence. -// -// Instead, 'while' loops with counters clumsily stood in their -// place: -// -// var i: usize = 0; -// while (i < 10) : (i += 1) { -// // Here variable 'i' will have each value 0 to 9. -// } -// -// But here we are in the glorious future and Zig's 'for' loops -// can now take this form: -// -// for (0..10) |i| { -// // Here variable 'i' will have each value 0 to 9. -// } -// -// The key to understanding this example is to know that '0..9' -// uses the new range syntax: -// -// 0..10 is a range from 0 to 9 -// 1..4 is a range from 1 to 3 -// -// Crucially, the end value is EXCLUSIVE. -// -// At the moment, ranges in loops are only supported in 'for' loops. -// -// Perhaps you recall Exercise 13? We were printing a numeric -// sequence like so: -// -// var n: u32 = 1; -// -// // I want to print every number between 1 and 20 that is NOT -// // divisible by 3 or 5. -// while (n <= 20) : (n += 1) { -// // The '%' symbol is the "modulo" operator and it -// // returns the remainder after division. -// if (n % 3 == 0) continue; -// if (n % 5 == 0) continue; -// std.debug.print("{} ", .{n}); -// } -// -// Let's try out the new form of 'for' to re-implement that -// exercise: -// -const std = @import("std"); - -pub fn main() void { - - // I want to print every number between 1 and 20 that is NOT - // divisible by 3 or 5. - for (???) |n| { - - // The '%' symbol is the "modulo" operator and it - // returns the remainder after division. - if (n % 3 == 0) continue; - if (n % 5 == 0) continue; - std.debug.print("{} ", .{n}); - } - - std.debug.print("\n", .{}); - - // Let's also print every number from 1 through 15 - for (???) |n| { - std.debug.print("{} ", .{n}); - } - std.debug.print("\n", .{}); -} -// -// That's a bit nicer, right? -// -// Of course, both 'while' and 'for' have different advantages. -// Exercises 11, 12, and 14 would NOT be simplified by switching -// a 'while' for a 'for'. diff --git a/exercises/095_interfaces.zig b/exercises/095_interfaces.zig new file mode 100644 index 0000000..7775dd5 --- /dev/null +++ b/exercises/095_interfaces.zig @@ -0,0 +1,127 @@ +// +// Remember our ant and bee simulator constructed with unions +// back in exercises 55 and 56? There, we demonstrated that +// unions allow us to treat different data types in a uniform +// manner. +// +// One neat feature was using tagged unions to create a single +// function to print a status for ants *or* bees by switching: +// +// switch (insect) { +// .still_alive => ... // (print ant stuff) +// .flowers_visited => ... // (print bee stuff) +// } +// +// Well, that simulation was running just fine until a new insect +// arrived in the virtual garden, a grasshopper! +// +// Doctor Zoraptera started to add grasshopper code to the +// program, but then she backed away from her keyboard with an +// angry hissing sound. She had realized that having code for +// each insect in one place and code to print each insect in +// another place was going to become unpleasant to maintain when +// the simulation expanded to hundreds of different insects. +// +// Thankfully, Zig has another comptime feature we can use +// to get out of this dilemma called the 'inline else'. +// +// We can replace this redundant code: +// +// switch (thing) { +// .a => |a| special(a), +// .b => |b| normal(b), +// .c => |c| normal(c), +// .d => |d| normal(d), +// .e => |e| normal(e), +// ... +// } +// +// With: +// +// switch (thing) { +// .a => |a| special(a), +// inline else => |t| normal(t), +// } +// +// We can have special handling of some cases and then Zig +// handles the rest of the matches for us. +// +// With this feature, you decide to make an Insect union with a +// single uniform 'print()' function. All of the insects can +// then be responsible for printing themselves. And Doctor +// Zoraptera can calm down and stop gnawing on the furniture. +// +const std = @import("std"); + +const Ant = struct { + still_alive: bool, + + pub fn print(self: Ant) void { + std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"}); + } +}; + +const Bee = struct { + flowers_visited: u16, + + pub fn print(self: Bee) void { + std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited}); + } +}; + +// Here's the new grasshopper. Notice how we've also added print +// methods to each insect. +const Grasshopper = struct { + distance_hopped: u16, + + pub fn print(self: Grasshopper) void { + std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped}); + } +}; + +const Insect = union(enum) { + ant: Ant, + bee: Bee, + grasshopper: Grasshopper, + + // Thanks to 'inline else', we can think of this print() as + // being an interface method. Any member of this union with + // a print() method can be treated uniformly by outside + // code without needing to know any other details. Cool! + pub fn print(self: Insect) void { + switch (self) { + inline else => |case| return case.print(), + } + } +}; + +pub fn main() !void { + const my_insects = [_]Insect{ + Insect{ .ant = Ant{ .still_alive = true } }, + Insect{ .bee = Bee{ .flowers_visited = 17 } }, + Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } }, + }; + + std.debug.print("Daily Insect Report:\n", .{}); + for (my_insects) |insect| { + // Almost done! We want to print() each insect with a + // single method call here. + ??? + } +} + +// Our print() method in the Insect union above demonstrates +// something very similar to the object-oriented concept of an +// abstract data type. That is, the Insect type doesn't contain +// the underlying data, and the print() function doesn't +// actually do the printing. +// +// The point of an interface is to support generic programming: +// the ability to treat different things as if they were the +// same to cut down on clutter and conceptual complexity. +// +// The Daily Insect Report doesn't need to worry about *which* +// insects are in the report - they all print the same way via +// the interface! +// +// Doctor Zoraptera loves it. diff --git a/exercises/096_hello_c.zig b/exercises/096_hello_c.zig new file mode 100644 index 0000000..182e0b0 --- /dev/null +++ b/exercises/096_hello_c.zig @@ -0,0 +1,68 @@ +// +// When Andrew Kelley announced the idea of a new programming language +// - namely Zig - in his blog on February 8, 2016, he also immediately +// stated his ambitious goal: to replace the C language! +// +// In order to be able to achieve this goal at all, Zig should be +// as compatible as possible with its "predecessor". +// Only if it is possible to exchange individual modules in existing +// C programs without having to use complicated wrappers, +// the undertaking has a chance of success. +// +// So it is not surprising that calling C functions and vice versa +// is extremely "smooth". +// +// To call C functions in Zig, you only need to specify the library +// that contains said function. For this purpose there is a built-in +// function corresponding to the well-known @import(): +// +// @cImport() +// +// All required libraries can now be included in the usual Zig notation: +// +// const c = @cImport({ +// @cInclude("stdio.h"); +// @cInclude("..."); +// }); +// +// Now a function can be called via the (in this example) constant 'c': +// +// c.puts("Hello world!"); +// +// By the way, most C functions have return values in the form of an +// integer value. Errors can then be evaluated (return < 0) or other +// information can be obtained. For example, 'puts' returns the number +// of characters output. +// +// So that all this does not remain a dry theory now, let's just start +// and call a C function out of Zig. + +// our well-known "import" for Zig +const std = @import("std"); + +// and here the new import for C +const c = @cImport({ + @cInclude("unistd.h"); +}); + +pub fn main() void { + + // In order to output text that can be evaluated by the + // Zig Builder, we need to write it to the Error output. + // In Zig, we do this with "std.debug.print" and in C we can + // specify a file descriptor i.e. 2 for error console. + // + // In this exercise we use 'write' to output 17 chars, + // but something is still missing... + const c_res = write(2, "Hello C from Zig!", 17); + + // let's see what the result from C is: + std.debug.print(" - C result is {d} chars written.\n", .{c_res}); +} +// +// Something must be considered when compiling with C functions. +// Namely that the Zig compiler knows that it should include +// corresponding libraries. For this purpose we call the compiler +// with the parameter "lc" for such a program, +// e.g. "zig run -lc hello_c.zig". +// diff --git a/exercises/096_memory_allocation.zig b/exercises/096_memory_allocation.zig deleted file mode 100644 index 58de7b0..0000000 --- a/exercises/096_memory_allocation.zig +++ /dev/null @@ -1,78 +0,0 @@ -// -// In most of the examples so far, the inputs are known at compile -// time, thus the amount of memory used by the program is fixed. -// However, if responding to input whose size is not known at compile -// time, such as: -// - user input via command-line arguments -// - inputs from another program -// -// You'll need to request memory for your program to be allocated by -// your operating system at runtime. -// -// Zig provides several different allocators. In the Zig -// documentation, it recommends the Arena allocator for simple -// programs which allocate once and then exit: -// -// const std = @import("std"); -// -// // memory allocation can fail, so the return type is !void -// pub fn main() !void { -// -// var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -// defer arena.deinit(); -// -// const allocator = arena.allocator(); -// -// const ptr = try allocator.create(i32); -// std.debug.print("ptr={*}\n", .{ptr}); -// -// const slice_ptr = try allocator.alloc(f64, 5); -// std.debug.print("slice_ptr={*}\n", .{slice_ptr}); -// } - -// Instead of a simple integer or a slice with a constant size, -// this program requires allocating a slice that is the same size -// as an input array. - -// Given a series of numbers, take the running average. In other -// words, each item N should contain the average of the last N -// elements. - -const std = @import("std"); - -fn runningAverage(arr: []const f64, avg: []f64) void { - var sum: f64 = 0; - - for (0.., arr) |index, val| { - sum += val; - const f_index: f64 = @floatFromInt(index + 1); - avg[index] = sum / f_index; - } -} - -pub fn main() !void { - // pretend this was defined by reading in user input - const arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 }; - - // initialize the allocator - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - - // free the memory on exit - defer arena.deinit(); - - // initialize the allocator - const allocator = arena.allocator(); - - // allocate memory for this array - const avg: []f64 = ???; - - runningAverage(arr, avg); - std.debug.print("Running Average: ", .{}); - for (avg) |val| { - std.debug.print("{d:.2} ", .{val}); - } - std.debug.print("\n", .{}); -} - -// For more details on memory allocation and the different types of -// memory allocators, see https://www.youtube.com/watch?v=vHWiDx_l4V0 diff --git a/exercises/097_bit_manipulation.zig b/exercises/097_bit_manipulation.zig deleted file mode 100644 index 0e64ad7..0000000 --- a/exercises/097_bit_manipulation.zig +++ /dev/null @@ -1,95 +0,0 @@ -// -// Bit manipulation is a very powerful tool, also from Zig. -// Since the dawn of the computer age, numerous algorithms have been -// developed that solve tasks solely by moving, setting, or logically -// combining bits. -// -// Zig also uses direct bit manipulation in its standard library for -// functions where possible. And it is often possible with calculations -// based on integers. -// -// At first glance, it is often not easy to understand what exactly these -// algorithms do when only "numbers" in memory areas change outwardly. -// However, it should never be forgotten that the numbers only represent -// the interpretation of the bit sequences. -// -// Quasi the reversed case we have otherwise, namely that we represent -// numbers in bit sequences. -// -// We remember: 1 byte = 8 bits = 11111111 = 255 decimal = FF hex. -// -// Zig provides all the necessary functions to change the bits inside -// a variable. It is distinguished whether the bit change leads to an -// overflow or not. The details are in the Zig documentation in section -// "Table of Operators". -// -// Here are some examples of how the bits of variables can be changed: -// -// const numOne: u8 = 15; // = 0000 1111 -// const numTwo: u8 = 245; // = 1111 0101 -// -// const res1 = numOne >> 4; // = 0000 0000 - shift right -// const res2 = numOne << 4; // = 1111 0000 - shift left -// const res3 = numOne & numTwo; // = 0000 0101 - and -// const res4 = numOne | numTwo; // = 1111 1111 - or -// const res5 = numOne ^ numTwo; // = 1111 1010 - xor -// -// -// To familiarize ourselves with bit manipulation, we start with a simple -// but often underestimated function and then add other exercises in -// loose order. -// -// The following text contains excerpts from Wikipedia. -// -// Swap -// In computer programming, the act of swapping two variables refers to -// mutually exchanging the values of the variables. Usually, this is -// done with the data in memory. For example, in a program, two variables -// may be defined thus (in pseudocode): -// -// data_item x := 1 -// data_item y := 0 -// -// swap (x, y); -// -// After swap() is performed, x will contain the value 0 and y will -// contain 1; their values have been exchanged. The simplest and probably -// most widely used method to swap two variables is to use a third temporary -// variable: -// -// define swap (x, y) -// temp := x -// x := y -// y := temp -// -// However, with integers we can also achieve the swap function simply by -// bit manipulation. To do this, the variables are mutually linked with xor -// and the result is the same. - -const std = @import("std"); -const print = std.debug.print; - -pub fn main() !void { - - // Let us use 1101 and 1011 as values for x and y - var x: u8 = 0b1101; - var y: u8 = 0b1011; - - // Now we swap the values of the two variables by doing xor on them - x ^= y; - y ^= x; - - // What must be written here? - ???; - - print("x = {b}; y = {b}\n", .{ x, y }); -} - -// This variable swap takes advantage of the fact that the value resulting -// from the xor of two values contains both of these values. -// This circumstance was (and still is) sometimes used for encryption. -// Value XOR Key = Crypto. => Crypto XOR Key = Value. -// Since this can be swapped arbitrarily, you can swap two variables in this way. -// -// For Crypto it is better not to use this, but in sorting algorithms like -// Bubble Sort it works very well. diff --git a/exercises/097_c_math.zig b/exercises/097_c_math.zig new file mode 100644 index 0000000..ec59a86 --- /dev/null +++ b/exercises/097_c_math.zig @@ -0,0 +1,41 @@ +// +// Often, C functions are used where no equivalent Zig function exists +// yet. Okay, that's getting less and less. ;-) +// +// Since the integration of a C function is very simple, as already +// seen in the last exercise, it naturally offers itself to use the +// very large variety of C functions for our own programs. +// As an example: +// +// Let's say we have a given angle of 765.2 degrees. If we want to +// normalize that, it means that we have to subtract X * 360 degrees +// to get the correct angle. +// How could we do that? A good method is to use the modulo function. +// But if we write "765.2 % 360", it only works with float values +// that are known at compile time. +// In Zig, we would use @mod(a, b) instead. +// +// Let us now assume that we cannot do this in Zig, but only with +// a C function from the standard library. In the library "math", +// there is a function called "fmod"; the "f" stands for floating +// and means that we can solve modulo for real numbers. With this +// function, it should be possible to normalize our angle. +// Let's go. + +const std = @import("std"); + +const c = @cImport({ + // What do we need here? + ??? +}); + +pub fn main() !void { + const angle = 765.2; + const circle = 360; + + // Here we call the C function 'fmod' to get our normalized angle. + const result = c.fmod(angle, circle); + + // We use formatters for the desired precision and to truncate the decimal places + std.debug.print("The normalized angle of {d: >3.1} degrees is {d: >3.1} degrees.\n", .{ angle, result }); +} diff --git a/exercises/098_bit_manipulation2.zig b/exercises/098_bit_manipulation2.zig deleted file mode 100644 index 8b51265..0000000 --- a/exercises/098_bit_manipulation2.zig +++ /dev/null @@ -1,64 +0,0 @@ -// -// Another useful application for bit manipulation is setting bits as flags. -// This is especially useful when processing lists of something and storing -// the states of the entries, e.g. a list of numbers and for each prime -// number a flag is set. -// -// As an example, let's take the Pangram exercise from Exercism: -// https://exercism.org/tracks/zig/exercises/pangram -// -// A pangram is a sentence using every letter of the alphabet at least once. -// It is case insensitive, so it doesn't matter if a letter is lower-case -// or upper-case. The best known English pangram is: -// -// "The quick brown fox jumps over the lazy dog." -// -// There are several ways to select the letters that appear in the pangram -// (and it doesn't matter if they appear once or several times). -// -// For example, you could take an array of bool and set the value to 'true' -// for each letter in the order of the alphabet (a=0; b=1; etc.) found in -// the sentence. However, this is neither memory efficient nor particularly -// fast. Instead we choose a simpler approach that is very similar in principle: -// We define a variable with at least 26 bits (e.g. u32) and set the bit for -// each letter that is found in the corresponding position. -// -// Zig provides functions for this in the standard library, but we prefer to -// solve it without these extras, after all we want to learn something. -// -const std = @import("std"); -const ascii = std.ascii; -const print = std.debug.print; - -pub fn main() !void { - // let's check the pangram - print("Is this a pangram? {}!\n", .{isPangram("The quick brown fox jumps over the lazy dog.")}); -} - -fn isPangram(str: []const u8) bool { - // first we check if the string has at least 26 characters - if (str.len < 26) return false; - - // we use a 32 bit variable of which we need 26 bits - var bits: u32 = 0; - - // loop about all characters in the string - for (str) |c| { - // if the character is an alphabetical character - if (ascii.isAscii(c) and ascii.isAlphabetic(c)) { - // then we set the bit at the position - // - // to do this, we use a little trick: - // since the letters in the ASCII table start at 65 - // and are numbered sequentially, we simply subtract the - // first letter (in this case the 'a') from the character - // found, and thus get the position of the desired bit - bits |= @as(u32, 1) << @truncate(ascii.toLower(c) - 'a'); - } - } - // last we return the comparison if all 26 bits are set, - // and if so, we know the given string is a pangram - // - // but what do we have to compare? - return bits == 0x..???; -} diff --git a/exercises/098_for3.zig b/exercises/098_for3.zig new file mode 100644 index 0000000..77a1b56 --- /dev/null +++ b/exercises/098_for3.zig @@ -0,0 +1,81 @@ +// +// The Zig language is in rapid development and continuously +// improves the language constructs. Ziglings evolves with it. +// +// Until version 0.11, Zig's 'for' loops did not directly +// replicate the functionality of the C-style: "for(a;b;c)" +// which are so well suited for iterating over a numeric +// sequence. +// +// Instead, 'while' loops with counters clumsily stood in their +// place: +// +// var i: usize = 0; +// while (i < 10) : (i += 1) { +// // Here variable 'i' will have each value 0 to 9. +// } +// +// But here we are in the glorious future and Zig's 'for' loops +// can now take this form: +// +// for (0..10) |i| { +// // Here variable 'i' will have each value 0 to 9. +// } +// +// The key to understanding this example is to know that '0..9' +// uses the new range syntax: +// +// 0..10 is a range from 0 to 9 +// 1..4 is a range from 1 to 3 +// +// Crucially, the end value is EXCLUSIVE. +// +// At the moment, ranges in loops are only supported in 'for' loops. +// +// Perhaps you recall Exercise 13? We were printing a numeric +// sequence like so: +// +// var n: u32 = 1; +// +// // I want to print every number between 1 and 20 that is NOT +// // divisible by 3 or 5. +// while (n <= 20) : (n += 1) { +// // The '%' symbol is the "modulo" operator and it +// // returns the remainder after division. +// if (n % 3 == 0) continue; +// if (n % 5 == 0) continue; +// std.debug.print("{} ", .{n}); +// } +// +// Let's try out the new form of 'for' to re-implement that +// exercise: +// +const std = @import("std"); + +pub fn main() void { + + // I want to print every number between 1 and 20 that is NOT + // divisible by 3 or 5. + for (???) |n| { + + // The '%' symbol is the "modulo" operator and it + // returns the remainder after division. + if (n % 3 == 0) continue; + if (n % 5 == 0) continue; + std.debug.print("{} ", .{n}); + } + + std.debug.print("\n", .{}); + + // Let's also print every number from 1 through 15 + for (???) |n| { + std.debug.print("{} ", .{n}); + } + std.debug.print("\n", .{}); +} +// +// That's a bit nicer, right? +// +// Of course, both 'while' and 'for' have different advantages. +// Exercises 11, 12, and 14 would NOT be simplified by switching +// a 'while' for a 'for'. diff --git a/exercises/099_formatting.zig b/exercises/099_formatting.zig deleted file mode 100644 index be16978..0000000 --- a/exercises/099_formatting.zig +++ /dev/null @@ -1,140 +0,0 @@ -// -// Terminals have come a long way over the years. Starting with -// monochrome lines on flickering CRT monitors and continuously -// improving to today's modern terminal emulators with sharp -// images, true color, fonts, ligatures, and characters in every -// known language. -// -// Formatting our results to be appealing and allow quick visual -// comprehension of the information is what users desire. <3 -// -// C set string formatting standards over the years, and Zig is -// following suit and growing daily. Due to this growth, there is -// no official documentation for standard library features such -// as string formatting. -// -// Therefore, the comments for the format() function are the only -// way to definitively learn how to format strings in Zig: -// -// https://codeberg.org/ziglang/zig/src/branch/master/lib/std/Io/Writer.zig#L537 -// -// Zig already has a very nice selection of formatting options. -// These can be used in different ways, but generally to convert -// numerical values into various text representations. The results -// can be used for direct output to a terminal or stored for -// later use or written to a file. The latter is useful when -// large amounts of data are to be processed by other programs. -// -// In Ziglings, we are concerned with the output to the console. -// But since the formatting instructions for files are the same, -// what you learn applies universally. -// -// Since we write to "debug" output in Ziglings, our answers -// usually look something like this: -// -// print("Text {placeholder} another text \n", .{foo}); -// -// In addition to being replaced with foo in this example, the -// {placeholder} in the string can also have formatting applied. -// How does that work? -// -// This actually happens in several stages. In one stage, escape -// sequences are evaluated. The one we've seen the most -// (including the example above) is "\n" which means "line feed". -// Whenever this statement is found, a new line is started in the -// output. Escape sequences can also be written one after the -// other, e.g. "\n\n" will cause two line feeds. -// -// By the way, the result of these escape sequences is passed -// directly to the terminal program. Other than translating them -// into control codes, escape sequences have nothing to do with -// Zig. Zig knows nothing about "line feeds" or "tabs" or -// "bells". -// -// The formatting that Zig *does* perform itself is found in the -// curly brackets: "{placeholder}". Formatting instructions in -// the placeholder will determine how the corresponding value, -// e.g. foo, is displayed. -// -// And this is where it gets exciting, because format() accepts a -// variety of formatting instructions. It's basically a tiny -// language of its own. Here's a numeric example: -// -// print("Catch-0x{x:0>4}.", .{twenty_two}); -// -// This formatting instruction outputs a hexadecimal number with -// leading zeros: -// -// Catch-0x0016. -// -// Or you can center-align a string like so: -// -// print("{s:*^20}\n", .{"Hello!"}); -// -// Output: -// -// *******Hello!******* -// -// Let's try making use of some formatting. We've decided that -// the one thing missing from our lives is a multiplication table -// for all numbers from 1-15. We want the table to be nice and -// neat, with numbers in straight columns like so: -// -// X | 1 2 3 4 5 ... -// ---+---+---+---+---+---+ -// 1 | 1 2 3 4 5 -// -// 2 | 2 4 6 8 10 -// -// 3 | 3 6 9 12 15 -// -// 4 | 4 8 12 16 20 -// -// 5 | 5 10 15 20 25 -// -// ... -// -// Without string formatting, this would be a more challenging -// assignment because the number of digits in the numbers varies -// from 1 to 3. But formatting can help us with that. -// -const std = @import("std"); -const print = std.debug.print; - -pub fn main() !void { - // Max number to multiply - const size = 15; - - // Print the header: - // - // We start with a single 'X' for the diagonal. - print("\n X |", .{}); - - // Header row with all numbers from 1 to size. - for (0..size) |n| { - print("{d:>3} ", .{n + 1}); - } - print("\n", .{}); - - // Header column rule line. - var n: u8 = 0; - while (n <= size) : (n += 1) { - print("---+", .{}); - } - print("\n", .{}); - - // Now the actual table. (Is there anything more beautiful - // than a well-formatted table?) - for (0..size) |a| { - print("{d:>2} |", .{a + 1}); - - for (0..size) |b| { - // What formatting is needed here to make our columns - // nice and straight? - print("{???} ", .{(a + 1) * (b + 1)}); - } - - // After each row we use double line feed: - print("\n\n", .{}); - } -} diff --git a/exercises/099_memory_allocation.zig b/exercises/099_memory_allocation.zig new file mode 100644 index 0000000..58de7b0 --- /dev/null +++ b/exercises/099_memory_allocation.zig @@ -0,0 +1,78 @@ +// +// In most of the examples so far, the inputs are known at compile +// time, thus the amount of memory used by the program is fixed. +// However, if responding to input whose size is not known at compile +// time, such as: +// - user input via command-line arguments +// - inputs from another program +// +// You'll need to request memory for your program to be allocated by +// your operating system at runtime. +// +// Zig provides several different allocators. In the Zig +// documentation, it recommends the Arena allocator for simple +// programs which allocate once and then exit: +// +// const std = @import("std"); +// +// // memory allocation can fail, so the return type is !void +// pub fn main() !void { +// +// var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); +// defer arena.deinit(); +// +// const allocator = arena.allocator(); +// +// const ptr = try allocator.create(i32); +// std.debug.print("ptr={*}\n", .{ptr}); +// +// const slice_ptr = try allocator.alloc(f64, 5); +// std.debug.print("slice_ptr={*}\n", .{slice_ptr}); +// } + +// Instead of a simple integer or a slice with a constant size, +// this program requires allocating a slice that is the same size +// as an input array. + +// Given a series of numbers, take the running average. In other +// words, each item N should contain the average of the last N +// elements. + +const std = @import("std"); + +fn runningAverage(arr: []const f64, avg: []f64) void { + var sum: f64 = 0; + + for (0.., arr) |index, val| { + sum += val; + const f_index: f64 = @floatFromInt(index + 1); + avg[index] = sum / f_index; + } +} + +pub fn main() !void { + // pretend this was defined by reading in user input + const arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 }; + + // initialize the allocator + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + + // free the memory on exit + defer arena.deinit(); + + // initialize the allocator + const allocator = arena.allocator(); + + // allocate memory for this array + const avg: []f64 = ???; + + runningAverage(arr, avg); + std.debug.print("Running Average: ", .{}); + for (avg) |val| { + std.debug.print("{d:.2} ", .{val}); + } + std.debug.print("\n", .{}); +} + +// For more details on memory allocation and the different types of +// memory allocators, see https://www.youtube.com/watch?v=vHWiDx_l4V0 diff --git a/exercises/100_bit_manipulation.zig b/exercises/100_bit_manipulation.zig new file mode 100644 index 0000000..0e64ad7 --- /dev/null +++ b/exercises/100_bit_manipulation.zig @@ -0,0 +1,95 @@ +// +// Bit manipulation is a very powerful tool, also from Zig. +// Since the dawn of the computer age, numerous algorithms have been +// developed that solve tasks solely by moving, setting, or logically +// combining bits. +// +// Zig also uses direct bit manipulation in its standard library for +// functions where possible. And it is often possible with calculations +// based on integers. +// +// At first glance, it is often not easy to understand what exactly these +// algorithms do when only "numbers" in memory areas change outwardly. +// However, it should never be forgotten that the numbers only represent +// the interpretation of the bit sequences. +// +// Quasi the reversed case we have otherwise, namely that we represent +// numbers in bit sequences. +// +// We remember: 1 byte = 8 bits = 11111111 = 255 decimal = FF hex. +// +// Zig provides all the necessary functions to change the bits inside +// a variable. It is distinguished whether the bit change leads to an +// overflow or not. The details are in the Zig documentation in section +// "Table of Operators". +// +// Here are some examples of how the bits of variables can be changed: +// +// const numOne: u8 = 15; // = 0000 1111 +// const numTwo: u8 = 245; // = 1111 0101 +// +// const res1 = numOne >> 4; // = 0000 0000 - shift right +// const res2 = numOne << 4; // = 1111 0000 - shift left +// const res3 = numOne & numTwo; // = 0000 0101 - and +// const res4 = numOne | numTwo; // = 1111 1111 - or +// const res5 = numOne ^ numTwo; // = 1111 1010 - xor +// +// +// To familiarize ourselves with bit manipulation, we start with a simple +// but often underestimated function and then add other exercises in +// loose order. +// +// The following text contains excerpts from Wikipedia. +// +// Swap +// In computer programming, the act of swapping two variables refers to +// mutually exchanging the values of the variables. Usually, this is +// done with the data in memory. For example, in a program, two variables +// may be defined thus (in pseudocode): +// +// data_item x := 1 +// data_item y := 0 +// +// swap (x, y); +// +// After swap() is performed, x will contain the value 0 and y will +// contain 1; their values have been exchanged. The simplest and probably +// most widely used method to swap two variables is to use a third temporary +// variable: +// +// define swap (x, y) +// temp := x +// x := y +// y := temp +// +// However, with integers we can also achieve the swap function simply by +// bit manipulation. To do this, the variables are mutually linked with xor +// and the result is the same. + +const std = @import("std"); +const print = std.debug.print; + +pub fn main() !void { + + // Let us use 1101 and 1011 as values for x and y + var x: u8 = 0b1101; + var y: u8 = 0b1011; + + // Now we swap the values of the two variables by doing xor on them + x ^= y; + y ^= x; + + // What must be written here? + ???; + + print("x = {b}; y = {b}\n", .{ x, y }); +} + +// This variable swap takes advantage of the fact that the value resulting +// from the xor of two values contains both of these values. +// This circumstance was (and still is) sometimes used for encryption. +// Value XOR Key = Crypto. => Crypto XOR Key = Value. +// Since this can be swapped arbitrarily, you can swap two variables in this way. +// +// For Crypto it is better not to use this, but in sorting algorithms like +// Bubble Sort it works very well. diff --git a/exercises/100_for4.zig b/exercises/100_for4.zig deleted file mode 100644 index c8a1161..0000000 --- a/exercises/100_for4.zig +++ /dev/null @@ -1,62 +0,0 @@ -// -// We've seen that the 'for' loop can let us perform some action -// for every item in an array or slice. -// -// More recently, we discovered that it supports ranges to -// iterate over number sequences. -// -// This is part of a more general capability of the `for` loop: -// looping over one or more "objects" where an object is an -// array, slice, or range. -// -// In fact, we *did* use multiple objects way back in Exercise -// 016 where we iterated over an array and also a numeric index. -// It didn't always work exactly this way, so the exercise had to -// be retroactively modified a little bit. -// -// for (bits, 0..) |bit, i| { ... } -// -// The general form of a 'for' loop with two lists is: -// -// for (list_a, list_b) |a, b| { -// // Here we have the first item from list_a and list_b, -// // then the second item from each, then the third and -// // so forth... -// } -// -// What's really beautiful about this is that we don't have to -// keep track of an index or advancing a memory pointer for -// *either* of these lists. That error-prone stuff is all taken -// care of for us by the compiler. -// -// Below, we have a program that is supposed to compare two -// arrays. Please make it work! -// -const std = @import("std"); -const print = std.debug.print; - -pub fn main() void { - const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 }; - const dec_nums = [_]u8{ 11, 42, 119 }; - - for (hex_nums, ???) |hn, ???| { - if (hn != dn) { - print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); - return; - } - } - - print("Arrays match!\n", .{}); -} -// -// You are perhaps wondering what happens if one of the two lists -// is longer than the other? Try it! -// -// By the way, congratulations for making it to Exercise 100! -// -// +-------------+ -// | Celebration | -// | Area * * * | -// +-------------+ -// -// Please keep your celebrating within the area provided. diff --git a/exercises/101_bit_manipulation2.zig b/exercises/101_bit_manipulation2.zig new file mode 100644 index 0000000..8b51265 --- /dev/null +++ b/exercises/101_bit_manipulation2.zig @@ -0,0 +1,64 @@ +// +// Another useful application for bit manipulation is setting bits as flags. +// This is especially useful when processing lists of something and storing +// the states of the entries, e.g. a list of numbers and for each prime +// number a flag is set. +// +// As an example, let's take the Pangram exercise from Exercism: +// https://exercism.org/tracks/zig/exercises/pangram +// +// A pangram is a sentence using every letter of the alphabet at least once. +// It is case insensitive, so it doesn't matter if a letter is lower-case +// or upper-case. The best known English pangram is: +// +// "The quick brown fox jumps over the lazy dog." +// +// There are several ways to select the letters that appear in the pangram +// (and it doesn't matter if they appear once or several times). +// +// For example, you could take an array of bool and set the value to 'true' +// for each letter in the order of the alphabet (a=0; b=1; etc.) found in +// the sentence. However, this is neither memory efficient nor particularly +// fast. Instead we choose a simpler approach that is very similar in principle: +// We define a variable with at least 26 bits (e.g. u32) and set the bit for +// each letter that is found in the corresponding position. +// +// Zig provides functions for this in the standard library, but we prefer to +// solve it without these extras, after all we want to learn something. +// +const std = @import("std"); +const ascii = std.ascii; +const print = std.debug.print; + +pub fn main() !void { + // let's check the pangram + print("Is this a pangram? {}!\n", .{isPangram("The quick brown fox jumps over the lazy dog.")}); +} + +fn isPangram(str: []const u8) bool { + // first we check if the string has at least 26 characters + if (str.len < 26) return false; + + // we use a 32 bit variable of which we need 26 bits + var bits: u32 = 0; + + // loop about all characters in the string + for (str) |c| { + // if the character is an alphabetical character + if (ascii.isAscii(c) and ascii.isAlphabetic(c)) { + // then we set the bit at the position + // + // to do this, we use a little trick: + // since the letters in the ASCII table start at 65 + // and are numbered sequentially, we simply subtract the + // first letter (in this case the 'a') from the character + // found, and thus get the position of the desired bit + bits |= @as(u32, 1) << @truncate(ascii.toLower(c) - 'a'); + } + } + // last we return the comparison if all 26 bits are set, + // and if so, we know the given string is a pangram + // + // but what do we have to compare? + return bits == 0x..???; +} diff --git a/exercises/101_for5.zig b/exercises/101_for5.zig deleted file mode 100644 index 200e71d..0000000 --- a/exercises/101_for5.zig +++ /dev/null @@ -1,126 +0,0 @@ -// -// The 'for' loop is not just limited to looping over one or two -// items. Let's try an example with a whole bunch! -// -// But first, there's one last thing we've avoided mentioning -// until now: The special range that leaves off the last value: -// -// for ( things, 0.. ) |t, i| { ... } -// -// That's how we tell Zig that we want to get a numeric value for -// every item in "things", starting with 0. -// -// A nice feature of these index ranges is that you can have them -// start with any number you choose. The first value of "i" in -// this example will be 500, then 501, 502, etc.: -// -// for ( things, 500.. ) |t, i| { ... } -// -// Remember our RPG characters? They had the following -// properties, which we stored in a struct type: -// -// class -// gold -// experience -// -// What we're going to do now is store the same RPG character -// data, but in a separate array for each property. -// -// It might look a little awkward, but let's bear with it. -// -// We've started writing a program to print a numbered list of -// characters with each of their properties, but it needs a -// little help: -// -const std = @import("std"); -const print = std.debug.print; - -// This is the same character role enum we've seen before. -const Role = enum { - wizard, - thief, - bard, - warrior, -}; - -pub fn main() void { - // Here are the three "property" arrays: - const roles = [4]Role{ .wizard, .bard, .bard, .warrior }; - const gold = [4]u16{ 25, 11, 5, 7392 }; - const experience = [4]u8{ 40, 17, 55, 21 }; - - // We would like to number our list starting with 1, not 0. - // How do we do that? - for (roles, gold, experience, ???) |c, g, e, i| { - const role_name = switch (c) { - .wizard => "Wizard", - .thief => "Thief", - .bard => "Bard", - .warrior => "Warrior", - }; - - std.debug.print("{d}. {s} (Gold: {d}, XP: {d})\n", .{ - i, - role_name, - g, - e, - }); - } -} -// -// By the way, storing our character data in arrays like this -// isn't *just* a silly way to demonstrate multi-object 'for' -// loops. -// -// It's *also* a silly way to introduce a concept called -// "data-oriented design". -// -// Let's use a metaphor to build up an intuition for what this is -// all about: -// -// Let's say you've been tasked with grabbing three glass -// marbles, three spoons, and three feathers from a magic bag. -// But you can't use your hands to grab them. Instead, you must -// use a marble scoop, spoon magnet, and feather tongs to grab -// each type of object. -// -// Now, would you rather use the magic bag: -// -// A. Grouped the items in clusters so you have to pick up one -// marble, then one spoon, then one feather? -// -// OR -// -// B. Grouped the items by type so you can pick up all of the -// marbles at once, then all the spoons, then all of the -// feathers? -// -// If this metaphor is working, hopefully, it's clear that the 'B' -// option would be much more efficient. -// -// Well, it probably comes as little surprise that storing and -// using data in a sequential and uniform fashion is also more -// efficient for modern CPUs. -// -// Decades of OOP practices have steered people towards grouping -// different data types together into mixed-type "objects" with -// the intent that these are easier on the human mind. -// Data-oriented design groups data by type in a way that is -// easier on the computer. -// -// With clever language design, maybe we can have both. -// -// In the Zig community, you may see the difference in groupings -// presented with the terms "Array of Structs" (AoS) versus -// "Struct of Arrays" (SoA). -// -// To envision these two designs in action, imagine an array of -// RPG character structs, each containing three different data -// types (AoS) versus a single RPG character struct containing -// three arrays of one data type each, like those in the exercise -// above (SoA). -// -// For a more practical application of "data-oriented design" -// watch the following talk from Andrew Kelley, the creator of Zig: -// https://vimeo.com/649009599 -// diff --git a/exercises/102_formatting.zig b/exercises/102_formatting.zig new file mode 100644 index 0000000..be16978 --- /dev/null +++ b/exercises/102_formatting.zig @@ -0,0 +1,140 @@ +// +// Terminals have come a long way over the years. Starting with +// monochrome lines on flickering CRT monitors and continuously +// improving to today's modern terminal emulators with sharp +// images, true color, fonts, ligatures, and characters in every +// known language. +// +// Formatting our results to be appealing and allow quick visual +// comprehension of the information is what users desire. <3 +// +// C set string formatting standards over the years, and Zig is +// following suit and growing daily. Due to this growth, there is +// no official documentation for standard library features such +// as string formatting. +// +// Therefore, the comments for the format() function are the only +// way to definitively learn how to format strings in Zig: +// +// https://codeberg.org/ziglang/zig/src/branch/master/lib/std/Io/Writer.zig#L537 +// +// Zig already has a very nice selection of formatting options. +// These can be used in different ways, but generally to convert +// numerical values into various text representations. The results +// can be used for direct output to a terminal or stored for +// later use or written to a file. The latter is useful when +// large amounts of data are to be processed by other programs. +// +// In Ziglings, we are concerned with the output to the console. +// But since the formatting instructions for files are the same, +// what you learn applies universally. +// +// Since we write to "debug" output in Ziglings, our answers +// usually look something like this: +// +// print("Text {placeholder} another text \n", .{foo}); +// +// In addition to being replaced with foo in this example, the +// {placeholder} in the string can also have formatting applied. +// How does that work? +// +// This actually happens in several stages. In one stage, escape +// sequences are evaluated. The one we've seen the most +// (including the example above) is "\n" which means "line feed". +// Whenever this statement is found, a new line is started in the +// output. Escape sequences can also be written one after the +// other, e.g. "\n\n" will cause two line feeds. +// +// By the way, the result of these escape sequences is passed +// directly to the terminal program. Other than translating them +// into control codes, escape sequences have nothing to do with +// Zig. Zig knows nothing about "line feeds" or "tabs" or +// "bells". +// +// The formatting that Zig *does* perform itself is found in the +// curly brackets: "{placeholder}". Formatting instructions in +// the placeholder will determine how the corresponding value, +// e.g. foo, is displayed. +// +// And this is where it gets exciting, because format() accepts a +// variety of formatting instructions. It's basically a tiny +// language of its own. Here's a numeric example: +// +// print("Catch-0x{x:0>4}.", .{twenty_two}); +// +// This formatting instruction outputs a hexadecimal number with +// leading zeros: +// +// Catch-0x0016. +// +// Or you can center-align a string like so: +// +// print("{s:*^20}\n", .{"Hello!"}); +// +// Output: +// +// *******Hello!******* +// +// Let's try making use of some formatting. We've decided that +// the one thing missing from our lives is a multiplication table +// for all numbers from 1-15. We want the table to be nice and +// neat, with numbers in straight columns like so: +// +// X | 1 2 3 4 5 ... +// ---+---+---+---+---+---+ +// 1 | 1 2 3 4 5 +// +// 2 | 2 4 6 8 10 +// +// 3 | 3 6 9 12 15 +// +// 4 | 4 8 12 16 20 +// +// 5 | 5 10 15 20 25 +// +// ... +// +// Without string formatting, this would be a more challenging +// assignment because the number of digits in the numbers varies +// from 1 to 3. But formatting can help us with that. +// +const std = @import("std"); +const print = std.debug.print; + +pub fn main() !void { + // Max number to multiply + const size = 15; + + // Print the header: + // + // We start with a single 'X' for the diagonal. + print("\n X |", .{}); + + // Header row with all numbers from 1 to size. + for (0..size) |n| { + print("{d:>3} ", .{n + 1}); + } + print("\n", .{}); + + // Header column rule line. + var n: u8 = 0; + while (n <= size) : (n += 1) { + print("---+", .{}); + } + print("\n", .{}); + + // Now the actual table. (Is there anything more beautiful + // than a well-formatted table?) + for (0..size) |a| { + print("{d:>2} |", .{a + 1}); + + for (0..size) |b| { + // What formatting is needed here to make our columns + // nice and straight? + print("{???} ", .{(a + 1) * (b + 1)}); + } + + // After each row we use double line feed: + print("\n\n", .{}); + } +} diff --git a/exercises/102_testing.zig b/exercises/102_testing.zig deleted file mode 100644 index 0dec9b9..0000000 --- a/exercises/102_testing.zig +++ /dev/null @@ -1,96 +0,0 @@ -// -// A big advantage of Zig is the integration of its own test system. -// This allows the philosophy of Test Driven Development (TDD) to be -// implemented perfectly. Zig even goes one step further than other -// languages, the tests can be included directly in the source file. -// -// This has several advantages. On the one hand it is much clearer to -// have everything in one file, both the source code and the associated -// test code. On the other hand, it is much easier for third parties -// to understand what exactly a function is supposed to do if they can -// simply look at the test inside the source and compare both. -// -// Especially if you want to understand how e.g. the standard library -// of Zig works, this approach is very helpful. Furthermore it is very -// practical, if you want to report a bug to the Zig community, to -// illustrate it with a small example including a test. -// -// Therefore, in this exercise we will deal with the basics of testing -// in Zig. Basically, tests work as follows: you pass certain parameters -// to a function, for which you get a return - the result. This is then -// compared with the EXPECTED value. If both values match, the test is -// passed, otherwise an error message is displayed. -// -// testing.expect(foo(param1, param2) == expected); -// -// Also other comparisons are possible, deviations or also errors can -// be provoked, which must lead to an appropriate behavior of the -// function, so that the test is passed. -// -// Tests can be run via Zig build system or applied directly to -// individual modules using "zig test xyz.zig". -// -// Both can be used script-driven to execute tests automatically, e.g. -// after checking into a Git repository. Something we also make extensive -// use of here at Ziglings. -// -const std = @import("std"); -const testing = std.testing; - -// This is a simple function that builds a sum from the passed parameters and -// returns. -fn add(a: f16, b: f16) f16 { - return a + b; -} - -// The associated test. It always starts with the keyword "test", followed by a -// description of the tasks of the test. This is followed by the test cases in -// curly brackets. -test "add" { - - // The first test checks if the sum of '41' and '1' gives '42', which is - // correct. - try testing.expect(add(41, 1) == 42); - - // Another way to perform this test is as follows: - try testing.expectEqual(42, add(41, 1)); - - // This time a test with the addition of a negative number: - try testing.expect(add(5, -4) == 1); - - // And a floating point operation: - try testing.expect(add(1.5, 1.5) == 3); -} - -// Another simple function that returns the result of subtracting the two -// parameters. -fn sub(a: f16, b: f16) f16 { - return a - b; -} - -// The corresponding test is not much different from the previous one. Except -// that it contains an error that you need to correct. -test "sub" { - try testing.expect(sub(10, 5) == 6); - - try testing.expect(sub(3, 1.5) == 1.5); -} - -// This function divides the numerator by the denominator. Here it is important -// that the denominator must not be zero. This is checked and if it occurs an -// error is returned. -fn divide(a: f16, b: f16) !f16 { - if (b == 0) return error.DivisionByZero; - return a / b; -} - -test "divide" { - try testing.expect(divide(2, 2) catch unreachable == 1); - try testing.expect(divide(-1, -1) catch unreachable == 1); - try testing.expect(divide(10, 2) catch unreachable == 5); - try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333); - - // Now we test if the function returns an error if we pass a zero as - // denominator. But which error needs to be tested? - try testing.expectError(error.???, divide(15, 0)); -} diff --git a/exercises/103_for4.zig b/exercises/103_for4.zig new file mode 100644 index 0000000..c8a1161 --- /dev/null +++ b/exercises/103_for4.zig @@ -0,0 +1,62 @@ +// +// We've seen that the 'for' loop can let us perform some action +// for every item in an array or slice. +// +// More recently, we discovered that it supports ranges to +// iterate over number sequences. +// +// This is part of a more general capability of the `for` loop: +// looping over one or more "objects" where an object is an +// array, slice, or range. +// +// In fact, we *did* use multiple objects way back in Exercise +// 016 where we iterated over an array and also a numeric index. +// It didn't always work exactly this way, so the exercise had to +// be retroactively modified a little bit. +// +// for (bits, 0..) |bit, i| { ... } +// +// The general form of a 'for' loop with two lists is: +// +// for (list_a, list_b) |a, b| { +// // Here we have the first item from list_a and list_b, +// // then the second item from each, then the third and +// // so forth... +// } +// +// What's really beautiful about this is that we don't have to +// keep track of an index or advancing a memory pointer for +// *either* of these lists. That error-prone stuff is all taken +// care of for us by the compiler. +// +// Below, we have a program that is supposed to compare two +// arrays. Please make it work! +// +const std = @import("std"); +const print = std.debug.print; + +pub fn main() void { + const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 }; + const dec_nums = [_]u8{ 11, 42, 119 }; + + for (hex_nums, ???) |hn, ???| { + if (hn != dn) { + print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); + return; + } + } + + print("Arrays match!\n", .{}); +} +// +// You are perhaps wondering what happens if one of the two lists +// is longer than the other? Try it! +// +// By the way, congratulations for making it to Exercise 100! +// +// +-------------+ +// | Celebration | +// | Area * * * | +// +-------------+ +// +// Please keep your celebrating within the area provided. diff --git a/exercises/103_tokenization.zig b/exercises/103_tokenization.zig deleted file mode 100644 index d0abb5c..0000000 --- a/exercises/103_tokenization.zig +++ /dev/null @@ -1,148 +0,0 @@ -// -// The functionality of the standard library is becoming increasingly -// important in Zig. First of all, it is helpful to take a look at how -// the individual functions are implemented. Because this is wonderfully -// suitable as a template for your own functions. In addition, these -// standard functions are part of the basic configuration of Zig. -// -// This means that they are always available on every system. -// Therefore it is worthwhile to deal with them also in Ziglings. -// It's a great way to learn important skills. For example, it is -// often necessary to process large amounts of data from files. -// And for this sequential reading and processing, Zig provides some -// useful functions, which we will take a closer look at in the coming -// exercises. -// -// A nice example of this has been published on the Zig homepage, -// replacing the somewhat dusty 'Hello world! -// -// Nothing against 'Hello world!', but it just doesn't do justice -// to the elegance of Zig and that's a pity, if someone takes a short, -// first look at the homepage and doesn't get 'enchanted'. And for that -// the present example is simply better suited and we will therefore -// use it as an introduction to tokenizing, because it is wonderfully -// suited to understand the basic principles. -// -// In the following exercises we will also read and process data from -// large files, it will then be clearer to you how useful all this is. -// -// Let's start with the analysis of the example from the Zig homepage -// and explain the most important things. -// -// const std = @import("std"); -// -// // Here a function from the Standard library is defined, -// // which transfers numbers from a string into the respective -// // integer values. -// const parseInt = std.fmt.parseInt; -// -// // Defining a test case -// test "parse integers" { -// -// // Four numbers are passed in a string. -// // Please note that the individual values are separated -// // either by a space or a comma. -// const input = "123 67 89,99"; -// -// // In order to be able to process the input values, -// // memory is required. An allocator is defined here for -// // this purpose. -// const gpa = std.testing.allocator; -// -// // An array into which the numbers are stored is initialized. -// var list: std.ArrayList(u32) = .empty; -// -// // This way you can never forget what is urgently needed -// // and the compiler doesn't grumble either. -// defer list.deinit(gpa); -// -// // Now it gets exciting: -// // A standard tokenizer is called (Zig has several) and -// // used to locate the positions of the respective separators -// // (we remember, space and comma) and pass them to an iterator. -// var it = std.mem.tokenizeAny(u8, input, " ,"); -// -// // The iterator can now be processed in a loop and the -// // individual numbers can be transferred. -// while (it.next()) |num| { -// // But be careful: The numbers are still only available -// // as strings. This is where the integer parser comes -// // into play, converting them into real integer values. -// const n = try parseInt(u32, num, 10); -// -// // Finally the individual values are stored in the array. -// try list.append(gpa, n); -// } -// -// // For the subsequent test, a second static array is created, -// // which is directly filled with the expected values. -// const expected = [_]u32{ 123, 67, 89, 99 }; -// -// // Now the numbers converted from the string can be compared -// // with the expected ones, so that the test is completed -// // successfully. -// for (expected, list.items) |exp, actual| { -// try std.testing.expectEqual(exp, actual); -// } -// } -// -// So much for the example from the homepage. -// Let's summarize the basic steps again: -// -// - We have a set of data in sequential order, separated from each other -// by means of various characters. -// -// - For further processing, for example in an array, this data must be -// read in, separated and, if necessary, converted into the target format. -// -// - We need a buffer that is large enough to hold the data. -// -// - This buffer can be created either statically at compile time, if the -// amount of data is already known, or dynamically at runtime by using -// a memory allocator. -// -// - The data are divided by means of Tokenizer at the respective -// separators and stored in the reserved memory. This usually also -// includes conversion to the target format. -// -// - Now the data can be conveniently processed further in the correct format. -// -// These steps are basically always the same. -// Whether the data is read from a file or entered by the user via the -// keyboard, for example, is irrelevant. Only subtleties are distinguished -// and that's why Zig has different tokenizers. But more about this in -// later exercises. -// -// Now we also want to write a small program to tokenize some data, -// after all we need some practice. Suppose we want to count the words -// of this little poem: -// -// My name is Ozymandias, King of Kings; -// Look on my Works, ye Mighty, and despair! -// by Percy Bysshe Shelley -// -// -const std = @import("std"); -const print = std.debug.print; - -pub fn main() !void { - - // our input - const poem = - \\My name is Ozymandias, King of Kings; - \\Look on my Works, ye Mighty, and despair! - ; - - // now the tokenizer, but what do we need here? - var it = std.mem.tokenizeAny(u8, poem, ???); - - // print all words and count them - var cnt: usize = 0; - while (it.next()) |word| { - cnt += 1; - print("{s}\n", .{word}); - } - - // print the result - print("This little poem has {d} words!\n", .{cnt}); -} diff --git a/exercises/104_for5.zig b/exercises/104_for5.zig new file mode 100644 index 0000000..200e71d --- /dev/null +++ b/exercises/104_for5.zig @@ -0,0 +1,126 @@ +// +// The 'for' loop is not just limited to looping over one or two +// items. Let's try an example with a whole bunch! +// +// But first, there's one last thing we've avoided mentioning +// until now: The special range that leaves off the last value: +// +// for ( things, 0.. ) |t, i| { ... } +// +// That's how we tell Zig that we want to get a numeric value for +// every item in "things", starting with 0. +// +// A nice feature of these index ranges is that you can have them +// start with any number you choose. The first value of "i" in +// this example will be 500, then 501, 502, etc.: +// +// for ( things, 500.. ) |t, i| { ... } +// +// Remember our RPG characters? They had the following +// properties, which we stored in a struct type: +// +// class +// gold +// experience +// +// What we're going to do now is store the same RPG character +// data, but in a separate array for each property. +// +// It might look a little awkward, but let's bear with it. +// +// We've started writing a program to print a numbered list of +// characters with each of their properties, but it needs a +// little help: +// +const std = @import("std"); +const print = std.debug.print; + +// This is the same character role enum we've seen before. +const Role = enum { + wizard, + thief, + bard, + warrior, +}; + +pub fn main() void { + // Here are the three "property" arrays: + const roles = [4]Role{ .wizard, .bard, .bard, .warrior }; + const gold = [4]u16{ 25, 11, 5, 7392 }; + const experience = [4]u8{ 40, 17, 55, 21 }; + + // We would like to number our list starting with 1, not 0. + // How do we do that? + for (roles, gold, experience, ???) |c, g, e, i| { + const role_name = switch (c) { + .wizard => "Wizard", + .thief => "Thief", + .bard => "Bard", + .warrior => "Warrior", + }; + + std.debug.print("{d}. {s} (Gold: {d}, XP: {d})\n", .{ + i, + role_name, + g, + e, + }); + } +} +// +// By the way, storing our character data in arrays like this +// isn't *just* a silly way to demonstrate multi-object 'for' +// loops. +// +// It's *also* a silly way to introduce a concept called +// "data-oriented design". +// +// Let's use a metaphor to build up an intuition for what this is +// all about: +// +// Let's say you've been tasked with grabbing three glass +// marbles, three spoons, and three feathers from a magic bag. +// But you can't use your hands to grab them. Instead, you must +// use a marble scoop, spoon magnet, and feather tongs to grab +// each type of object. +// +// Now, would you rather use the magic bag: +// +// A. Grouped the items in clusters so you have to pick up one +// marble, then one spoon, then one feather? +// +// OR +// +// B. Grouped the items by type so you can pick up all of the +// marbles at once, then all the spoons, then all of the +// feathers? +// +// If this metaphor is working, hopefully, it's clear that the 'B' +// option would be much more efficient. +// +// Well, it probably comes as little surprise that storing and +// using data in a sequential and uniform fashion is also more +// efficient for modern CPUs. +// +// Decades of OOP practices have steered people towards grouping +// different data types together into mixed-type "objects" with +// the intent that these are easier on the human mind. +// Data-oriented design groups data by type in a way that is +// easier on the computer. +// +// With clever language design, maybe we can have both. +// +// In the Zig community, you may see the difference in groupings +// presented with the terms "Array of Structs" (AoS) versus +// "Struct of Arrays" (SoA). +// +// To envision these two designs in action, imagine an array of +// RPG character structs, each containing three different data +// types (AoS) versus a single RPG character struct containing +// three arrays of one data type each, like those in the exercise +// above (SoA). +// +// For a more practical application of "data-oriented design" +// watch the following talk from Andrew Kelley, the creator of Zig: +// https://vimeo.com/649009599 +// diff --git a/exercises/104_threading.zig b/exercises/104_threading.zig deleted file mode 100644 index 3c3fa21..0000000 --- a/exercises/104_threading.zig +++ /dev/null @@ -1,129 +0,0 @@ -// -// In Exercises 84-91, we learned about Zig's Io interface for -// concurrent execution: io.async(), Group, Select, and Futures. -// Under the hood, the Threaded backend manages a pool of real -// OS threads for you - including scheduling, cancellation, and -// resource cleanup. -// -// But sometimes you need direct control over threads: -// * Long-lived dedicated workers -// * Specific stack sizes or thread counts -// * Code that doesn't have an Io interface available -// * Fine-grained synchronization patterns -// -// That's where std.Thread comes in. It gives you a raw OS thread -// that you spawn, manage, and join yourself. No pool, no Futures, -// no automatic cancellation - but full control. -// -// The following diagram roughly illustrates the difference between -// the various types of process execution: -// -// -// Synchronous Asynchronous -// Processing Processing Multithreading -// ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ -// │ Thread 1 │ │ Thread 1 │ │ Thread 1 │ │ Thread 2 │ -// ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ Overall Time -// └──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┴──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┬───────┬───────┬── -// ├───┤ ├───┤ ├───┤ ├───┤ │ │ │ -// │ T │ │ T │ │ T │ │ T │ │ │ │ -// │ a │ │ a │ │ a │ │ a │ │ │ │ -// │ s │ │ s │ │ s │ │ s │ │ │ │ -// │ k │ │ k │ │ k │ │ k │ │ │ │ -// │ │ │ │ │ │ │ │ │ │ │ -// │ 1 │ │ 1 │ │ 1 │ │ 3 │ │ │ │ -// └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ │ │ │ -// │ │ │ │ 5 Sec │ │ -// ┌────┴───┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ │ │ │ -// │Blocking│ │ T │ │ T │ │ T │ │ │ │ -// └────┬───┘ │ a │ │ a │ │ a │ │ │ │ -// │ │ s │ │ s │ │ s │ │ 8 Sec │ -// ┌─┴─┐ │ k │ │ k │ │ k │ │ │ │ -// │ T │ │ │ │ │ │ │ │ │ │ -// │ a │ │ 2 │ │ 2 │ │ 4 │ │ │ │ -// │ s │ └─┬─┘ ├───┤ ├───┤ │ │ │ -// │ k │ │ │┼┼┼│ │┼┼┼│ ▼ │ 10 Sec -// │ │ ┌─┴─┐ └───┴────────┴───┴───────── │ │ -// │ 1 │ │ T │ │ │ -// └─┬─┘ │ a │ │ │ -// │ │ s │ │ │ -// ┌─┴─┐ │ k │ │ │ -// │ T │ │ │ │ │ -// │ a │ │ 1 │ │ │ -// │ s │ ├───┤ │ │ -// │ k │ │┼┼┼│ ▼ │ -// │ │ └───┴──────────────────────────────────────────── │ -// │ 2 │ │ -// ├───┤ │ -// │┼┼┼│ ▼ -// └───┴──────────────────────────────────────────────────────────────── -// -// -// The diagram was modeled on the one in a blog in which the differences -// between asynchronous processing and multithreading are explained in detail: -// https://blog.devgenius.io/multi-threading-vs-asynchronous-programming-what-is-the-difference-3ebfe1179a5 -// -// Our exercise is essentially about clarifying the approach in Zig and -// therefore we try to keep it as simple as possible. -// Multithreading in itself is already difficult enough. ;-) -// -const std = @import("std"); - -pub fn main() !void { - // This is where the preparatory work takes place - // before the parallel processing begins. - std.debug.print("Starting work...\n", .{}); - - // These curly brackets are very important, they are necessary - // to enclose the area where the threads are called. - // Without these brackets, the program would not wait for the - // end of the threads and they would continue to run beyond the - // end of the program. - { - // Now we start the first thread, with the number as parameter - const handle = try std.Thread.spawn(.{}, thread_function, .{1}); - - // Waits for the thread to complete, - // then deallocates any resources created on `spawn()`. - defer handle.join(); - - // Second thread - const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right? - defer handle2.join(); - - // Third thread - const handle3 = try std.Thread.spawn(.{}, thread_function, .{3}); - defer ??? // <-- something is missing - - // After the threads have been started, - // they run in parallel and we can still do some work in between. - var io_instance: std.Io.Threaded = .init_single_threaded; - const io = io_instance.io(); - try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake); - std.debug.print("Some weird stuff, after starting the threads.\n", .{}); - } - // After we have left the closed area, we wait until - // the threads have run through, if this has not yet been the case. - std.debug.print("Zig is cool!\n", .{}); -} - -// This function is started with every thread that we set up. -// In our example, we pass the number of the thread as a parameter. -fn thread_function(id: usize) !void { - var io_instance: std.Io.Threaded = .init_single_threaded; - const io = io_instance.io(); - try io.sleep(std.Io.Duration.fromMilliseconds(100 * @as(isize, @intCast(id))), .awake); - std.debug.print("thread {d}: {s}\n", .{ id, "started." }); - - // This timer simulates the work of the thread. - const work_time = 300 * ((5 - id % 3) - 2); - try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake); - - std.debug.print("thread {d}: {s}\n", .{ id, "finished." }); -} -// This is the easiest way to run threads in parallel. -// In general, however, more management effort is required, -// e.g. by setting up a pool and allowing the threads to communicate -// with each other using semaphores. -// -// But that's a topic for another exercise. diff --git a/exercises/105_testing.zig b/exercises/105_testing.zig new file mode 100644 index 0000000..0dec9b9 --- /dev/null +++ b/exercises/105_testing.zig @@ -0,0 +1,96 @@ +// +// A big advantage of Zig is the integration of its own test system. +// This allows the philosophy of Test Driven Development (TDD) to be +// implemented perfectly. Zig even goes one step further than other +// languages, the tests can be included directly in the source file. +// +// This has several advantages. On the one hand it is much clearer to +// have everything in one file, both the source code and the associated +// test code. On the other hand, it is much easier for third parties +// to understand what exactly a function is supposed to do if they can +// simply look at the test inside the source and compare both. +// +// Especially if you want to understand how e.g. the standard library +// of Zig works, this approach is very helpful. Furthermore it is very +// practical, if you want to report a bug to the Zig community, to +// illustrate it with a small example including a test. +// +// Therefore, in this exercise we will deal with the basics of testing +// in Zig. Basically, tests work as follows: you pass certain parameters +// to a function, for which you get a return - the result. This is then +// compared with the EXPECTED value. If both values match, the test is +// passed, otherwise an error message is displayed. +// +// testing.expect(foo(param1, param2) == expected); +// +// Also other comparisons are possible, deviations or also errors can +// be provoked, which must lead to an appropriate behavior of the +// function, so that the test is passed. +// +// Tests can be run via Zig build system or applied directly to +// individual modules using "zig test xyz.zig". +// +// Both can be used script-driven to execute tests automatically, e.g. +// after checking into a Git repository. Something we also make extensive +// use of here at Ziglings. +// +const std = @import("std"); +const testing = std.testing; + +// This is a simple function that builds a sum from the passed parameters and +// returns. +fn add(a: f16, b: f16) f16 { + return a + b; +} + +// The associated test. It always starts with the keyword "test", followed by a +// description of the tasks of the test. This is followed by the test cases in +// curly brackets. +test "add" { + + // The first test checks if the sum of '41' and '1' gives '42', which is + // correct. + try testing.expect(add(41, 1) == 42); + + // Another way to perform this test is as follows: + try testing.expectEqual(42, add(41, 1)); + + // This time a test with the addition of a negative number: + try testing.expect(add(5, -4) == 1); + + // And a floating point operation: + try testing.expect(add(1.5, 1.5) == 3); +} + +// Another simple function that returns the result of subtracting the two +// parameters. +fn sub(a: f16, b: f16) f16 { + return a - b; +} + +// The corresponding test is not much different from the previous one. Except +// that it contains an error that you need to correct. +test "sub" { + try testing.expect(sub(10, 5) == 6); + + try testing.expect(sub(3, 1.5) == 1.5); +} + +// This function divides the numerator by the denominator. Here it is important +// that the denominator must not be zero. This is checked and if it occurs an +// error is returned. +fn divide(a: f16, b: f16) !f16 { + if (b == 0) return error.DivisionByZero; + return a / b; +} + +test "divide" { + try testing.expect(divide(2, 2) catch unreachable == 1); + try testing.expect(divide(-1, -1) catch unreachable == 1); + try testing.expect(divide(10, 2) catch unreachable == 5); + try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333); + + // Now we test if the function returns an error if we pass a zero as + // denominator. But which error needs to be tested? + try testing.expectError(error.???, divide(15, 0)); +} diff --git a/exercises/105_threading2.zig b/exercises/105_threading2.zig deleted file mode 100644 index 374391a..0000000 --- a/exercises/105_threading2.zig +++ /dev/null @@ -1,107 +0,0 @@ -// -// Now that we are familiar with the principles of multi-threading, -// let's boldly venture into a practical example from mathematics. -// We will determine the circle number PI with sufficient accuracy. -// -// There are different methods for this, and some of them are several -// hundred years old. For us, the dusty procedures are surprisingly well -// suited to our exercise. Because the mathematicians of the time didn't -// have fancy computers with which we can calculate something like this -// in seconds today. -// Whereby, of course, it depends on the accuracy, i.e. how many digits -// after the decimal point we are interested in. -// But these old procedures can still be tackled with paper and pencil, -// which is why they are easier for us to understand. -// At least for me. ;-) -// -// So let's take a mental leap back a few years. -// Around 1672 (if you want to know and read about it in detail, you can -// do so on Wikipedia, for example), various mathematicians once again -// discovered a method of approaching the circle number PI. -// There were the Scottish mathematician Gregory and the German -// mathematician Leibniz, and even a few hundred years earlier the Indian -// mathematician Madhava. All of them independently developed the same -// formula, which was published by Leibniz in 1682 in the journal -// "Acta Eruditorum". -// This is why this method has become known as the "Leibniz series", -// although the other names are also often used today. -// We will not go into the formula and its derivation in detail, but -// will deal with the series straight away: -// -// 4 4 4 4 4 -// PI = --- - --- + --- - --- + --- ... -// 1 3 5 7 9 -// -// As you can clearly see, the series starts with the whole number 4 and -// approaches the circle number by subtracting and adding smaller and -// smaller parts of 4. Pretty much everyone has learned PI = 3.14 at school, -// but very few people remember other digits, and this is rarely necessary -// in practice. Because either you don't need the precision, or you use a -// calculator in which the number is stored as a very precise constant. -// But at some point this constant was calculated and we are doing the same -// now. The question at this point is, how many partial values do we have -// to calculate for which accuracy? -// -// The answer is chewing, to get 8 digits after the decimal point we need -// 1,000,000,000 partial values. And for each additional digit we have to -// add a zero. -// Even fast computers - and I mean really fast computers - get a bit warmer -// on the CPU when it comes to really many digits. But the 8 digits are -// enough for us for now, because we want to understand the principle and -// nothing more, right? -// -// As we have already discovered, the Leibniz series is a series with a -// fixed distance of 2 between the individual partial values. This makes -// it easy to apply a simple loop to it, because if we start with n = 1 -// (which is not necessarily useful now) we always have to add 2 in each -// round. -// But wait! The partial values are alternately added and subtracted. -// This could also be achieved with one loop, but not very elegantly. -// It also makes sense to split this between two CPUs, one calculates -// the positive values and the other the negative values. And so we can -// simply start two threads and add everything up at the end and we're -// done. -// We just have to remember that if only the positive or negative values -// are calculated, the distances are twice as large, i.e. 4. -// -// So that the whole thing has a real learning effect, the first thread -// call is specified and you have to make the second. -// But don't worry, it will work out. :-) -// -const std = @import("std"); - -pub fn main() !void { - const count = 1_000_000_000; - var pi_plus: f64 = 0; - var pi_minus: f64 = 0; - - { - // First thread to calculate the plus numbers. - const handle1 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_plus, 5, count }); - defer handle1.join(); - - // Second thread to calculate the minus numbers. - ??? - - } - // Here we add up the results. - std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus}); -} - -fn thread_pi(pi: *f64, begin: u64, end: u64) !void { - var n: u64 = begin; - while (n < end) : (n += 4) { - pi.* += 4 / @as(f64, @floatFromInt(n)); - } -} -// If you wish, you can increase the number of loop passes, which -// improves the number of digits. -// -// But be careful: -// In order for parallel processing to really show its strengths, -// the compiler must be given the "-O ReleaseFast" flag when it -// is created. Otherwise the debug functions slow down the speed -// to such an extent that seconds become minutes during execution. -// -// And you should remove the formatting restriction in "print", -// otherwise you will not be able to see the additional digits. diff --git a/exercises/106_files.zig b/exercises/106_files.zig deleted file mode 100644 index bf4de9b..0000000 --- a/exercises/106_files.zig +++ /dev/null @@ -1,102 +0,0 @@ -// -// Until now, we've only been printing our output in the console, -// which is good enough for fighting aliens and hermit bookkeeping. -// -// However, many other tasks require some interaction with the file system, -// which is the underlying structure for organizing files on your computer. -// -// The file system provides a hierarchical structure for storing files -// by organizing them into directories, which hold files and other directories, -// thus creating a tree structure that can be navigated. -// -// Fortunately, the Zig Standard Library provides a simple API for interacting -// with the file system, see the detail documentation here: -// -// https://ziglang.org/documentation/master/std/#std.Io -// -// In this exercise, we'll try to: -// - create a new directory, -// - open a file in the directory, -// - write to the file. -// -// Note: For simplicity, we write byte-by-byte without buffering. -// In real applications, you'd typically use a buffer for better -// performance. We'll learn about buffered I/O in a later exercise. -// -const std = @import("std"); - -pub fn main(init: std.process.Init) !void { - // default I/O implementation - const io = init.io; - - // first we get the current working directory - const cwd: std.Io.Dir = std.Io.Dir.cwd(); - - // then we'll try to make a new directory /output/ - // to store our output files. - cwd.createDir(io, "output", .default_dir) catch |e| switch (e) { - // there is a chance you might want to run this - // program more than once and the path might already - // have been created, so we'll have to handle this error - // by doing nothing - // - // we want to catch error.PathAlreadyExists and do nothing - ??? => {}, - // if there's any other unexpected error we just propagate it through - else => return e, - }; - - // then we'll try to open our freshly created directory - // wait a minute... - // opening a directory might fail! - // what should we do here? - var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{}); - defer output_dir.close(io); - - // we try to open the file `zigling.txt`, - // and propagate any error up - const file: std.Io.File = try output_dir.createFile(io, "zigling.txt", .{}); - // it is a good habit to close a file after you are done with it - // so that other programs can read it and prevent data corruption - // but here we are not yet done writing to the file - // if only there were a keyword in Zig that - // allowed you to "defer" code execution to the end of the scope... - file.close(io); - - // you are not allowed to move these lines above the file closing line! - var file_writer = file.writer(io, &.{}); - const writer = &file_writer.interface; - - const byte_written = try writer.write("It's zigling time!"); - std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written}); -} -// to check if you actually write to the file, you can either, -// 1. open the file in your text editor, or -// 2. print the content of the file in the console with one of these commands -// Linux/macOS: >> cat ./output/zigling.txt -// Windows (CMD): >> type .\output\zigling.txt -// -// -// More on Creating files -// -// notice in: -// ... try output_dir.createFile(io, "zigling.txt", .{}); -// ^^^ -// we passed this anonymous struct to the function call -// -// this is the struct `CreateFlag` with default fields -// { -// read: bool = false, -// truncate: bool = true, -// exclusive: bool = false, -// lock: Lock = .none, -// lock_nonblocking: bool = false, -// mode: Mode = default_mode -// } -// -// Question: -// - what should you do if you want to also read the file after opening it? -// - go to the documentation of the struct `std.Io.Dir` here: -// https://ziglang.org/documentation/master/std/#std.Io.Dir -// - can you find a function for opening a file? how about deleting a file? -// - what kind of options can you use with those functions? diff --git a/exercises/106_tokenization.zig b/exercises/106_tokenization.zig new file mode 100644 index 0000000..d0abb5c --- /dev/null +++ b/exercises/106_tokenization.zig @@ -0,0 +1,148 @@ +// +// The functionality of the standard library is becoming increasingly +// important in Zig. First of all, it is helpful to take a look at how +// the individual functions are implemented. Because this is wonderfully +// suitable as a template for your own functions. In addition, these +// standard functions are part of the basic configuration of Zig. +// +// This means that they are always available on every system. +// Therefore it is worthwhile to deal with them also in Ziglings. +// It's a great way to learn important skills. For example, it is +// often necessary to process large amounts of data from files. +// And for this sequential reading and processing, Zig provides some +// useful functions, which we will take a closer look at in the coming +// exercises. +// +// A nice example of this has been published on the Zig homepage, +// replacing the somewhat dusty 'Hello world! +// +// Nothing against 'Hello world!', but it just doesn't do justice +// to the elegance of Zig and that's a pity, if someone takes a short, +// first look at the homepage and doesn't get 'enchanted'. And for that +// the present example is simply better suited and we will therefore +// use it as an introduction to tokenizing, because it is wonderfully +// suited to understand the basic principles. +// +// In the following exercises we will also read and process data from +// large files, it will then be clearer to you how useful all this is. +// +// Let's start with the analysis of the example from the Zig homepage +// and explain the most important things. +// +// const std = @import("std"); +// +// // Here a function from the Standard library is defined, +// // which transfers numbers from a string into the respective +// // integer values. +// const parseInt = std.fmt.parseInt; +// +// // Defining a test case +// test "parse integers" { +// +// // Four numbers are passed in a string. +// // Please note that the individual values are separated +// // either by a space or a comma. +// const input = "123 67 89,99"; +// +// // In order to be able to process the input values, +// // memory is required. An allocator is defined here for +// // this purpose. +// const gpa = std.testing.allocator; +// +// // An array into which the numbers are stored is initialized. +// var list: std.ArrayList(u32) = .empty; +// +// // This way you can never forget what is urgently needed +// // and the compiler doesn't grumble either. +// defer list.deinit(gpa); +// +// // Now it gets exciting: +// // A standard tokenizer is called (Zig has several) and +// // used to locate the positions of the respective separators +// // (we remember, space and comma) and pass them to an iterator. +// var it = std.mem.tokenizeAny(u8, input, " ,"); +// +// // The iterator can now be processed in a loop and the +// // individual numbers can be transferred. +// while (it.next()) |num| { +// // But be careful: The numbers are still only available +// // as strings. This is where the integer parser comes +// // into play, converting them into real integer values. +// const n = try parseInt(u32, num, 10); +// +// // Finally the individual values are stored in the array. +// try list.append(gpa, n); +// } +// +// // For the subsequent test, a second static array is created, +// // which is directly filled with the expected values. +// const expected = [_]u32{ 123, 67, 89, 99 }; +// +// // Now the numbers converted from the string can be compared +// // with the expected ones, so that the test is completed +// // successfully. +// for (expected, list.items) |exp, actual| { +// try std.testing.expectEqual(exp, actual); +// } +// } +// +// So much for the example from the homepage. +// Let's summarize the basic steps again: +// +// - We have a set of data in sequential order, separated from each other +// by means of various characters. +// +// - For further processing, for example in an array, this data must be +// read in, separated and, if necessary, converted into the target format. +// +// - We need a buffer that is large enough to hold the data. +// +// - This buffer can be created either statically at compile time, if the +// amount of data is already known, or dynamically at runtime by using +// a memory allocator. +// +// - The data are divided by means of Tokenizer at the respective +// separators and stored in the reserved memory. This usually also +// includes conversion to the target format. +// +// - Now the data can be conveniently processed further in the correct format. +// +// These steps are basically always the same. +// Whether the data is read from a file or entered by the user via the +// keyboard, for example, is irrelevant. Only subtleties are distinguished +// and that's why Zig has different tokenizers. But more about this in +// later exercises. +// +// Now we also want to write a small program to tokenize some data, +// after all we need some practice. Suppose we want to count the words +// of this little poem: +// +// My name is Ozymandias, King of Kings; +// Look on my Works, ye Mighty, and despair! +// by Percy Bysshe Shelley +// +// +const std = @import("std"); +const print = std.debug.print; + +pub fn main() !void { + + // our input + const poem = + \\My name is Ozymandias, King of Kings; + \\Look on my Works, ye Mighty, and despair! + ; + + // now the tokenizer, but what do we need here? + var it = std.mem.tokenizeAny(u8, poem, ???); + + // print all words and count them + var cnt: usize = 0; + while (it.next()) |word| { + cnt += 1; + print("{s}\n", .{word}); + } + + // print the result + print("This little poem has {d} words!\n", .{cnt}); +} diff --git a/exercises/107_files2.zig b/exercises/107_files2.zig deleted file mode 100644 index c363086..0000000 --- a/exercises/107_files2.zig +++ /dev/null @@ -1,61 +0,0 @@ -// -// Prerequisite : -// - exercise/106_files.zig, or -// - create a file {project_root}/output/zigling.txt -// with content `It's zigling time!`(18 bytes total) -// -// Now there's no point in writing to a file if we don't read from it, am I right? -// Let's write a program to read the content of the file that we just created. -// -// I am assuming that you've created the appropriate files for this to work. -// -// Alright, bud, lean in close. Here's the game plan. -// - First, we open the {project_root}/output/ directory -// - Secondly, we open file `zigling.txt` in that directory -// - Then, we initialize an array of characters with all letter 'A', and print it -// - After that, we read the content of the file into the array -// - Finally, we print out the content we just read -// -// Note: For simplicity, we read byte-by-byte without buffering. -// In real applications, you'd typically use a buffer for better -// performance. We'll learn about buffered I/O in a later exercise. - -const std = @import("std"); - -pub fn main(init: std.process.Init) !void { - const io = init.io; - - // Get the current working directory - const cwd = std.Io.Dir.cwd(); - - // try to open ./output assuming you did your 106_files exercise - var output_dir = try cwd.openDir(io, "output", .{}); - defer output_dir.close(io); - - // try to open the file - const file = try output_dir.openFile(io, "zigling.txt", .{}); - defer file.close(io); - - // initialize an array of u8 with all letter 'A' - // we need to pick the size of the array, 64 seems like a good number - // fix the initialization below - var content = ['A']*64; - // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA` - std.debug.print("{s}\n", .{content}); - - var file_reader = file.reader(io, &.{}); - const reader = &file_reader.interface; - - // okay, seems like a threat of violence is not the answer in this case - // can you go here to find a way to read the content? - // https://ziglang.org/documentation/master/std/#std.Io.Reader - // hint: look for a method that reads into a slice - const bytes_read = zig_read_the_file_or_i_will_fight_you(&content); - - // Woah, too screamy. I know you're excited for zigling time but tone it down a bit. - // Can you print only what we read from the file? - std.debug.print("Successfully Read {d} bytes: {s}\n", .{ - bytes_read, - content, // change this line only - }); -} diff --git a/exercises/107_threading.zig b/exercises/107_threading.zig new file mode 100644 index 0000000..3c3fa21 --- /dev/null +++ b/exercises/107_threading.zig @@ -0,0 +1,129 @@ +// +// In Exercises 84-91, we learned about Zig's Io interface for +// concurrent execution: io.async(), Group, Select, and Futures. +// Under the hood, the Threaded backend manages a pool of real +// OS threads for you - including scheduling, cancellation, and +// resource cleanup. +// +// But sometimes you need direct control over threads: +// * Long-lived dedicated workers +// * Specific stack sizes or thread counts +// * Code that doesn't have an Io interface available +// * Fine-grained synchronization patterns +// +// That's where std.Thread comes in. It gives you a raw OS thread +// that you spawn, manage, and join yourself. No pool, no Futures, +// no automatic cancellation - but full control. +// +// The following diagram roughly illustrates the difference between +// the various types of process execution: +// +// +// Synchronous Asynchronous +// Processing Processing Multithreading +// ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +// │ Thread 1 │ │ Thread 1 │ │ Thread 1 │ │ Thread 2 │ +// ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ Overall Time +// └──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┴──┼┼┼┼┼───┴─┴──┼┼┼┼┼───┴──┬───────┬───────┬── +// ├───┤ ├───┤ ├───┤ ├───┤ │ │ │ +// │ T │ │ T │ │ T │ │ T │ │ │ │ +// │ a │ │ a │ │ a │ │ a │ │ │ │ +// │ s │ │ s │ │ s │ │ s │ │ │ │ +// │ k │ │ k │ │ k │ │ k │ │ │ │ +// │ │ │ │ │ │ │ │ │ │ │ +// │ 1 │ │ 1 │ │ 1 │ │ 3 │ │ │ │ +// └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ │ │ │ +// │ │ │ │ 5 Sec │ │ +// ┌────┴───┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ │ │ │ +// │Blocking│ │ T │ │ T │ │ T │ │ │ │ +// └────┬───┘ │ a │ │ a │ │ a │ │ │ │ +// │ │ s │ │ s │ │ s │ │ 8 Sec │ +// ┌─┴─┐ │ k │ │ k │ │ k │ │ │ │ +// │ T │ │ │ │ │ │ │ │ │ │ +// │ a │ │ 2 │ │ 2 │ │ 4 │ │ │ │ +// │ s │ └─┬─┘ ├───┤ ├───┤ │ │ │ +// │ k │ │ │┼┼┼│ │┼┼┼│ ▼ │ 10 Sec +// │ │ ┌─┴─┐ └───┴────────┴───┴───────── │ │ +// │ 1 │ │ T │ │ │ +// └─┬─┘ │ a │ │ │ +// │ │ s │ │ │ +// ┌─┴─┐ │ k │ │ │ +// │ T │ │ │ │ │ +// │ a │ │ 1 │ │ │ +// │ s │ ├───┤ │ │ +// │ k │ │┼┼┼│ ▼ │ +// │ │ └───┴──────────────────────────────────────────── │ +// │ 2 │ │ +// ├───┤ │ +// │┼┼┼│ ▼ +// └───┴──────────────────────────────────────────────────────────────── +// +// +// The diagram was modeled on the one in a blog in which the differences +// between asynchronous processing and multithreading are explained in detail: +// https://blog.devgenius.io/multi-threading-vs-asynchronous-programming-what-is-the-difference-3ebfe1179a5 +// +// Our exercise is essentially about clarifying the approach in Zig and +// therefore we try to keep it as simple as possible. +// Multithreading in itself is already difficult enough. ;-) +// +const std = @import("std"); + +pub fn main() !void { + // This is where the preparatory work takes place + // before the parallel processing begins. + std.debug.print("Starting work...\n", .{}); + + // These curly brackets are very important, they are necessary + // to enclose the area where the threads are called. + // Without these brackets, the program would not wait for the + // end of the threads and they would continue to run beyond the + // end of the program. + { + // Now we start the first thread, with the number as parameter + const handle = try std.Thread.spawn(.{}, thread_function, .{1}); + + // Waits for the thread to complete, + // then deallocates any resources created on `spawn()`. + defer handle.join(); + + // Second thread + const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right? + defer handle2.join(); + + // Third thread + const handle3 = try std.Thread.spawn(.{}, thread_function, .{3}); + defer ??? // <-- something is missing + + // After the threads have been started, + // they run in parallel and we can still do some work in between. + var io_instance: std.Io.Threaded = .init_single_threaded; + const io = io_instance.io(); + try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake); + std.debug.print("Some weird stuff, after starting the threads.\n", .{}); + } + // After we have left the closed area, we wait until + // the threads have run through, if this has not yet been the case. + std.debug.print("Zig is cool!\n", .{}); +} + +// This function is started with every thread that we set up. +// In our example, we pass the number of the thread as a parameter. +fn thread_function(id: usize) !void { + var io_instance: std.Io.Threaded = .init_single_threaded; + const io = io_instance.io(); + try io.sleep(std.Io.Duration.fromMilliseconds(100 * @as(isize, @intCast(id))), .awake); + std.debug.print("thread {d}: {s}\n", .{ id, "started." }); + + // This timer simulates the work of the thread. + const work_time = 300 * ((5 - id % 3) - 2); + try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake); + + std.debug.print("thread {d}: {s}\n", .{ id, "finished." }); +} +// This is the easiest way to run threads in parallel. +// In general, however, more management effort is required, +// e.g. by setting up a pool and allowing the threads to communicate +// with each other using semaphores. +// +// But that's a topic for another exercise. diff --git a/exercises/108_labeled_switch.zig b/exercises/108_labeled_switch.zig deleted file mode 100644 index 897fcf5..0000000 --- a/exercises/108_labeled_switch.zig +++ /dev/null @@ -1,79 +0,0 @@ -// -// You've heard of while loops in exercises 011,012,013 and 014 -// You've also heard of switch expressions in exercises 030 and 31. -// You've also seen how labels can be used in exercise 063. -// -// By combining while loops and switch statements with continue and break statements -// one can create very concise State Machines. -// -// One such example would be: -// -// pub fn main() void { -// var op: u8 = 1; -// while (true) { -// switch (op) { -// 1 => { op = 2; continue; }, -// 2 => { op = 3; continue; }, -// 3 => return, -// else => {}, -// } -// break; -// } -// std.debug.print("This statement cannot be reached\n", .{}); -// } -// -// By combining all we've learned so far, we can now proceed with a labeled switch. -// -// A labeled switch is some extra syntactic sugar, which comes with all sorts of -// candy (performance benefits). Don't believe me? Directly to source https://github.com/ziglang/zig/pull/21367 -// -// Here is the previous excerpt implemented as a labeled switch instead: -// -// pub fn main() void { -// foo: switch (@as(u8, 1)) { -// 1 => continue :foo 2, -// 2 => continue :foo 3, -// 3 => return, -// else => {}, -// } -// std.debug.print("This statement cannot be reached\n", .{}); -// } -// -// The flow of execution on this second case is: -// 1. The switch starts with value '1'; -// 2. The switch evaluates to case '1' which in turn uses the continue statement -// to re-evaluate the labeled switch again, now providing the value '2'; -// 3. In the case '2' we repeat the same pattern as case '1' -// but instead the value to be evaluated is now '3'; -// 4. Finally we get to case '3', where we return from the function as a whole, -// so the debug statement is never executed. -// 5. In this example, since the input does not have clear, exhaustive patterns and -// can essentially be any 'u8' integer, we need to handle all cases not explicitly -// covered by using the 'else => {}' branch as the default case. -// -// -const std = @import("std"); - -const PullRequestState = enum(u8) { - Draft, - InReview, - Approved, - Rejected, - Merged, -}; - -pub fn main() void { - // Oh no, your pull request keeps being rejected, - // how would you fix it? - pr: switch (PullRequestState.Draft) { - PullRequestState.Draft => continue :pr PullRequestState.InReview, - PullRequestState.InReview => continue :pr PullRequestState.Rejected, - PullRequestState.Approved => continue :pr PullRequestState.Merged, - PullRequestState.Rejected => { - std.debug.print("The pull request has been rejected.\n", .{}); - return; - }, - PullRequestState.Merged => break, // Would you know where to break to? - } - std.debug.print("The pull request has been merged.\n", .{}); -} diff --git a/exercises/108_threading2.zig b/exercises/108_threading2.zig new file mode 100644 index 0000000..374391a --- /dev/null +++ b/exercises/108_threading2.zig @@ -0,0 +1,107 @@ +// +// Now that we are familiar with the principles of multi-threading, +// let's boldly venture into a practical example from mathematics. +// We will determine the circle number PI with sufficient accuracy. +// +// There are different methods for this, and some of them are several +// hundred years old. For us, the dusty procedures are surprisingly well +// suited to our exercise. Because the mathematicians of the time didn't +// have fancy computers with which we can calculate something like this +// in seconds today. +// Whereby, of course, it depends on the accuracy, i.e. how many digits +// after the decimal point we are interested in. +// But these old procedures can still be tackled with paper and pencil, +// which is why they are easier for us to understand. +// At least for me. ;-) +// +// So let's take a mental leap back a few years. +// Around 1672 (if you want to know and read about it in detail, you can +// do so on Wikipedia, for example), various mathematicians once again +// discovered a method of approaching the circle number PI. +// There were the Scottish mathematician Gregory and the German +// mathematician Leibniz, and even a few hundred years earlier the Indian +// mathematician Madhava. All of them independently developed the same +// formula, which was published by Leibniz in 1682 in the journal +// "Acta Eruditorum". +// This is why this method has become known as the "Leibniz series", +// although the other names are also often used today. +// We will not go into the formula and its derivation in detail, but +// will deal with the series straight away: +// +// 4 4 4 4 4 +// PI = --- - --- + --- - --- + --- ... +// 1 3 5 7 9 +// +// As you can clearly see, the series starts with the whole number 4 and +// approaches the circle number by subtracting and adding smaller and +// smaller parts of 4. Pretty much everyone has learned PI = 3.14 at school, +// but very few people remember other digits, and this is rarely necessary +// in practice. Because either you don't need the precision, or you use a +// calculator in which the number is stored as a very precise constant. +// But at some point this constant was calculated and we are doing the same +// now. The question at this point is, how many partial values do we have +// to calculate for which accuracy? +// +// The answer is chewing, to get 8 digits after the decimal point we need +// 1,000,000,000 partial values. And for each additional digit we have to +// add a zero. +// Even fast computers - and I mean really fast computers - get a bit warmer +// on the CPU when it comes to really many digits. But the 8 digits are +// enough for us for now, because we want to understand the principle and +// nothing more, right? +// +// As we have already discovered, the Leibniz series is a series with a +// fixed distance of 2 between the individual partial values. This makes +// it easy to apply a simple loop to it, because if we start with n = 1 +// (which is not necessarily useful now) we always have to add 2 in each +// round. +// But wait! The partial values are alternately added and subtracted. +// This could also be achieved with one loop, but not very elegantly. +// It also makes sense to split this between two CPUs, one calculates +// the positive values and the other the negative values. And so we can +// simply start two threads and add everything up at the end and we're +// done. +// We just have to remember that if only the positive or negative values +// are calculated, the distances are twice as large, i.e. 4. +// +// So that the whole thing has a real learning effect, the first thread +// call is specified and you have to make the second. +// But don't worry, it will work out. :-) +// +const std = @import("std"); + +pub fn main() !void { + const count = 1_000_000_000; + var pi_plus: f64 = 0; + var pi_minus: f64 = 0; + + { + // First thread to calculate the plus numbers. + const handle1 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_plus, 5, count }); + defer handle1.join(); + + // Second thread to calculate the minus numbers. + ??? + + } + // Here we add up the results. + std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus}); +} + +fn thread_pi(pi: *f64, begin: u64, end: u64) !void { + var n: u64 = begin; + while (n < end) : (n += 4) { + pi.* += 4 / @as(f64, @floatFromInt(n)); + } +} +// If you wish, you can increase the number of loop passes, which +// improves the number of digits. +// +// But be careful: +// In order for parallel processing to really show its strengths, +// the compiler must be given the "-O ReleaseFast" flag when it +// is created. Otherwise the debug functions slow down the speed +// to such an extent that seconds become minutes during execution. +// +// And you should remove the formatting restriction in "print", +// otherwise you will not be able to see the additional digits. diff --git a/exercises/109_files.zig b/exercises/109_files.zig new file mode 100644 index 0000000..bf4de9b --- /dev/null +++ b/exercises/109_files.zig @@ -0,0 +1,102 @@ +// +// Until now, we've only been printing our output in the console, +// which is good enough for fighting aliens and hermit bookkeeping. +// +// However, many other tasks require some interaction with the file system, +// which is the underlying structure for organizing files on your computer. +// +// The file system provides a hierarchical structure for storing files +// by organizing them into directories, which hold files and other directories, +// thus creating a tree structure that can be navigated. +// +// Fortunately, the Zig Standard Library provides a simple API for interacting +// with the file system, see the detail documentation here: +// +// https://ziglang.org/documentation/master/std/#std.Io +// +// In this exercise, we'll try to: +// - create a new directory, +// - open a file in the directory, +// - write to the file. +// +// Note: For simplicity, we write byte-by-byte without buffering. +// In real applications, you'd typically use a buffer for better +// performance. We'll learn about buffered I/O in a later exercise. +// +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + // default I/O implementation + const io = init.io; + + // first we get the current working directory + const cwd: std.Io.Dir = std.Io.Dir.cwd(); + + // then we'll try to make a new directory /output/ + // to store our output files. + cwd.createDir(io, "output", .default_dir) catch |e| switch (e) { + // there is a chance you might want to run this + // program more than once and the path might already + // have been created, so we'll have to handle this error + // by doing nothing + // + // we want to catch error.PathAlreadyExists and do nothing + ??? => {}, + // if there's any other unexpected error we just propagate it through + else => return e, + }; + + // then we'll try to open our freshly created directory + // wait a minute... + // opening a directory might fail! + // what should we do here? + var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{}); + defer output_dir.close(io); + + // we try to open the file `zigling.txt`, + // and propagate any error up + const file: std.Io.File = try output_dir.createFile(io, "zigling.txt", .{}); + // it is a good habit to close a file after you are done with it + // so that other programs can read it and prevent data corruption + // but here we are not yet done writing to the file + // if only there were a keyword in Zig that + // allowed you to "defer" code execution to the end of the scope... + file.close(io); + + // you are not allowed to move these lines above the file closing line! + var file_writer = file.writer(io, &.{}); + const writer = &file_writer.interface; + + const byte_written = try writer.write("It's zigling time!"); + std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written}); +} +// to check if you actually write to the file, you can either, +// 1. open the file in your text editor, or +// 2. print the content of the file in the console with one of these commands +// Linux/macOS: >> cat ./output/zigling.txt +// Windows (CMD): >> type .\output\zigling.txt +// +// +// More on Creating files +// +// notice in: +// ... try output_dir.createFile(io, "zigling.txt", .{}); +// ^^^ +// we passed this anonymous struct to the function call +// +// this is the struct `CreateFlag` with default fields +// { +// read: bool = false, +// truncate: bool = true, +// exclusive: bool = false, +// lock: Lock = .none, +// lock_nonblocking: bool = false, +// mode: Mode = default_mode +// } +// +// Question: +// - what should you do if you want to also read the file after opening it? +// - go to the documentation of the struct `std.Io.Dir` here: +// https://ziglang.org/documentation/master/std/#std.Io.Dir +// - can you find a function for opening a file? how about deleting a file? +// - what kind of options can you use with those functions? diff --git a/exercises/109_vectors.zig b/exercises/109_vectors.zig deleted file mode 100644 index 96892ca..0000000 --- a/exercises/109_vectors.zig +++ /dev/null @@ -1,147 +0,0 @@ -// So far in Ziglings, we've seen how for loops can be used to -// repeat calculations across an array in several ways. -// -// For loops are generally great for this kind of task, but -// sometimes they don't fully utilize the capabilities of the -// CPU. -// -// Most modern CPUs can execute instructions in which SEVERAL -// calculations are performed WITHIN registers at the SAME TIME. -// These are known as "single instruction, multiple data" (SIMD) -// instructions. SIMD instructions can make code significantly -// more performant. -// -// To see why, imagine we have a program in which we take the -// square root of four (changing) f32 floats. -// -// A simple compiler would take the program and produce machine code -// which calculates each square root sequentially. Most registers on -// modern CPUs have 64 bits, so we could imagine that each float moves -// into a 64-bit register, and the following happens four times: -// -// 32 bits 32 bits -// +-------------------+ -// register | 0 | x | -// +-------------------+ -// -// | -// [SQRT instruction] -// V -// -// +-------------------+ -// | 0 | sqrt(x) | -// +-------------------+ -// -// Notice that half of the register contains blank data to which -// nothing happened. What a waste! What if we were able to use -// that space instead? This is the idea at the core of SIMD. -// -// Most modern CPUs contain specialized registers with at least 128 bits -// for performing SIMD instructions. On a machine with 128-bit SIMD -// registers, a smart compiler would probably NOT issue four sqrt -// instructions as above, but instead pack the floats into a single -// 128-bit register, then execute a single "packed" sqrt -// instruction to do ALL the square root calculations at once. -// -// For example: -// -// -// 32 bits 32 bits 32 bits 32 bits -// +---------------------------------------+ -// register | 4.0 | 9.0 | 25.0 | 49.0 | -// +---------------------------------------+ -// -// | -// [SIMD SQRT instruction] -// V -// -// +---------------------------------------+ -// register | 2.0 | 3.0 | 5.0 | 7.0 | -// +---------------------------------------+ -// -// Pretty cool, right? -// -// Code with SIMD instructions is usually more performant than code -// without SIMD instructions. Zig cares a lot about performance, -// so it has built-in support for SIMD! It has a data structure that -// directly supports SIMD instructions: -// -// +-----------+ -// | Vectors | -// +-----------+ -// -// Operations performed on vectors in Zig will be done in parallel using -// SIMD instructions, whenever possible. -// -// Defining vectors in Zig is straightforwards. No library import is needed. -const v1 = @Vector(3, i32){ 1, 10, 100 }; -const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 }; - -// Vectors support the same builtin operators as their underlying base types. -const v3 = v1 + v1; // { 2, 20, 200}; -const v4 = v2 * v2; // { 4.0, 9.0, 25.0}; - -// Intrinsics that apply to base types usually extend to vectors. -const v5: @Vector(3, f32) = @floatFromInt(v3); // { 2.0, 20.0, 200.0} -const v6 = v4 - v5; // { 2.0, -11.0, -175.0} -const v7 = @abs(v6); // { 2.0, 11.0, 175.0} - -// We can make constant vectors, and reduce vectors. -const v8: @Vector(4, u8) = @splat(2); // { 2, 2, 2, 2} -const v8_sum = @reduce(.Add, v8); // 8 -const v8_min = @reduce(.Min, v8); // 2 - -// Fixed-length arrays can be automatically assigned to vectors (and vice-versa). -const single_digit_primes = [4]i8{ 2, 3, 5, 7 }; -const prime_vector: @Vector(4, i8) = single_digit_primes; - -// Now let's use vectors to simplify and optimize some code! -// -// Ewa is writing a program in which they frequently want to compare -// two lists of four f32s. Ewa expects the lists to be similar, and -// wants to determine the largest pairwise difference between the lists. -// -// Ewa wrote the following function to figure this out. - -fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 { - var max_diff: f32 = 0; - for (list1, list2) |n1, n2| { - const abs_diff = @abs(n1 - n2); - if (abs_diff > max_diff) { - max_diff = abs_diff; - } - } - return max_diff; -} - -// Ewa heard about vectors in Zig, and started writing a new vector -// version of the function, but has got stuck! -// -// Help Ewa finish the vector version! The examples above should help. - -const Vec4 = @Vector(4, f32); -fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 { - const abs_diff_vec = ???; - const max_diff = @reduce(???, abs_diff_vec); - return max_diff; -} - -// Quite the simplification! We could even write the function in one line -// and it would still be readable. -// -// Since the entire function is now expressed in terms of vector operations, -// the Zig compiler will easily be able to compile it down to machine code -// which utilizes the all-powerful SIMD instructions and does a lot of the -// computation in parallel. - -const std = @import("std"); -const print = std.debug.print; - -pub fn main() void { - const l1 = [4]f32{ 3.141, 2.718, 0.577, 1.000 }; - const l2 = [4]f32{ 3.154, 2.707, 0.591, 0.993 }; - const mpd_old = calcMaxPairwiseDiffOld(l1, l2); - const mpd_new = calcMaxPairwiseDiffNew(l1, l2); - print("Max difference (old fn): {d: >5.3}\n", .{mpd_old}); - print("Max difference (new fn): {d: >5.3}\n", .{mpd_new}); -} diff --git a/exercises/110_files2.zig b/exercises/110_files2.zig new file mode 100644 index 0000000..c363086 --- /dev/null +++ b/exercises/110_files2.zig @@ -0,0 +1,61 @@ +// +// Prerequisite : +// - exercise/106_files.zig, or +// - create a file {project_root}/output/zigling.txt +// with content `It's zigling time!`(18 bytes total) +// +// Now there's no point in writing to a file if we don't read from it, am I right? +// Let's write a program to read the content of the file that we just created. +// +// I am assuming that you've created the appropriate files for this to work. +// +// Alright, bud, lean in close. Here's the game plan. +// - First, we open the {project_root}/output/ directory +// - Secondly, we open file `zigling.txt` in that directory +// - Then, we initialize an array of characters with all letter 'A', and print it +// - After that, we read the content of the file into the array +// - Finally, we print out the content we just read +// +// Note: For simplicity, we read byte-by-byte without buffering. +// In real applications, you'd typically use a buffer for better +// performance. We'll learn about buffered I/O in a later exercise. + +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + + // Get the current working directory + const cwd = std.Io.Dir.cwd(); + + // try to open ./output assuming you did your 106_files exercise + var output_dir = try cwd.openDir(io, "output", .{}); + defer output_dir.close(io); + + // try to open the file + const file = try output_dir.openFile(io, "zigling.txt", .{}); + defer file.close(io); + + // initialize an array of u8 with all letter 'A' + // we need to pick the size of the array, 64 seems like a good number + // fix the initialization below + var content = ['A']*64; + // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA` + std.debug.print("{s}\n", .{content}); + + var file_reader = file.reader(io, &.{}); + const reader = &file_reader.interface; + + // okay, seems like a threat of violence is not the answer in this case + // can you go here to find a way to read the content? + // https://ziglang.org/documentation/master/std/#std.Io.Reader + // hint: look for a method that reads into a slice + const bytes_read = zig_read_the_file_or_i_will_fight_you(&content); + + // Woah, too screamy. I know you're excited for zigling time but tone it down a bit. + // Can you print only what we read from the file? + std.debug.print("Successfully Read {d} bytes: {s}\n", .{ + bytes_read, + content, // change this line only + }); +} diff --git a/exercises/110_quiz9.zig b/exercises/110_quiz9.zig deleted file mode 100644 index 8f5cb61..0000000 --- a/exercises/110_quiz9.zig +++ /dev/null @@ -1,484 +0,0 @@ -// ---------------------------------------------------------------------------- -// Quiz Time: Toggling, Setting, and Clearing Bits -// ---------------------------------------------------------------------------- -// -// Another exciting thing about Zig is its suitability for embedded -// programming. Your Zig code doesn't have to remain on your laptop; you can -// also deploy your code to microcontrollers! This means you can write Zig to -// drive your next robot or greenhouse climate control system! Ready to enter -// the exciting world of embedded programming? Let's get started! -// -// ---------------------------------------------------------------------------- -// Some Background -// ---------------------------------------------------------------------------- -// -// A common activity in microcontroller programming is setting and clearing -// bits on input and output pins. This lets you control LEDs, sensors, motors -// and more! In a previous exercise (097_bit_manipulation.zig) you learned how -// to swap two bytes using the ^ (XOR - exclusive or) operator. This quiz will -// test your knowledge of bit manipulation in Zig while giving you a taste of -// what it's like to control registers in a real microcontroller. Included at -// the end are some helper functions that demonstrate how we might make our -// code a little more readable. -// -// Below is a pinout diagram for the famous ATmega328 AVR microcontroller used -// as the primary microchip on popular microcontroller platforms like the -// Arduino UNO. -// -// ============ PINOUT DIAGRAM FOR ATMEGA328 MICROCONTROLLER ============ -// _____ _____ -// | U | -// (RESET) PC6 --| 1 28 |-- PC5 -// PD0 --| 2 27 |-- PC4 -// PD1 --| 3 26 |-- PC3 -// PD2 --| 4 25 |-- PC2 -// PD3 --| 5 24 |-- PC1 -// PD4 --| 6 23 |-- PC0 -// VCC --| 7 22 |-- GND -// GND --| 8 21 |-- AREF -// |-- PB6 --| 9 20 |-- AVCC -// |-- PB7 --| 10 19 |-- PB5 --| -// | PD5 --| 11 18 |-- PB4 --| -// | PD6 --| 12 17 |-- PB3 --| -// | PD7 --| 13 16 |-- PB2 --| -// |-- PB0 --| 14 15 |-- PB1 --| -// | |___________| | -// \_______________________________/ -// | -// PORTB -// -// Drawing inspiration from this diagram, we'll use the pins for PORTB as our -// mental model for this quiz on bit manipulation. It should be noted that -// in the following problems we are using ordinary variables, one of which we -// have named PORTB, to simulate modifying the bits of real hardware registers. -// But in actual microcontroller code, PORTB would be defined something like -// this: -// pub const PORTB = @as(*volatile u8, @ptrFromInt(0x25)); -// -// This lets the compiler know not to make any optimizations to PORTB so that -// the IO pins are properly mapped to our code. -// -// NOTE : To keep things simple, the following problems are given using type -// u4, so applying the output to PORTB would only affect the lower four pins -// PB0..PB3. Of course, there is nothing to prevent you from swapping the u4 -// with a u8 so you can control all 8 of PORTB's IO pins. - -const std = @import("std"); -const print = std.debug.print; -const testing = std.testing; - -pub fn main() !void { - var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity - - // ------------------------------------------------------------------------ - // Quiz - // ------------------------------------------------------------------------ - - // See if you can solve the following problems. The last two problems throw - // you a bit of a curve ball. Try solving them on your own. If you need - // help, scroll to the bottom of main to see some in depth explanations on - // toggling, setting, and clearing bits in Zig. - - print("Toggle pins with XOR on PORTB\n", .{}); - print("-----------------------------\n", .{}); - PORTB = 0b1100; - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("^ {b:0>4} // (bitmask)\n", .{0b0101}); - PORTB ^= (1 << 1) | (1 << 0); // What's wrong here? - checkAnswer(0b1001, PORTB); - - newline(); - - PORTB = 0b1100; - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("^ {b:0>4} // (bitmask)\n", .{0b0011}); - PORTB ^= (1 << 1) & (1 << 0); // What's wrong here? - checkAnswer(0b1111, PORTB); - - newline(); - - print("Set pins with OR on PORTB\n", .{}); - print("-------------------------\n", .{}); - - PORTB = 0b1001; // reset PORTB - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("| {b:0>4} // (bitmask)\n", .{0b0100}); - PORTB = PORTB ??? (1 << 2); // What's missing here? - checkAnswer(0b1101, PORTB); - - newline(); - - PORTB = 0b1001; // reset PORTB - print(" {b:0>4} // (reset state)\n", .{PORTB}); - print("| {b:0>4} // (bitmask)\n", .{0b0100}); - PORTB ??? (1 << 2); // What's missing here? - checkAnswer(0b1101, PORTB); - - newline(); - - print("Clear pins with AND and NOT on PORTB\n", .{}); - print("------------------------------------\n", .{}); - - PORTB = 0b1110; // reset PORTB - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("& {b:0>4} // (bitmask)\n", .{0b1011}); - PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here? - checkAnswer(0b1010, PORTB); - - newline(); - - PORTB = 0b0111; // reset PORTB - print(" {b:0>4} // (reset state)\n", .{PORTB}); - print("& {b:0>4} // (bitmask)\n", .{0b1110}); - PORTB &= ~(1 << 0); // What's missing here? - checkAnswer(0b0110, PORTB); - - newline(); - newline(); -} - -// ************************************************************************ -// IN-DEPTH EXPLANATIONS BELOW -// ************************************************************************ -// -// -// -// -// -// -// -// -// -// -// -// ------------------------------------------------------------------------ -// Toggling bits with XOR: -// ------------------------------------------------------------------------ -// XOR stands for "exclusive or". We can toggle bits with the ^ (XOR) -// bitwise operator, like so: -// -// -// In order to output a 1, the logic of an XOR operation requires that the -// two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will -// both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior -// of outputting a 0 when both inputs are 1s is what makes it different from -// the OR operator; it also gives us the ability to toggle bits by putting -// 1s into our bitmask. -// -// - 1s in our bitmask operand, can be thought of as causing the -// corresponding bits in the other operand to flip to the opposite value. -// - 0s cause no change. -// -// The 0s in our bitmask preserve these values -// -XOR op- ---expanded--- in the output. -// _______________/ -// / / -// 1100 1 1 0 0 -// ^ 0101 0 1 0 1 (bitmask) -// ------ - - - - -// = 1001 1 0 0 1 <- This bit was already cleared. -// \_______\ -// \ -// We can think of these bits having flipped -// because of the presence of 1s in those columns -// of our bitmask. -// -// Now let's take a look at setting bits with the | operator. -// -// -// -// -// -// ------------------------------------------------------------------------ -// Setting bits with OR: -// ------------------------------------------------------------------------ -// We can set bits on PORTB with the | (OR) operator, like so: -// -// var PORTB: u4 = 0b1001; -// PORTB = PORTB | 0b0010; -// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 -// -// -OR op- ---expanded--- -// _ Set only this bit. -// / -// 1001 1 0 0 1 -// | 0010 0 0 1 0 (bitmask) -// ------ - - - - -// = 1011 1 0 1 1 -// \___\_______\ -// \ -// These bits remain untouched because OR-ing with -// a 0 effects no change. -// -// ------------------------------------------------------------------------ -// To create a bitmask like 0b0010 used above: -// -// 1. First, shift the value 1 over one place with the bitwise << (shift -// left) operator as indicated below: -// 1 << 0 -> 0001 -// 1 << 1 -> 0010 <-- Shift 1 one place to the left -// 1 << 2 -> 0100 -// 1 << 3 -> 1000 -// -// This allows us to rewrite the above code like this: -// -// var PORTB: u4 = 0b1001; -// PORTB = PORTB | (1 << 1); -// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 -// -// Finally, as in the C language, Zig allows us to use the |= operator, so -// we can rewrite our code again in an even more compact and idiomatic -// form: PORTB |= (1 << 1) - -// So now we've covered how to toggle and set bits. What about clearing -// them? Well, this is where Zig throws us a curve ball. Don't worry we'll -// go through it step by step. -// -// -// -// -// -// ------------------------------------------------------------------------ -// Clearing bits with AND and NOT: -// ------------------------------------------------------------------------ -// We can clear bits with the & (AND) bitwise operator, like so: - -// PORTB = 0b1110; // reset PORTB -// PORTB = PORTB & 0b1011; -// print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010 -// -// - 0s clear bits when used in conjunction with a bitwise AND. -// - 1s do nothing, thus preserving the original bits. -// -// -AND op- ---expanded--- -// __________ Clear only this bit. -// / -// 1110 1 1 1 0 -// & 1011 1 0 1 1 (bitmask) -// ------ - - - - -// = 1010 1 0 1 0 <- This bit was already cleared. -// \_______\ -// \ -// These bits remain untouched because AND-ing with a -// 1 preserves the original bit value whether 0 or 1. -// -// ------------------------------------------------------------------------ -// We can use the ~ (NOT) operator to easily create a bitmask like 1011: -// -// 1. First, shift the value 1 over two places with the bit-wise << (shift -// left) operator as indicated below: -// 1 << 0 -> 0001 -// 1 << 1 -> 0010 -// 1 << 2 -> 0100 <- The 1 has been shifted two places to the left -// 1 << 3 -> 1000 -// -// 2. The second step in creating our bitmask is to invert the bits -// ~0100 -> 1011 -// in C we would write this as: -// ~(1 << 2) -> 1011 -// -// But if we try to compile ~(1 << 2) in Zig, we'll get an error: -// unable to perform binary not operation on type 'comptime_int' -// -// Before Zig can invert our bits, it needs to know the number of -// bits it's being asked to invert. -// -// We do this with the @as (cast as) built-in like this: -// @as(u4, 1 << 2) -> 0100 -// -// Finally, we can invert our new mask by placing the NOT ~ operator -// before our expression, like this: -// ~@as(u4, 1 << 2) -> 1011 -// -// If you are offput by the fact that you can't simply invert bits like -// you can in languages such as C without casting to a particular size -// of integer, you're not alone. However, this is actually another -// instance where Zig is really helpful because it protects you from -// difficult to debug integer overflow bugs that can have you tearing -// your hair out. In the interest of keeping things sane, Zig requires -// you simply to tell it the size of number you are inverting. In the -// words of Andrew Kelley, "If you want to invert the bits of an -// integer, zig has to know how many bits there are." -// -// For more insight into the Zig team's position on why the language -// takes the approach it does with the ~ operator, take a look at -// Andrew's comments on the following github issue: -// https://github.com/ziglang/zig/issues/1382#issuecomment-414459529 -// -// Whew, so after all that what we end up with is: -// PORTB = PORTB & ~@as(u4, 1 << 2); -// -// We can shorten this with the &= combined AND and assignment operator, -// which applies the AND operator on PORTB and then reassigns PORTB. Here's -// what that looks like: -// PORTB &= ~@as(u4, 1 << 2); -// - -// ------------------------------------------------------------------------ -// Conclusion -// ------------------------------------------------------------------------ -// -// While the examples in this quiz have used only 4-bit wide variables, -// working with 8 bits is no different. Here's an example where we set -// every other bit beginning with the two's place: - -// var PORTD: u8 = 0b0000_0000; -// print("PORTD: {b:0>8}\n", .{PORTD}); -// PORTD |= (1 << 1); -// PORTD = setBit(u8, PORTD, 3); -// PORTD |= (1 << 5) | (1 << 7); -// print("PORTD: {b:0>8} // set every other bit\n", .{PORTD}); -// PORTD = ~PORTD; -// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); -// newline(); -// -// // Here we clear every other bit beginning with the two's place. -// -// PORTD = 0b1111_1111; -// print("PORTD: {b:0>8}\n", .{PORTD}); -// PORTD &= ~@as(u8, 1 << 1); -// PORTD = clearBit(u8, PORTD, 3); -// PORTD &= ~@as(u8, (1 << 5) | (1 << 7)); -// print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD}); -// PORTD = ~PORTD; -// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); -// newline(); - -// ---------------------------------------------------------------------------- -// Here are some helper functions for manipulating bits -// ---------------------------------------------------------------------------- - -// Functions for setting, clearing, and toggling a single bit -fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T { - return byte | (1 << bit_pos); -} - -test "setBit" { - try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000); -} - -fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T { - return byte & ~@as(T, (1 << bit_pos)); -} - -test "clearBit" { - try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110); -} - -fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T { - return byte ^ (1 << bit_pos); -} - -test "toggleBit" { - var byte = toggleBit(u8, 0b0000_0000, 0); - try testing.expectEqual(byte, 0b0000_0001); - byte = toggleBit(u8, byte, 0); - try testing.expectEqual(byte, 0b0000_0000); -} - -// ---------------------------------------------------------------------------- -// Some additional functions for setting, clearing, and toggling multiple bits -// at once with a tuple because, hey, why not? -// ---------------------------------------------------------------------------- -// - -fn createBitmask(comptime T: type, comptime bits: anytype) !T { - comptime var bitmask: T = 0; - inline for (bits) |bit| { - if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge; - if (bit < 0) return error.BitPosTooSmall; - - bitmask |= (1 << bit); - } - return bitmask; -} - -test "creating bitmasks from a tuple" { - try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001); - try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010); - try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100); - try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000); - // - try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001); - try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010); - try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100); - try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000); - - try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4})); -} - -fn setBits(byte: u8, bits: anytype) !u8 { - const bitmask = try createBitmask(u8, bits); - return byte | bitmask; -} - -test "setBits" { - try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001); - try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000); - - try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); - try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); - - try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100); - - try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8})); - try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1})); -} - -fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 { - const bitmask: u8 = try createBitmask(u8, bits); - return byte & ~@as(u8, bitmask); -} - -test "clearBits" { - try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110); - try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111); - - try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); - try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); - - try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100); - - try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8})); - try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1})); -} - -fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 { - const bitmask = try createBitmask(u8, bits); - return byte ^ bitmask; -} - -test "toggleBits" { - try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001); - try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000); - - try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); - try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); - - try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000); - try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000); - - try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101); - - try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8})); - try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1})); -} - -// ---------------------------------------------------------------------------- -// Utility functions -// ---------------------------------------------------------------------------- - -fn newline() void { - print("\n", .{}); -} - -fn checkAnswer(expected: u4, answer: u4) void { - if (expected != answer) { - print("*************************************************************\n", .{}); - print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}\n", .{ answer, expected }); - print("*************************************************************\n", .{}); - } else { - print("= {b:0>4}", .{answer}); - } - newline(); -} diff --git a/exercises/111_labeled_switch.zig b/exercises/111_labeled_switch.zig new file mode 100644 index 0000000..897fcf5 --- /dev/null +++ b/exercises/111_labeled_switch.zig @@ -0,0 +1,79 @@ +// +// You've heard of while loops in exercises 011,012,013 and 014 +// You've also heard of switch expressions in exercises 030 and 31. +// You've also seen how labels can be used in exercise 063. +// +// By combining while loops and switch statements with continue and break statements +// one can create very concise State Machines. +// +// One such example would be: +// +// pub fn main() void { +// var op: u8 = 1; +// while (true) { +// switch (op) { +// 1 => { op = 2; continue; }, +// 2 => { op = 3; continue; }, +// 3 => return, +// else => {}, +// } +// break; +// } +// std.debug.print("This statement cannot be reached\n", .{}); +// } +// +// By combining all we've learned so far, we can now proceed with a labeled switch. +// +// A labeled switch is some extra syntactic sugar, which comes with all sorts of +// candy (performance benefits). Don't believe me? Directly to source https://github.com/ziglang/zig/pull/21367 +// +// Here is the previous excerpt implemented as a labeled switch instead: +// +// pub fn main() void { +// foo: switch (@as(u8, 1)) { +// 1 => continue :foo 2, +// 2 => continue :foo 3, +// 3 => return, +// else => {}, +// } +// std.debug.print("This statement cannot be reached\n", .{}); +// } +// +// The flow of execution on this second case is: +// 1. The switch starts with value '1'; +// 2. The switch evaluates to case '1' which in turn uses the continue statement +// to re-evaluate the labeled switch again, now providing the value '2'; +// 3. In the case '2' we repeat the same pattern as case '1' +// but instead the value to be evaluated is now '3'; +// 4. Finally we get to case '3', where we return from the function as a whole, +// so the debug statement is never executed. +// 5. In this example, since the input does not have clear, exhaustive patterns and +// can essentially be any 'u8' integer, we need to handle all cases not explicitly +// covered by using the 'else => {}' branch as the default case. +// +// +const std = @import("std"); + +const PullRequestState = enum(u8) { + Draft, + InReview, + Approved, + Rejected, + Merged, +}; + +pub fn main() void { + // Oh no, your pull request keeps being rejected, + // how would you fix it? + pr: switch (PullRequestState.Draft) { + PullRequestState.Draft => continue :pr PullRequestState.InReview, + PullRequestState.InReview => continue :pr PullRequestState.Rejected, + PullRequestState.Approved => continue :pr PullRequestState.Merged, + PullRequestState.Rejected => { + std.debug.print("The pull request has been rejected.\n", .{}); + return; + }, + PullRequestState.Merged => break, // Would you know where to break to? + } + std.debug.print("The pull request has been merged.\n", .{}); +} diff --git a/exercises/111_packed.zig b/exercises/111_packed.zig deleted file mode 100644 index 1058f76..0000000 --- a/exercises/111_packed.zig +++ /dev/null @@ -1,175 +0,0 @@ -// -// We've already learned plenty about bit manipulation using bitwise operations -// in exercices 097 and 098 and in quiz 110. The techniques we already know work -// just fine, but creating masks and shifting individual bits around can become -// quite tedious and unwieldy pretty quickly. -// What if there was a better, a more convenient way to control invidivual bits? -// -// Luckily, Zig has a keyword for exactly this purpose: -// -// packed -// -// It doesn't do anything on its own, to unlock its potential (and to get our -// program to compile) we have to attach it either to a struct or to a union -// declaration: -// -// const Foo = packed struct { ... }; -// const Bar = packed union { ... }; -// -// Now, what does this keyword even do? -// To answer this question we first have to talk about *container layouts*. -// -// Plain structs and unions use the `auto` layout; it gives no guarantees about -// their size or the order of the fields they contain, both are fully up to the -// compiler (though both size and field order *are* guaranteed to be the same -// across any single compilation unit). -// -// Attaching the `packed` keyword to a container makes it use `packed` layout: -// Suddenly, all of its fields are *packed* together tightly without any padding -// in between and their order is guaranteed to be the same as the one specified -// in our source code. For structs, the size of the container is guaranteed to -// be the sum of the (bit-)sizes of all of its fields. For unions, all fields -// have to have the exact same (bit-)size (no padding allowed!); the union itself -// is also guaranteed to be exactly of this size. -// -// If you're familiar with C, you might have already heard of structure packing -// in a different context: arranging fields in a way that minimizes the amount -// of alignment padding between them (or having the compiler do it for you). -// This is *not* what Zig's `packed` keyword is for! -// -// Try to make the comptime assertions below pass: - -const PackedStruct = packed struct { - a: u2, - b: u?, -}; - -comptime { - assert(@bitSizeOf(PackedStruct) == 6); -} - -const PackedUnion = packed union { - a: bool, - b: u?, -}; - -comptime { - assert(@bitSizeOf(PackedUnion) == 1); -} - -// Now, how can we use this new knowledge to manipulate some bits? -// -// As you might have already guessed, `packed` containers are very useful for -// representing bitflags or other tightly packed collections of bit-sized values -// often found in file headers and network protocols. -// -// Let's take a look at a real-life example: -// The LZ4 compression format (†) specifies a frame format to describe compressed -// data. Each LZ4 frame has a descriptor, and each descriptor contains a 'FLG' -// byte that specifies the contents of its frame: - -/// | BitNb | 7-6 | 5 | 4 | 3 | 2 | 1 | 0 | -/// | ------- |-------|-------|----------|------|----------|--------|------| -/// |FieldName|Version|B.Indep|B.Checksum|C.Size|C.Checksum|Reserved|DictID| -/// -const FLG = packed struct(u8) { - dict_id: bool, - reserved: u1 = 0, - content_checksum: bool, - content_size: bool, - block_checksum: bool, - block_indepencence: bool, - version: u2, -}; - -// Wait, what's with the `(u8)` after the `struct` keyword? What do integers have -// to do with all of this? -// Well, this is a good opportunity to come clear about something: -// packed structs and packed unions aren't actually structs or unions at all... -// They are merely integers in disguise! For all intents and purposes, their -// fields are just convenient names for ranges of their underlying bits. To make -// it easier to enforce size requirements for packed containers, Zig allows us -// to specify a *backing integer* for them, just like for enums. -// -// In the case of `FLG`, we want our struct to occupy exactly a single byte, so -// we specify `u8` as the backing integer. It's safe to convert between a packed -// container and its backing integer using the builtin `@bitCast`. -// The LZ4 spec also mandates that reserved bits must always be zero, so it's -// good practice to set `0` as a default value for `reserved`. -// -// The fields of a packed struct start at the least significant bit of its backing -// integer and end at its most significant bit. This is the case no matter what -// endianness our target has. -// -// Try to silence the complaints below: - -const Bits = packed struct(u4) { - a: u1 = 0, - b: u1 = 0, - c: u1 = 0, - d: u1 = 0, -}; - -pub fn main() void { - { - const expected: Bits = @bitCast(@as(u4, 0b1000)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0001)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0010)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0011)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b1101)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } -} - -// As we can see, equality comparisons (`==` and `!=`) work for packed structs. -// They also work for packed unions. However, since packed containers are not -// naturally ordered, we can't use any other comparison operators on them. -// -// It's also possible to use packed containers in `switch` statements, which we -// will cover in the next exercise! -// -// Since packed containers make very strong guarantees about their memory layout, -// only a handful of types are eligible to be part of them. -// The following types are allowed as field types: -// -// - integers -// - floats -// - bool -// - void -// - enums with explicit backing integers -// - packed unions -// - packed structs -// - -const std = @import("std"); -const assert = std.debug.assert; - -fn complain(my_bits: Bits, expected: Bits, src_loc: std.builtin.SourceLocation) void { - std.debug.print( - "That's not quite right! You've got 0b{b:0>4}, but we want 0b{b:0>4} in line {d}.\n", - .{ @as(u4, @bitCast(my_bits)), @as(u4, @bitCast(expected)), src_loc.line }, - ); -} - -// (†) https://github.com/lz4/lz4/blob/5c4c1fb2354133e1f3b087a341576985f8114bd5/doc/lz4_Frame_format.md#frame-descriptor diff --git a/exercises/112_packed2.zig b/exercises/112_packed2.zig deleted file mode 100644 index 2b6b558..0000000 --- a/exercises/112_packed2.zig +++ /dev/null @@ -1,78 +0,0 @@ -// -// We've already learned about switch statements in exercises 030, 031 and 108. -// They also work with packed containers: - -const S = packed struct(u2) { - a: bool, - b: i1, -}; - -// Try to make it compile without adding an `else` prong! - -comptime { - const s: S = .{ .a = true, .b = -1 }; - switch (s) { - .{ .a = true, .b = -1 } => {}, // ok! - .{ .a = true, .b = ??? }, - .{ .a = ???, .b = 0 }, - .{ .a = ???, .b = ??? }, - => @compileError("We don't want to end up here!"), - } -} - -// As we can see, switching on packed structs is pretty straightforward. -// When switching on packed unions however, we'll realize that a packed -// union never keeps track of its active tag, not even in debug mode! This -// means that packed unions compare solely by their bit pattern (again, just -// like integers). - -const U = packed union(u2) { - a: u2, - b: i2, -}; - -// Find and remove the duplicate case! - -comptime { - const u: U = .{ .a = 3 }; - switch (u) { - .{ .a = 3 } => {}, // ok! - .{ .a = 2 }, - .{ .b = 1 }, - .{ .b = -1 }, - .{ .a = 0 }, - => @compileError("We don't want to end up here!"), - } -} - -// Since packed unions don't have the concept of an active tag, it's always legal -// to access any of their fields. This can be useful to view the same data from -// different perspectives seamlessly. -// -// Try to make the float below negative: - -/// IEEE 754 half precision float -const Float = packed union(u16) { - value: f16, - bits: packed struct(u16) { - mantissa: u10, - exponent: u5, - sign: u1, - }, -}; - -pub fn main() void { - // Reminder: if the sign bit of a float is set, the number is negative! - - var number: Float = .{ .value = 2.34 }; - number.bits.??? = ???; - if (number.value != -2.34) { - std.debug.print("Make it negative!\n", .{}); - } -} - -// This concludes our introduction to packed containers. The next time you need -// control over individual bits, keep them in mind as a potent alternative! -// - -const std = @import("std"); diff --git a/exercises/112_vectors.zig b/exercises/112_vectors.zig new file mode 100644 index 0000000..96892ca --- /dev/null +++ b/exercises/112_vectors.zig @@ -0,0 +1,147 @@ +// So far in Ziglings, we've seen how for loops can be used to +// repeat calculations across an array in several ways. +// +// For loops are generally great for this kind of task, but +// sometimes they don't fully utilize the capabilities of the +// CPU. +// +// Most modern CPUs can execute instructions in which SEVERAL +// calculations are performed WITHIN registers at the SAME TIME. +// These are known as "single instruction, multiple data" (SIMD) +// instructions. SIMD instructions can make code significantly +// more performant. +// +// To see why, imagine we have a program in which we take the +// square root of four (changing) f32 floats. +// +// A simple compiler would take the program and produce machine code +// which calculates each square root sequentially. Most registers on +// modern CPUs have 64 bits, so we could imagine that each float moves +// into a 64-bit register, and the following happens four times: +// +// 32 bits 32 bits +// +-------------------+ +// register | 0 | x | +// +-------------------+ +// +// | +// [SQRT instruction] +// V +// +// +-------------------+ +// | 0 | sqrt(x) | +// +-------------------+ +// +// Notice that half of the register contains blank data to which +// nothing happened. What a waste! What if we were able to use +// that space instead? This is the idea at the core of SIMD. +// +// Most modern CPUs contain specialized registers with at least 128 bits +// for performing SIMD instructions. On a machine with 128-bit SIMD +// registers, a smart compiler would probably NOT issue four sqrt +// instructions as above, but instead pack the floats into a single +// 128-bit register, then execute a single "packed" sqrt +// instruction to do ALL the square root calculations at once. +// +// For example: +// +// +// 32 bits 32 bits 32 bits 32 bits +// +---------------------------------------+ +// register | 4.0 | 9.0 | 25.0 | 49.0 | +// +---------------------------------------+ +// +// | +// [SIMD SQRT instruction] +// V +// +// +---------------------------------------+ +// register | 2.0 | 3.0 | 5.0 | 7.0 | +// +---------------------------------------+ +// +// Pretty cool, right? +// +// Code with SIMD instructions is usually more performant than code +// without SIMD instructions. Zig cares a lot about performance, +// so it has built-in support for SIMD! It has a data structure that +// directly supports SIMD instructions: +// +// +-----------+ +// | Vectors | +// +-----------+ +// +// Operations performed on vectors in Zig will be done in parallel using +// SIMD instructions, whenever possible. +// +// Defining vectors in Zig is straightforwards. No library import is needed. +const v1 = @Vector(3, i32){ 1, 10, 100 }; +const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 }; + +// Vectors support the same builtin operators as their underlying base types. +const v3 = v1 + v1; // { 2, 20, 200}; +const v4 = v2 * v2; // { 4.0, 9.0, 25.0}; + +// Intrinsics that apply to base types usually extend to vectors. +const v5: @Vector(3, f32) = @floatFromInt(v3); // { 2.0, 20.0, 200.0} +const v6 = v4 - v5; // { 2.0, -11.0, -175.0} +const v7 = @abs(v6); // { 2.0, 11.0, 175.0} + +// We can make constant vectors, and reduce vectors. +const v8: @Vector(4, u8) = @splat(2); // { 2, 2, 2, 2} +const v8_sum = @reduce(.Add, v8); // 8 +const v8_min = @reduce(.Min, v8); // 2 + +// Fixed-length arrays can be automatically assigned to vectors (and vice-versa). +const single_digit_primes = [4]i8{ 2, 3, 5, 7 }; +const prime_vector: @Vector(4, i8) = single_digit_primes; + +// Now let's use vectors to simplify and optimize some code! +// +// Ewa is writing a program in which they frequently want to compare +// two lists of four f32s. Ewa expects the lists to be similar, and +// wants to determine the largest pairwise difference between the lists. +// +// Ewa wrote the following function to figure this out. + +fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 { + var max_diff: f32 = 0; + for (list1, list2) |n1, n2| { + const abs_diff = @abs(n1 - n2); + if (abs_diff > max_diff) { + max_diff = abs_diff; + } + } + return max_diff; +} + +// Ewa heard about vectors in Zig, and started writing a new vector +// version of the function, but has got stuck! +// +// Help Ewa finish the vector version! The examples above should help. + +const Vec4 = @Vector(4, f32); +fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 { + const abs_diff_vec = ???; + const max_diff = @reduce(???, abs_diff_vec); + return max_diff; +} + +// Quite the simplification! We could even write the function in one line +// and it would still be readable. +// +// Since the entire function is now expressed in terms of vector operations, +// the Zig compiler will easily be able to compile it down to machine code +// which utilizes the all-powerful SIMD instructions and does a lot of the +// computation in parallel. + +const std = @import("std"); +const print = std.debug.print; + +pub fn main() void { + const l1 = [4]f32{ 3.141, 2.718, 0.577, 1.000 }; + const l2 = [4]f32{ 3.154, 2.707, 0.591, 0.993 }; + const mpd_old = calcMaxPairwiseDiffOld(l1, l2); + const mpd_new = calcMaxPairwiseDiffNew(l1, l2); + print("Max difference (old fn): {d: >5.3}\n", .{mpd_old}); + print("Max difference (new fn): {d: >5.3}\n", .{mpd_new}); +} diff --git a/exercises/113_quiz9.zig b/exercises/113_quiz9.zig new file mode 100644 index 0000000..8f5cb61 --- /dev/null +++ b/exercises/113_quiz9.zig @@ -0,0 +1,484 @@ +// ---------------------------------------------------------------------------- +// Quiz Time: Toggling, Setting, and Clearing Bits +// ---------------------------------------------------------------------------- +// +// Another exciting thing about Zig is its suitability for embedded +// programming. Your Zig code doesn't have to remain on your laptop; you can +// also deploy your code to microcontrollers! This means you can write Zig to +// drive your next robot or greenhouse climate control system! Ready to enter +// the exciting world of embedded programming? Let's get started! +// +// ---------------------------------------------------------------------------- +// Some Background +// ---------------------------------------------------------------------------- +// +// A common activity in microcontroller programming is setting and clearing +// bits on input and output pins. This lets you control LEDs, sensors, motors +// and more! In a previous exercise (097_bit_manipulation.zig) you learned how +// to swap two bytes using the ^ (XOR - exclusive or) operator. This quiz will +// test your knowledge of bit manipulation in Zig while giving you a taste of +// what it's like to control registers in a real microcontroller. Included at +// the end are some helper functions that demonstrate how we might make our +// code a little more readable. +// +// Below is a pinout diagram for the famous ATmega328 AVR microcontroller used +// as the primary microchip on popular microcontroller platforms like the +// Arduino UNO. +// +// ============ PINOUT DIAGRAM FOR ATMEGA328 MICROCONTROLLER ============ +// _____ _____ +// | U | +// (RESET) PC6 --| 1 28 |-- PC5 +// PD0 --| 2 27 |-- PC4 +// PD1 --| 3 26 |-- PC3 +// PD2 --| 4 25 |-- PC2 +// PD3 --| 5 24 |-- PC1 +// PD4 --| 6 23 |-- PC0 +// VCC --| 7 22 |-- GND +// GND --| 8 21 |-- AREF +// |-- PB6 --| 9 20 |-- AVCC +// |-- PB7 --| 10 19 |-- PB5 --| +// | PD5 --| 11 18 |-- PB4 --| +// | PD6 --| 12 17 |-- PB3 --| +// | PD7 --| 13 16 |-- PB2 --| +// |-- PB0 --| 14 15 |-- PB1 --| +// | |___________| | +// \_______________________________/ +// | +// PORTB +// +// Drawing inspiration from this diagram, we'll use the pins for PORTB as our +// mental model for this quiz on bit manipulation. It should be noted that +// in the following problems we are using ordinary variables, one of which we +// have named PORTB, to simulate modifying the bits of real hardware registers. +// But in actual microcontroller code, PORTB would be defined something like +// this: +// pub const PORTB = @as(*volatile u8, @ptrFromInt(0x25)); +// +// This lets the compiler know not to make any optimizations to PORTB so that +// the IO pins are properly mapped to our code. +// +// NOTE : To keep things simple, the following problems are given using type +// u4, so applying the output to PORTB would only affect the lower four pins +// PB0..PB3. Of course, there is nothing to prevent you from swapping the u4 +// with a u8 so you can control all 8 of PORTB's IO pins. + +const std = @import("std"); +const print = std.debug.print; +const testing = std.testing; + +pub fn main() !void { + var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity + + // ------------------------------------------------------------------------ + // Quiz + // ------------------------------------------------------------------------ + + // See if you can solve the following problems. The last two problems throw + // you a bit of a curve ball. Try solving them on your own. If you need + // help, scroll to the bottom of main to see some in depth explanations on + // toggling, setting, and clearing bits in Zig. + + print("Toggle pins with XOR on PORTB\n", .{}); + print("-----------------------------\n", .{}); + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0101}); + PORTB ^= (1 << 1) | (1 << 0); // What's wrong here? + checkAnswer(0b1001, PORTB); + + newline(); + + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0011}); + PORTB ^= (1 << 1) & (1 << 0); // What's wrong here? + checkAnswer(0b1111, PORTB); + + newline(); + + print("Set pins with OR on PORTB\n", .{}); + print("-------------------------\n", .{}); + + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); + PORTB = PORTB ??? (1 << 2); // What's missing here? + checkAnswer(0b1101, PORTB); + + newline(); + + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); + PORTB ??? (1 << 2); // What's missing here? + checkAnswer(0b1101, PORTB); + + newline(); + + print("Clear pins with AND and NOT on PORTB\n", .{}); + print("------------------------------------\n", .{}); + + PORTB = 0b1110; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1011}); + PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here? + checkAnswer(0b1010, PORTB); + + newline(); + + PORTB = 0b0111; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1110}); + PORTB &= ~(1 << 0); // What's missing here? + checkAnswer(0b0110, PORTB); + + newline(); + newline(); +} + +// ************************************************************************ +// IN-DEPTH EXPLANATIONS BELOW +// ************************************************************************ +// +// +// +// +// +// +// +// +// +// +// +// ------------------------------------------------------------------------ +// Toggling bits with XOR: +// ------------------------------------------------------------------------ +// XOR stands for "exclusive or". We can toggle bits with the ^ (XOR) +// bitwise operator, like so: +// +// +// In order to output a 1, the logic of an XOR operation requires that the +// two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will +// both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior +// of outputting a 0 when both inputs are 1s is what makes it different from +// the OR operator; it also gives us the ability to toggle bits by putting +// 1s into our bitmask. +// +// - 1s in our bitmask operand, can be thought of as causing the +// corresponding bits in the other operand to flip to the opposite value. +// - 0s cause no change. +// +// The 0s in our bitmask preserve these values +// -XOR op- ---expanded--- in the output. +// _______________/ +// / / +// 1100 1 1 0 0 +// ^ 0101 0 1 0 1 (bitmask) +// ------ - - - - +// = 1001 1 0 0 1 <- This bit was already cleared. +// \_______\ +// \ +// We can think of these bits having flipped +// because of the presence of 1s in those columns +// of our bitmask. +// +// Now let's take a look at setting bits with the | operator. +// +// +// +// +// +// ------------------------------------------------------------------------ +// Setting bits with OR: +// ------------------------------------------------------------------------ +// We can set bits on PORTB with the | (OR) operator, like so: +// +// var PORTB: u4 = 0b1001; +// PORTB = PORTB | 0b0010; +// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 +// +// -OR op- ---expanded--- +// _ Set only this bit. +// / +// 1001 1 0 0 1 +// | 0010 0 0 1 0 (bitmask) +// ------ - - - - +// = 1011 1 0 1 1 +// \___\_______\ +// \ +// These bits remain untouched because OR-ing with +// a 0 effects no change. +// +// ------------------------------------------------------------------------ +// To create a bitmask like 0b0010 used above: +// +// 1. First, shift the value 1 over one place with the bitwise << (shift +// left) operator as indicated below: +// 1 << 0 -> 0001 +// 1 << 1 -> 0010 <-- Shift 1 one place to the left +// 1 << 2 -> 0100 +// 1 << 3 -> 1000 +// +// This allows us to rewrite the above code like this: +// +// var PORTB: u4 = 0b1001; +// PORTB = PORTB | (1 << 1); +// print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011 +// +// Finally, as in the C language, Zig allows us to use the |= operator, so +// we can rewrite our code again in an even more compact and idiomatic +// form: PORTB |= (1 << 1) + +// So now we've covered how to toggle and set bits. What about clearing +// them? Well, this is where Zig throws us a curve ball. Don't worry we'll +// go through it step by step. +// +// +// +// +// +// ------------------------------------------------------------------------ +// Clearing bits with AND and NOT: +// ------------------------------------------------------------------------ +// We can clear bits with the & (AND) bitwise operator, like so: + +// PORTB = 0b1110; // reset PORTB +// PORTB = PORTB & 0b1011; +// print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010 +// +// - 0s clear bits when used in conjunction with a bitwise AND. +// - 1s do nothing, thus preserving the original bits. +// +// -AND op- ---expanded--- +// __________ Clear only this bit. +// / +// 1110 1 1 1 0 +// & 1011 1 0 1 1 (bitmask) +// ------ - - - - +// = 1010 1 0 1 0 <- This bit was already cleared. +// \_______\ +// \ +// These bits remain untouched because AND-ing with a +// 1 preserves the original bit value whether 0 or 1. +// +// ------------------------------------------------------------------------ +// We can use the ~ (NOT) operator to easily create a bitmask like 1011: +// +// 1. First, shift the value 1 over two places with the bit-wise << (shift +// left) operator as indicated below: +// 1 << 0 -> 0001 +// 1 << 1 -> 0010 +// 1 << 2 -> 0100 <- The 1 has been shifted two places to the left +// 1 << 3 -> 1000 +// +// 2. The second step in creating our bitmask is to invert the bits +// ~0100 -> 1011 +// in C we would write this as: +// ~(1 << 2) -> 1011 +// +// But if we try to compile ~(1 << 2) in Zig, we'll get an error: +// unable to perform binary not operation on type 'comptime_int' +// +// Before Zig can invert our bits, it needs to know the number of +// bits it's being asked to invert. +// +// We do this with the @as (cast as) built-in like this: +// @as(u4, 1 << 2) -> 0100 +// +// Finally, we can invert our new mask by placing the NOT ~ operator +// before our expression, like this: +// ~@as(u4, 1 << 2) -> 1011 +// +// If you are offput by the fact that you can't simply invert bits like +// you can in languages such as C without casting to a particular size +// of integer, you're not alone. However, this is actually another +// instance where Zig is really helpful because it protects you from +// difficult to debug integer overflow bugs that can have you tearing +// your hair out. In the interest of keeping things sane, Zig requires +// you simply to tell it the size of number you are inverting. In the +// words of Andrew Kelley, "If you want to invert the bits of an +// integer, zig has to know how many bits there are." +// +// For more insight into the Zig team's position on why the language +// takes the approach it does with the ~ operator, take a look at +// Andrew's comments on the following github issue: +// https://github.com/ziglang/zig/issues/1382#issuecomment-414459529 +// +// Whew, so after all that what we end up with is: +// PORTB = PORTB & ~@as(u4, 1 << 2); +// +// We can shorten this with the &= combined AND and assignment operator, +// which applies the AND operator on PORTB and then reassigns PORTB. Here's +// what that looks like: +// PORTB &= ~@as(u4, 1 << 2); +// + +// ------------------------------------------------------------------------ +// Conclusion +// ------------------------------------------------------------------------ +// +// While the examples in this quiz have used only 4-bit wide variables, +// working with 8 bits is no different. Here's an example where we set +// every other bit beginning with the two's place: + +// var PORTD: u8 = 0b0000_0000; +// print("PORTD: {b:0>8}\n", .{PORTD}); +// PORTD |= (1 << 1); +// PORTD = setBit(u8, PORTD, 3); +// PORTD |= (1 << 5) | (1 << 7); +// print("PORTD: {b:0>8} // set every other bit\n", .{PORTD}); +// PORTD = ~PORTD; +// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); +// newline(); +// +// // Here we clear every other bit beginning with the two's place. +// +// PORTD = 0b1111_1111; +// print("PORTD: {b:0>8}\n", .{PORTD}); +// PORTD &= ~@as(u8, 1 << 1); +// PORTD = clearBit(u8, PORTD, 3); +// PORTD &= ~@as(u8, (1 << 5) | (1 << 7)); +// print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD}); +// PORTD = ~PORTD; +// print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD}); +// newline(); + +// ---------------------------------------------------------------------------- +// Here are some helper functions for manipulating bits +// ---------------------------------------------------------------------------- + +// Functions for setting, clearing, and toggling a single bit +fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T { + return byte | (1 << bit_pos); +} + +test "setBit" { + try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000); +} + +fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T { + return byte & ~@as(T, (1 << bit_pos)); +} + +test "clearBit" { + try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110); +} + +fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T { + return byte ^ (1 << bit_pos); +} + +test "toggleBit" { + var byte = toggleBit(u8, 0b0000_0000, 0); + try testing.expectEqual(byte, 0b0000_0001); + byte = toggleBit(u8, byte, 0); + try testing.expectEqual(byte, 0b0000_0000); +} + +// ---------------------------------------------------------------------------- +// Some additional functions for setting, clearing, and toggling multiple bits +// at once with a tuple because, hey, why not? +// ---------------------------------------------------------------------------- +// + +fn createBitmask(comptime T: type, comptime bits: anytype) !T { + comptime var bitmask: T = 0; + inline for (bits) |bit| { + if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge; + if (bit < 0) return error.BitPosTooSmall; + + bitmask |= (1 << bit); + } + return bitmask; +} + +test "creating bitmasks from a tuple" { + try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001); + try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010); + try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100); + try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000); + // + try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001); + try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010); + try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100); + try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000); + + try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4})); +} + +fn setBits(byte: u8, bits: anytype) !u8 { + const bitmask = try createBitmask(u8, bits); + return byte | bitmask; +} + +test "setBits" { + try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001); + try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000); + + try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); + try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); + + try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100); + + try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8})); + try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1})); +} + +fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 { + const bitmask: u8 = try createBitmask(u8, bits); + return byte & ~@as(u8, bitmask); +} + +test "clearBits" { + try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110); + try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111); + + try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); + try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); + + try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100); + + try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8})); + try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1})); +} + +fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 { + const bitmask = try createBitmask(u8, bits); + return byte ^ bitmask; +} + +test "toggleBits" { + try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001); + try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000); + + try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000); + try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111); + + try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000); + try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000); + + try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101); + + try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8})); + try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1})); +} + +// ---------------------------------------------------------------------------- +// Utility functions +// ---------------------------------------------------------------------------- + +fn newline() void { + print("\n", .{}); +} + +fn checkAnswer(expected: u4, answer: u4) void { + if (expected != answer) { + print("*************************************************************\n", .{}); + print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}\n", .{ answer, expected }); + print("*************************************************************\n", .{}); + } else { + print("= {b:0>4}", .{answer}); + } + newline(); +} diff --git a/exercises/114_packed.zig b/exercises/114_packed.zig new file mode 100644 index 0000000..1058f76 --- /dev/null +++ b/exercises/114_packed.zig @@ -0,0 +1,175 @@ +// +// We've already learned plenty about bit manipulation using bitwise operations +// in exercices 097 and 098 and in quiz 110. The techniques we already know work +// just fine, but creating masks and shifting individual bits around can become +// quite tedious and unwieldy pretty quickly. +// What if there was a better, a more convenient way to control invidivual bits? +// +// Luckily, Zig has a keyword for exactly this purpose: +// +// packed +// +// It doesn't do anything on its own, to unlock its potential (and to get our +// program to compile) we have to attach it either to a struct or to a union +// declaration: +// +// const Foo = packed struct { ... }; +// const Bar = packed union { ... }; +// +// Now, what does this keyword even do? +// To answer this question we first have to talk about *container layouts*. +// +// Plain structs and unions use the `auto` layout; it gives no guarantees about +// their size or the order of the fields they contain, both are fully up to the +// compiler (though both size and field order *are* guaranteed to be the same +// across any single compilation unit). +// +// Attaching the `packed` keyword to a container makes it use `packed` layout: +// Suddenly, all of its fields are *packed* together tightly without any padding +// in between and their order is guaranteed to be the same as the one specified +// in our source code. For structs, the size of the container is guaranteed to +// be the sum of the (bit-)sizes of all of its fields. For unions, all fields +// have to have the exact same (bit-)size (no padding allowed!); the union itself +// is also guaranteed to be exactly of this size. +// +// If you're familiar with C, you might have already heard of structure packing +// in a different context: arranging fields in a way that minimizes the amount +// of alignment padding between them (or having the compiler do it for you). +// This is *not* what Zig's `packed` keyword is for! +// +// Try to make the comptime assertions below pass: + +const PackedStruct = packed struct { + a: u2, + b: u?, +}; + +comptime { + assert(@bitSizeOf(PackedStruct) == 6); +} + +const PackedUnion = packed union { + a: bool, + b: u?, +}; + +comptime { + assert(@bitSizeOf(PackedUnion) == 1); +} + +// Now, how can we use this new knowledge to manipulate some bits? +// +// As you might have already guessed, `packed` containers are very useful for +// representing bitflags or other tightly packed collections of bit-sized values +// often found in file headers and network protocols. +// +// Let's take a look at a real-life example: +// The LZ4 compression format (†) specifies a frame format to describe compressed +// data. Each LZ4 frame has a descriptor, and each descriptor contains a 'FLG' +// byte that specifies the contents of its frame: + +/// | BitNb | 7-6 | 5 | 4 | 3 | 2 | 1 | 0 | +/// | ------- |-------|-------|----------|------|----------|--------|------| +/// |FieldName|Version|B.Indep|B.Checksum|C.Size|C.Checksum|Reserved|DictID| +/// +const FLG = packed struct(u8) { + dict_id: bool, + reserved: u1 = 0, + content_checksum: bool, + content_size: bool, + block_checksum: bool, + block_indepencence: bool, + version: u2, +}; + +// Wait, what's with the `(u8)` after the `struct` keyword? What do integers have +// to do with all of this? +// Well, this is a good opportunity to come clear about something: +// packed structs and packed unions aren't actually structs or unions at all... +// They are merely integers in disguise! For all intents and purposes, their +// fields are just convenient names for ranges of their underlying bits. To make +// it easier to enforce size requirements for packed containers, Zig allows us +// to specify a *backing integer* for them, just like for enums. +// +// In the case of `FLG`, we want our struct to occupy exactly a single byte, so +// we specify `u8` as the backing integer. It's safe to convert between a packed +// container and its backing integer using the builtin `@bitCast`. +// The LZ4 spec also mandates that reserved bits must always be zero, so it's +// good practice to set `0` as a default value for `reserved`. +// +// The fields of a packed struct start at the least significant bit of its backing +// integer and end at its most significant bit. This is the case no matter what +// endianness our target has. +// +// Try to silence the complaints below: + +const Bits = packed struct(u4) { + a: u1 = 0, + b: u1 = 0, + c: u1 = 0, + d: u1 = 0, +}; + +pub fn main() void { + { + const expected: Bits = @bitCast(@as(u4, 0b1000)); + const my_bits: Bits = .{}; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b0001)); + const my_bits: Bits = .{}; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b0010)); + const my_bits: Bits = .{}; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b0011)); + const my_bits: Bits = .{}; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b1101)); + const my_bits: Bits = .{}; + if (my_bits != expected) complain(my_bits, expected, @src()); + } +} + +// As we can see, equality comparisons (`==` and `!=`) work for packed structs. +// They also work for packed unions. However, since packed containers are not +// naturally ordered, we can't use any other comparison operators on them. +// +// It's also possible to use packed containers in `switch` statements, which we +// will cover in the next exercise! +// +// Since packed containers make very strong guarantees about their memory layout, +// only a handful of types are eligible to be part of them. +// The following types are allowed as field types: +// +// - integers +// - floats +// - bool +// - void +// - enums with explicit backing integers +// - packed unions +// - packed structs +// + +const std = @import("std"); +const assert = std.debug.assert; + +fn complain(my_bits: Bits, expected: Bits, src_loc: std.builtin.SourceLocation) void { + std.debug.print( + "That's not quite right! You've got 0b{b:0>4}, but we want 0b{b:0>4} in line {d}.\n", + .{ @as(u4, @bitCast(my_bits)), @as(u4, @bitCast(expected)), src_loc.line }, + ); +} + +// (†) https://github.com/lz4/lz4/blob/5c4c1fb2354133e1f3b087a341576985f8114bd5/doc/lz4_Frame_format.md#frame-descriptor diff --git a/exercises/115_packed2.zig b/exercises/115_packed2.zig new file mode 100644 index 0000000..2b6b558 --- /dev/null +++ b/exercises/115_packed2.zig @@ -0,0 +1,78 @@ +// +// We've already learned about switch statements in exercises 030, 031 and 108. +// They also work with packed containers: + +const S = packed struct(u2) { + a: bool, + b: i1, +}; + +// Try to make it compile without adding an `else` prong! + +comptime { + const s: S = .{ .a = true, .b = -1 }; + switch (s) { + .{ .a = true, .b = -1 } => {}, // ok! + .{ .a = true, .b = ??? }, + .{ .a = ???, .b = 0 }, + .{ .a = ???, .b = ??? }, + => @compileError("We don't want to end up here!"), + } +} + +// As we can see, switching on packed structs is pretty straightforward. +// When switching on packed unions however, we'll realize that a packed +// union never keeps track of its active tag, not even in debug mode! This +// means that packed unions compare solely by their bit pattern (again, just +// like integers). + +const U = packed union(u2) { + a: u2, + b: i2, +}; + +// Find and remove the duplicate case! + +comptime { + const u: U = .{ .a = 3 }; + switch (u) { + .{ .a = 3 } => {}, // ok! + .{ .a = 2 }, + .{ .b = 1 }, + .{ .b = -1 }, + .{ .a = 0 }, + => @compileError("We don't want to end up here!"), + } +} + +// Since packed unions don't have the concept of an active tag, it's always legal +// to access any of their fields. This can be useful to view the same data from +// different perspectives seamlessly. +// +// Try to make the float below negative: + +/// IEEE 754 half precision float +const Float = packed union(u16) { + value: f16, + bits: packed struct(u16) { + mantissa: u10, + exponent: u5, + sign: u1, + }, +}; + +pub fn main() void { + // Reminder: if the sign bit of a float is set, the number is negative! + + var number: Float = .{ .value = 2.34 }; + number.bits.??? = ???; + if (number.value != -2.34) { + std.debug.print("Make it negative!\n", .{}); + } +} + +// This concludes our introduction to packed containers. The next time you need +// control over individual bits, keep them in mind as a potent alternative! +// + +const std = @import("std"); diff --git a/patches/patches/091_async8.patch b/patches/patches/091_async8.patch new file mode 100644 index 0000000..865292f --- /dev/null +++ b/patches/patches/091_async8.patch @@ -0,0 +1,11 @@ +--- exercises/091_async8.zig 2026-04-02 10:49:27.925721496 +0200 ++++ answers/091_async8.zig 2026-04-02 10:49:31.694795212 +0200 +@@ -43,7 +43,7 @@ + // Send numbers 1 through 10 into the queue. + for (1..11) |i| { + // What Queue method sends a single element, blocking if full? +- queue.???(io, @intCast(i)) catch return; ++ queue.putOne(io, @intCast(i)) catch return; + } + // Signal that we're done sending. + queue.close(io); diff --git a/patches/patches/092_interfaces.patch b/patches/patches/092_interfaces.patch deleted file mode 100644 index 36d8784..0000000 --- a/patches/patches/092_interfaces.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/092_interfaces.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/092_interfaces.zig 2023-10-05 20:04:07.259437354 +0200 -@@ -106,7 +106,7 @@ - for (my_insects) |insect| { - // Almost done! We want to print() each insect with a - // single method call here. -- ??? -+ insect.print(); - } - } - diff --git a/patches/patches/093_hello_c.patch b/patches/patches/093_hello_c.patch deleted file mode 100644 index fe1f853..0000000 --- a/patches/patches/093_hello_c.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/093_hello_c.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/093_hello_c.zig 2023-10-05 20:04:07.262770750 +0200 -@@ -54,7 +54,7 @@ - // - // In this exercise we use 'write' to output 17 chars, - // but something is still missing... -- const c_res = write(2, "Hello C from Zig!", 17); -+ const c_res = c.write(2, "Hello C from Zig!", 17); - - // let's see what the result from C is: - std.debug.print(" - C result is {d} chars written.\n", .{c_res}); diff --git a/patches/patches/094_c_math.patch b/patches/patches/094_c_math.patch deleted file mode 100644 index f8c7620..0000000 --- a/patches/patches/094_c_math.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/094_c_math.zig 2024-02-28 12:50:35.789939935 +0100 -+++ answers/094_c_math.zig 2024-02-28 12:53:57.910309471 +0100 -@@ -26,7 +26,7 @@ - - const c = @cImport({ - // What do we need here? -- ??? -+ @cInclude("math.h"); - }); - - pub fn main() !void { diff --git a/patches/patches/095_for3.patch b/patches/patches/095_for3.patch deleted file mode 100644 index a158b31..0000000 --- a/patches/patches/095_for3.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- exercises/095_for3.zig 2026-02-27 19:33:59 -+++ answers/095_for3.zig 2026-02-27 19:33:38 -@@ -56,7 +56,7 @@ - - // I want to print every number between 1 and 20 that is NOT - // divisible by 3 or 5. -- for (???) |n| { -+ for (1..21) |n| { - - // The '%' symbol is the "modulo" operator and it - // returns the remainder after division. -@@ -68,7 +68,7 @@ - std.debug.print("\n", .{}); - - // Let's also print every number from 1 through 15 -- for (???) |n| { -+ for (1..16) |n| { - std.debug.print("{} ", .{n}); - } - std.debug.print("\n", .{}); diff --git a/patches/patches/095_interfaces.patch b/patches/patches/095_interfaces.patch new file mode 100644 index 0000000..cb5fd62 --- /dev/null +++ b/patches/patches/095_interfaces.patch @@ -0,0 +1,11 @@ +--- exercises/095_interfaces.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/095_interfaces.zig 2026-04-03 13:09:13.722917764 +0200 +@@ -106,7 +106,7 @@ + for (my_insects) |insect| { + // Almost done! We want to print() each insect with a + // single method call here. +- ??? ++ insect.print(); + } + } + diff --git a/patches/patches/096_hello_c.patch b/patches/patches/096_hello_c.patch new file mode 100644 index 0000000..f5fe06f --- /dev/null +++ b/patches/patches/096_hello_c.patch @@ -0,0 +1,11 @@ +--- exercises/096_hello_c.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/096_hello_c.zig 2026-04-03 13:09:26.195163128 +0200 +@@ -54,7 +54,7 @@ + // + // In this exercise we use 'write' to output 17 chars, + // but something is still missing... +- const c_res = write(2, "Hello C from Zig!", 17); ++ const c_res = c.write(2, "Hello C from Zig!", 17); + + // let's see what the result from C is: + std.debug.print(" - C result is {d} chars written.\n", .{c_res}); diff --git a/patches/patches/096_memory_allocation.patch b/patches/patches/096_memory_allocation.patch deleted file mode 100644 index c26eeeb..0000000 --- a/patches/patches/096_memory_allocation.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/096_memory_allocation.zig 2023-11-21 14:55:33.805678390 +0100 -+++ answers/096_memory_allocation.zig 2023-11-21 14:56:00.236163484 +0100 -@@ -64,7 +64,7 @@ - const allocator = arena.allocator(); - - // allocate memory for this array -- const avg: []f64 = ???; -+ const avg: []f64 = try allocator.alloc(f64, arr.len); - - runningAverage(arr, avg); - std.debug.print("Running Average: ", .{}); diff --git a/patches/patches/097_bit_manipulation.patch b/patches/patches/097_bit_manipulation.patch deleted file mode 100644 index 19ba876..0000000 --- a/patches/patches/097_bit_manipulation.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/097_bit_manipulation.zig 2025-05-12 21:25:03.395385743 +0200 -+++ answers/097_bit_manipulation.zig 2025-05-12 21:22:57.472986976 +0200 -@@ -80,7 +80,7 @@ - y ^= x; - - // What must be written here? -- ???; -+ x ^= y; - - print("x = {b}; y = {b}\n", .{ x, y }); - } diff --git a/patches/patches/097_c_math.patch b/patches/patches/097_c_math.patch new file mode 100644 index 0000000..917fe6b --- /dev/null +++ b/patches/patches/097_c_math.patch @@ -0,0 +1,11 @@ +--- exercises/097_c_math.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/097_c_math.zig 2026-04-03 13:09:32.059278502 +0200 +@@ -26,7 +26,7 @@ + + const c = @cImport({ + // What do we need here? +- ??? ++ @cInclude("math.h"); + }); + + pub fn main() !void { diff --git a/patches/patches/098_bit_manipulation2.patch b/patches/patches/098_bit_manipulation2.patch deleted file mode 100644 index 5354a7b..0000000 --- a/patches/patches/098_bit_manipulation2.patch +++ /dev/null @@ -1,9 +0,0 @@ ---- exercises/098_bit_manipulation2.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/098_bit_manipulation2.zig 2023-10-05 20:04:07.286104520 +0200 -@@ -60,5 +60,5 @@ - // and if so, we know the given string is a pangram - // - // but what do we have to compare? -- return bits == 0x..???; -+ return bits == 0x3ffffff; - } diff --git a/patches/patches/098_for3.patch b/patches/patches/098_for3.patch new file mode 100644 index 0000000..c280bc6 --- /dev/null +++ b/patches/patches/098_for3.patch @@ -0,0 +1,20 @@ +--- exercises/098_for3.zig 2026-03-20 19:23:48.873150100 +0100 ++++ answers/098_for3.zig 2026-04-03 13:09:39.916433087 +0200 +@@ -56,7 +56,7 @@ + + // I want to print every number between 1 and 20 that is NOT + // divisible by 3 or 5. +- for (???) |n| { ++ for (1..21) |n| { + + // The '%' symbol is the "modulo" operator and it + // returns the remainder after division. +@@ -68,7 +68,7 @@ + std.debug.print("\n", .{}); + + // Let's also print every number from 1 through 15 +- for (???) |n| { ++ for (1..16) |n| { + std.debug.print("{} ", .{n}); + } + std.debug.print("\n", .{}); diff --git a/patches/patches/099_formatting.patch b/patches/patches/099_formatting.patch deleted file mode 100644 index a56b556..0000000 --- a/patches/patches/099_formatting.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/099_formatting.zig 2024-11-07 21:45:10.459123650 +0100 -+++ answers/099_formatting.zig 2024-11-07 21:43:55.154345991 +0100 -@@ -131,7 +131,7 @@ - for (0..size) |b| { - // What formatting is needed here to make our columns - // nice and straight? -- print("{???} ", .{(a + 1) * (b + 1)}); -+ print("{d:>3} ", .{(a + 1) * (b + 1)}); - } - - // After each row we use double line feed: diff --git a/patches/patches/099_memory_allocation.patch b/patches/patches/099_memory_allocation.patch new file mode 100644 index 0000000..d83a0ae --- /dev/null +++ b/patches/patches/099_memory_allocation.patch @@ -0,0 +1,11 @@ +--- exercises/099_memory_allocation.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/099_memory_allocation.zig 2026-04-03 13:09:47.403580391 +0200 +@@ -64,7 +64,7 @@ + const allocator = arena.allocator(); + + // allocate memory for this array +- const avg: []f64 = ???; ++ const avg: []f64 = try allocator.alloc(f64, arr.len); + + runningAverage(arr, avg); + std.debug.print("Running Average: ", .{}); diff --git a/patches/patches/100_bit_manipulation.patch b/patches/patches/100_bit_manipulation.patch new file mode 100644 index 0000000..f70cc41 --- /dev/null +++ b/patches/patches/100_bit_manipulation.patch @@ -0,0 +1,11 @@ +--- exercises/100_bit_manipulation.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/100_bit_manipulation.zig 2026-04-02 10:51:15.795831343 +0200 +@@ -80,7 +80,7 @@ + y ^= x; + + // What must be written here? +- ???; ++ x ^= y; + + print("x = {b}; y = {b}\n", .{ x, y }); + } diff --git a/patches/patches/100_for4.patch b/patches/patches/100_for4.patch deleted file mode 100644 index ad73e9a..0000000 --- a/patches/patches/100_for4.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/100_for4.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/100_for4.zig 2023-10-05 20:04:07.296104707 +0200 -@@ -39,7 +39,7 @@ - const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 }; - const dec_nums = [_]u8{ 11, 42, 119 }; - -- for (hex_nums, ???) |hn, ???| { -+ for (hex_nums, dec_nums) |hn, dn| { - if (hn != dn) { - print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); - return; diff --git a/patches/patches/101_bit_manipulation2.patch b/patches/patches/101_bit_manipulation2.patch new file mode 100644 index 0000000..5179272 --- /dev/null +++ b/patches/patches/101_bit_manipulation2.patch @@ -0,0 +1,9 @@ +--- exercises/101_bit_manipulation2.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/101_bit_manipulation2.zig 2026-04-02 10:51:15.797831382 +0200 +@@ -60,5 +60,5 @@ + // and if so, we know the given string is a pangram + // + // but what do we have to compare? +- return bits == 0x..???; ++ return bits == 0x3ffffff; + } diff --git a/patches/patches/101_for5.patch b/patches/patches/101_for5.patch deleted file mode 100644 index 00f19df..0000000 --- a/patches/patches/101_for5.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/101_for5.zig 2023-10-03 22:15:22.125574535 +0200 -+++ answers/101_for5.zig 2023-10-05 20:04:07.299438103 +0200 -@@ -51,7 +51,7 @@ - - // We would like to number our list starting with 1, not 0. - // How do we do that? -- for (roles, gold, experience, ???) |c, g, e, i| { -+ for (roles, gold, experience, 1..) |c, g, e, i| { - const role_name = switch (c) { - .wizard => "Wizard", - .thief => "Thief", diff --git a/patches/patches/102_formatting.patch b/patches/patches/102_formatting.patch new file mode 100644 index 0000000..dba8aef --- /dev/null +++ b/patches/patches/102_formatting.patch @@ -0,0 +1,11 @@ +--- exercises/102_formatting.zig 2026-03-20 19:23:48.873150100 +0100 ++++ answers/102_formatting.zig 2026-04-02 10:51:15.799831421 +0200 +@@ -131,7 +131,7 @@ + for (0..size) |b| { + // What formatting is needed here to make our columns + // nice and straight? +- print("{???} ", .{(a + 1) * (b + 1)}); ++ print("{d:>3} ", .{(a + 1) * (b + 1)}); + } + + // After each row we use double line feed: diff --git a/patches/patches/102_testing.patch b/patches/patches/102_testing.patch deleted file mode 100644 index 6d18bf6..0000000 --- a/patches/patches/102_testing.patch +++ /dev/null @@ -1,18 +0,0 @@ ---- exercises/102_testing.zig 2025-10-24 12:54:56 -+++ answers/102_testing.zig 2025-10-24 12:56:33 -@@ -71,7 +71,7 @@ - // The corresponding test is not much different from the previous one. Except - // that it contains an error that you need to correct. - test "sub" { -- try testing.expect(sub(10, 5) == 6); -+ try testing.expect(sub(10, 5) == 5); - - try testing.expect(sub(3, 1.5) == 1.5); - } -@@ -92,5 +92,5 @@ - - // Now we test if the function returns an error if we pass a zero as - // denominator. But which error needs to be tested? -- try testing.expectError(error.???, divide(15, 0)); -+ try testing.expectError(error.DivisionByZero, divide(15, 0)); - } diff --git a/patches/patches/103_for4.patch b/patches/patches/103_for4.patch new file mode 100644 index 0000000..7a7e161 --- /dev/null +++ b/patches/patches/103_for4.patch @@ -0,0 +1,11 @@ +--- exercises/103_for4.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/103_for4.zig 2026-04-02 10:51:15.801831460 +0200 +@@ -39,7 +39,7 @@ + const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 }; + const dec_nums = [_]u8{ 11, 42, 119 }; + +- for (hex_nums, ???) |hn, ???| { ++ for (hex_nums, dec_nums) |hn, dn| { + if (hn != dn) { + print("Uh oh! Found a mismatch: {d} vs {d}\n", .{ hn, dn }); + return; diff --git a/patches/patches/103_tokenization.patch b/patches/patches/103_tokenization.patch deleted file mode 100644 index 941ca3b..0000000 --- a/patches/patches/103_tokenization.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- exercises/103_tokenization.zig 2026-02-27 19:25:11 -+++ answers/103_tokenization.zig 2026-02-27 19:26:04 -@@ -134,7 +134,7 @@ - ; - - // now the tokenizer, but what do we need here? -- var it = std.mem.tokenizeAny(u8, poem, ???); -+ var it = std.mem.tokenizeAny(u8, poem, " ,;!\n"); - - // print all words and count them - var cnt: usize = 0; diff --git a/patches/patches/104_for5.patch b/patches/patches/104_for5.patch new file mode 100644 index 0000000..ef77013 --- /dev/null +++ b/patches/patches/104_for5.patch @@ -0,0 +1,11 @@ +--- exercises/104_for5.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/104_for5.zig 2026-04-02 10:51:15.803831499 +0200 +@@ -51,7 +51,7 @@ + + // We would like to number our list starting with 1, not 0. + // How do we do that? +- for (roles, gold, experience, ???) |c, g, e, i| { ++ for (roles, gold, experience, 1..) |c, g, e, i| { + const role_name = switch (c) { + .wizard => "Wizard", + .thief => "Thief", diff --git a/patches/patches/104_threading.patch b/patches/patches/104_threading.patch deleted file mode 100644 index e6fe0f4..0000000 --- a/patches/patches/104_threading.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- exercises/104_threading.zig 2026-04-01 23:31:10.073198955 +0200 -+++ answers/104_threading.zig 2026-04-01 23:29:51.314585919 +0200 -@@ -88,12 +88,12 @@ - defer handle.join(); - - // Second thread -- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right? -+ const handle2 = try std.Thread.spawn(.{}, thread_function, .{2}); - defer handle2.join(); - - // Third thread - const handle3 = try std.Thread.spawn(.{}, thread_function, .{3}); -- defer ??? // <-- something is missing -+ defer handle3.join(); - - // After the threads have been started, - // they run in parallel and we can still do some work in between. diff --git a/patches/patches/105_testing.patch b/patches/patches/105_testing.patch new file mode 100644 index 0000000..069b72b --- /dev/null +++ b/patches/patches/105_testing.patch @@ -0,0 +1,18 @@ +--- exercises/105_testing.zig 2026-03-20 19:23:48.873150100 +0100 ++++ answers/105_testing.zig 2026-04-02 10:51:15.805831538 +0200 +@@ -71,7 +71,7 @@ + // The corresponding test is not much different from the previous one. Except + // that it contains an error that you need to correct. + test "sub" { +- try testing.expect(sub(10, 5) == 6); ++ try testing.expect(sub(10, 5) == 5); + + try testing.expect(sub(3, 1.5) == 1.5); + } +@@ -92,5 +92,5 @@ + + // Now we test if the function returns an error if we pass a zero as + // denominator. But which error needs to be tested? +- try testing.expectError(error.???, divide(15, 0)); ++ try testing.expectError(error.DivisionByZero, divide(15, 0)); + } diff --git a/patches/patches/105_threading2.patch b/patches/patches/105_threading2.patch deleted file mode 100644 index dfa5613..0000000 --- a/patches/patches/105_threading2.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- exercises/105_threading2.zig 2024-03-23 16:35:14.754540802 +0100 -+++ answers/105_threading2.zig 2024-03-23 16:38:00.577539733 +0100 -@@ -81,8 +81,8 @@ - defer handle1.join(); - - // Second thread to calculate the minus numbers. -- ??? -- -+ const handle2 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_minus, 3, count }); -+ defer handle2.join(); - } - // Here we add up the results. - std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus}); diff --git a/patches/patches/106_files.patch b/patches/patches/106_files.patch deleted file mode 100644 index 27bcb56..0000000 --- a/patches/patches/106_files.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- exercises/106_files.zig 2026-01-09 22:41:19.373872684 +0100 -+++ answers/106_files.zig 2026-01-09 22:41:44.518372910 +0100 -@@ -41,7 +41,7 @@ - // by doing nothing - // - // we want to catch error.PathAlreadyExists and do nothing -- ??? => {}, -+ error.PathAlreadyExists => {}, - // if there's any other unexpected error we just propagate it through - else => return e, - }; -@@ -61,7 +61,7 @@ - // but here we are not yet done writing to the file - // if only there were a keyword in Zig that - // allowed you to "defer" code execution to the end of the scope... -- file.close(io); -+ defer file.close(io); - - // you are not allowed to move these lines above the file closing line! - var file_writer = file.writer(io, &.{}); diff --git a/patches/patches/106_tokenization.patch b/patches/patches/106_tokenization.patch new file mode 100644 index 0000000..4792df6 --- /dev/null +++ b/patches/patches/106_tokenization.patch @@ -0,0 +1,11 @@ +--- exercises/106_tokenization.zig 2026-03-20 19:23:48.873150100 +0100 ++++ answers/106_tokenization.zig 2026-04-02 10:51:15.807831578 +0200 +@@ -134,7 +134,7 @@ + ; + + // now the tokenizer, but what do we need here? +- var it = std.mem.tokenizeAny(u8, poem, ???); ++ var it = std.mem.tokenizeAny(u8, poem, " ,;!\n"); + + // print all words and count them + var cnt: usize = 0; diff --git a/patches/patches/107_files2.patch b/patches/patches/107_files2.patch deleted file mode 100644 index 6820742..0000000 --- a/patches/patches/107_files2.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- exercises/107_files2.zig 2026-01-09 22:43:15.211177186 +0100 -+++ answers/107_files2.zig 2026-01-09 22:42:48.943654602 +0100 -@@ -39,7 +39,7 @@ - // initialize an array of u8 with all letter 'A' - // we need to pick the size of the array, 64 seems like a good number - // fix the initialization below -- var content = ['A']*64; -+ var content = [_]u8{'A'} ** 64; - // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA` - std.debug.print("{s}\n", .{content}); - -@@ -50,12 +50,12 @@ - // can you go here to find a way to read the content? - // https://ziglang.org/documentation/master/std/#std.Io.Reader - // hint: look for a method that reads into a slice -- const bytes_read = zig_read_the_file_or_i_will_fight_you(&content); -+ const bytes_read = try reader.readSliceShort(&content); - - // Woah, too screamy. I know you're excited for zigling time but tone it down a bit. - // Can you print only what we read from the file? - std.debug.print("Successfully Read {d} bytes: {s}\n", .{ - bytes_read, -- content, // change this line only -+ content[0..bytes_read], // change this line only - }); - } diff --git a/patches/patches/107_threading.patch b/patches/patches/107_threading.patch new file mode 100644 index 0000000..3f65ef4 --- /dev/null +++ b/patches/patches/107_threading.patch @@ -0,0 +1,17 @@ +--- exercises/107_threading.zig 2026-04-01 23:31:10.073198955 +0200 ++++ answers/107_threading.zig 2026-04-02 10:51:15.809831617 +0200 +@@ -88,12 +88,12 @@ + defer handle.join(); + + // Second thread +- const handle2 = try std.Thread.spawn(.{}, thread_function, .{-4}); // that can't be right? ++ const handle2 = try std.Thread.spawn(.{}, thread_function, .{2}); + defer handle2.join(); + + // Third thread + const handle3 = try std.Thread.spawn(.{}, thread_function, .{3}); +- defer ??? // <-- something is missing ++ defer handle3.join(); + + // After the threads have been started, + // they run in parallel and we can still do some work in between. diff --git a/patches/patches/108_labeled_switch.patch b/patches/patches/108_labeled_switch.patch deleted file mode 100644 index fa9dd67..0000000 --- a/patches/patches/108_labeled_switch.patch +++ /dev/null @@ -1,18 +0,0 @@ ---- exercises/108_labeled_switch.zig 2024-09-20 12:09:24.370066539 +0200 -+++ answers/108_labeled_switch.zig 2024-09-20 12:09:06.499711739 +0200 -@@ -65,13 +65,13 @@ - // how would you fix it? - pr: switch (PullRequestState.Draft) { - PullRequestState.Draft => continue :pr PullRequestState.InReview, -- PullRequestState.InReview => continue :pr PullRequestState.Rejected, -+ PullRequestState.InReview => continue :pr PullRequestState.Approved, - PullRequestState.Approved => continue :pr PullRequestState.Merged, - PullRequestState.Rejected => { - std.debug.print("The pull request has been rejected.\n", .{}); - return; - }, -- PullRequestState.Merged => break, // Would you know where to break to? -+ PullRequestState.Merged => break :pr, // Would you know where to break to? - } - std.debug.print("The pull request has been merged.\n", .{}); - } diff --git a/patches/patches/108_threading2.patch b/patches/patches/108_threading2.patch new file mode 100644 index 0000000..d71f254 --- /dev/null +++ b/patches/patches/108_threading2.patch @@ -0,0 +1,13 @@ +--- exercises/108_threading2.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/108_threading2.zig 2026-04-02 10:51:15.811831656 +0200 +@@ -81,8 +81,8 @@ + defer handle1.join(); + + // Second thread to calculate the minus numbers. +- ??? +- ++ const handle2 = try std.Thread.spawn(.{}, thread_pi, .{ &pi_minus, 3, count }); ++ defer handle2.join(); + } + // Here we add up the results. + std.debug.print("PI ≈ {d:.8}\n", .{4 + pi_plus - pi_minus}); diff --git a/patches/patches/109_files.patch b/patches/patches/109_files.patch new file mode 100644 index 0000000..ac59d70 --- /dev/null +++ b/patches/patches/109_files.patch @@ -0,0 +1,20 @@ +--- exercises/109_files.zig 2026-03-20 19:23:48.874150121 +0100 ++++ answers/109_files.zig 2026-04-02 10:51:15.813831695 +0200 +@@ -41,7 +41,7 @@ + // by doing nothing + // + // we want to catch error.PathAlreadyExists and do nothing +- ??? => {}, ++ error.PathAlreadyExists => {}, + // if there's any other unexpected error we just propagate it through + else => return e, + }; +@@ -61,7 +61,7 @@ + // but here we are not yet done writing to the file + // if only there were a keyword in Zig that + // allowed you to "defer" code execution to the end of the scope... +- file.close(io); ++ defer file.close(io); + + // you are not allowed to move these lines above the file closing line! + var file_writer = file.writer(io, &.{}); diff --git a/patches/patches/109_vectors.patch b/patches/patches/109_vectors.patch deleted file mode 100644 index bf18cc0..0000000 --- a/patches/patches/109_vectors.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- exercises/109_vectors.zig 2024-11-07 14:57:09.673383618 +0100 -+++ answers/109_vectors.zig 2024-11-07 14:22:59.069150138 +0100 -@@ -121,8 +121,8 @@ - - const Vec4 = @Vector(4, f32); - fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 { -- const abs_diff_vec = ???; -- const max_diff = @reduce(???, abs_diff_vec); -+ const abs_diff_vec = @abs(a - b); -+ const max_diff = @reduce(.Max, abs_diff_vec); - return max_diff; - } - diff --git a/patches/patches/110_files2.patch b/patches/patches/110_files2.patch new file mode 100644 index 0000000..64dc193 --- /dev/null +++ b/patches/patches/110_files2.patch @@ -0,0 +1,26 @@ +--- exercises/110_files2.zig 2026-03-20 19:23:48.874150121 +0100 ++++ answers/110_files2.zig 2026-04-02 10:51:15.815831734 +0200 +@@ -39,7 +39,7 @@ + // initialize an array of u8 with all letter 'A' + // we need to pick the size of the array, 64 seems like a good number + // fix the initialization below +- var content = ['A']*64; ++ var content = [_]u8{'A'} ** 64; + // this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA` + std.debug.print("{s}\n", .{content}); + +@@ -50,12 +50,12 @@ + // can you go here to find a way to read the content? + // https://ziglang.org/documentation/master/std/#std.Io.Reader + // hint: look for a method that reads into a slice +- const bytes_read = zig_read_the_file_or_i_will_fight_you(&content); ++ const bytes_read = try reader.readSliceShort(&content); + + // Woah, too screamy. I know you're excited for zigling time but tone it down a bit. + // Can you print only what we read from the file? + std.debug.print("Successfully Read {d} bytes: {s}\n", .{ + bytes_read, +- content, // change this line only ++ content[0..bytes_read], // change this line only + }); + } diff --git a/patches/patches/110_quiz9.patch b/patches/patches/110_quiz9.patch deleted file mode 100644 index 9d9b864..0000000 --- a/patches/patches/110_quiz9.patch +++ /dev/null @@ -1,56 +0,0 @@ ---- exercises/110_quiz9.zig 2025-02-08 13:19:48.522641785 -0800 -+++ answers/110_quiz9.zig 2025-02-10 17:42:04.525004335 -0800 -@@ -108,7 +108,7 @@ - PORTB = 0b1100; - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("^ {b:0>4} // (bitmask)\n", .{0b0101}); -- PORTB ^= (1 << 1) | (1 << 0); // What's wrong here? -+ PORTB ^= (1 << 2) | (1 << 0); - checkAnswer(0b1001, PORTB); - - newline(); -@@ -116,7 +116,7 @@ - PORTB = 0b1100; - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("^ {b:0>4} // (bitmask)\n", .{0b0011}); -- PORTB ^= (1 << 1) & (1 << 0); // What's wrong here? -+ PORTB ^= (1 << 1) | (1 << 0); - checkAnswer(0b1111, PORTB); - - newline(); -@@ -170,7 +170,7 @@ - PORTB = 0b1001; // reset PORTB - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("| {b:0>4} // (bitmask)\n", .{0b0100}); -- PORTB = PORTB ??? (1 << 2); // What's missing here? -+ PORTB = PORTB | (1 << 2); - checkAnswer(0b1101, PORTB); - - newline(); -@@ -178,7 +178,7 @@ - PORTB = 0b1001; // reset PORTB - print(" {b:0>4} // (reset state)\n", .{PORTB}); - print("| {b:0>4} // (bitmask)\n", .{0b0100}); -- PORTB ??? (1 << 2); // What's missing here? -+ PORTB |= (1 << 2); - checkAnswer(0b1101, PORTB); - - newline(); -@@ -269,7 +269,7 @@ - PORTB = 0b1110; // reset PORTB - print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); - print("& {b:0>4} // (bitmask)\n", .{0b1011}); -- PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here? -+ PORTB = PORTB & ~@as(u4, 1 << 2); - checkAnswer(0b1010, PORTB); - - newline(); -@@ -277,7 +277,7 @@ - PORTB = 0b0111; // reset PORTB - print(" {b:0>4} // (reset state)\n", .{PORTB}); - print("& {b:0>4} // (bitmask)\n", .{0b1110}); -- PORTB &= ~(1 << 0); // What's missing here? -+ PORTB &= ~@as(u4, 1 << 0); - checkAnswer(0b0110, PORTB); - - newline(); diff --git a/patches/patches/111_labeled_switch.patch b/patches/patches/111_labeled_switch.patch new file mode 100644 index 0000000..0fb872d --- /dev/null +++ b/patches/patches/111_labeled_switch.patch @@ -0,0 +1,18 @@ +--- exercises/111_labeled_switch.zig 2025-08-15 15:17:57.840348083 +0200 ++++ answers/111_labeled_switch.zig 2026-04-02 10:51:15.817831773 +0200 +@@ -67,13 +67,13 @@ + // how would you fix it? + pr: switch (PullRequestState.Draft) { + PullRequestState.Draft => continue :pr PullRequestState.InReview, +- PullRequestState.InReview => continue :pr PullRequestState.Rejected, ++ PullRequestState.InReview => continue :pr PullRequestState.Approved, + PullRequestState.Approved => continue :pr PullRequestState.Merged, + PullRequestState.Rejected => { + std.debug.print("The pull request has been rejected.\n", .{}); + return; + }, +- PullRequestState.Merged => break, // Would you know where to break to? ++ PullRequestState.Merged => break :pr, // Would you know where to break to? + } + std.debug.print("The pull request has been merged.\n", .{}); + } diff --git a/patches/patches/111_packed.patch b/patches/patches/111_packed.patch deleted file mode 100644 index d38ac68..0000000 --- a/patches/patches/111_packed.patch +++ /dev/null @@ -1,57 +0,0 @@ ---- exercises/111_packed.zig 2026-03-13 11:18:44 -+++ answers/111_packed.zig 2026-03-13 11:18:57 -@@ -41,7 +41,7 @@ - - const PackedStruct = packed struct { - a: u2, -- b: u?, -+ b: u4, - }; - - comptime { -@@ -50,7 +50,7 @@ - - const PackedUnion = packed union { - a: bool, -- b: u?, -+ b: u1, - }; - - comptime { -@@ -113,31 +113,31 @@ - pub fn main() void { - { - const expected: Bits = @bitCast(@as(u4, 0b1000)); -- const my_bits: Bits = .{}; -+ const my_bits: Bits = .{ .d = 1 }; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0001)); -- const my_bits: Bits = .{}; -+ const my_bits: Bits = .{ .a = 1 }; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0010)); -- const my_bits: Bits = .{}; -+ const my_bits: Bits = .{ .b = 1 }; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0011)); -- const my_bits: Bits = .{}; -+ const my_bits: Bits = .{ .a = 1, .b = 1 }; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b1101)); -- const my_bits: Bits = .{}; -+ const my_bits: Bits = .{ .a = 1, .c = 1, .d = 1 }; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - } diff --git a/patches/patches/112_packed2.patch b/patches/patches/112_packed2.patch deleted file mode 100644 index 9b01eb3..0000000 --- a/patches/patches/112_packed2.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- exercises/112_packed2.zig 2026-03-13 11:14:08 -+++ answers/112_packed2.zig 2026-03-13 11:14:16 -@@ -13,9 +13,9 @@ - const s: S = .{ .a = true, .b = -1 }; - switch (s) { - .{ .a = true, .b = -1 } => {}, // ok! -- .{ .a = true, .b = ??? }, -- .{ .a = ???, .b = 0 }, -- .{ .a = ???, .b = ??? }, -+ .{ .a = true, .b = 0 }, -+ .{ .a = false, .b = 0 }, -+ .{ .a = false, .b = -1 }, - => @compileError("We don't want to end up here!"), - } - } -@@ -39,7 +39,6 @@ - .{ .a = 3 } => {}, // ok! - .{ .a = 2 }, - .{ .b = 1 }, -- .{ .b = -1 }, - .{ .a = 0 }, - => @compileError("We don't want to end up here!"), - } -@@ -65,7 +64,7 @@ - // Reminder: if the sign bit of a float is set, the number is negative! - - var number: Float = .{ .value = 2.34 }; -- number.bits.??? = ???; -+ number.bits.sign = 1; - if (number.value != -2.34) { - std.debug.print("Make it negative!\n", .{}); - } diff --git a/patches/patches/112_vectors.patch b/patches/patches/112_vectors.patch new file mode 100644 index 0000000..5bf684b --- /dev/null +++ b/patches/patches/112_vectors.patch @@ -0,0 +1,13 @@ +--- exercises/112_vectors.zig 2025-08-15 15:17:57.840348083 +0200 ++++ answers/112_vectors.zig 2026-04-02 10:51:15.819831812 +0200 +@@ -121,8 +121,8 @@ + + const Vec4 = @Vector(4, f32); + fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 { +- const abs_diff_vec = ???; +- const max_diff = @reduce(???, abs_diff_vec); ++ const abs_diff_vec = @abs(a - b); ++ const max_diff = @reduce(.Max, abs_diff_vec); + return max_diff; + } + diff --git a/patches/patches/113_quiz9.patch b/patches/patches/113_quiz9.patch new file mode 100644 index 0000000..03bd572 --- /dev/null +++ b/patches/patches/113_quiz9.patch @@ -0,0 +1,56 @@ +--- exercises/113_quiz9.zig 2025-08-15 15:17:57.840348083 +0200 ++++ answers/113_quiz9.zig 2026-04-02 10:51:15.821831851 +0200 +@@ -84,7 +84,7 @@ + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0101}); +- PORTB ^= (1 << 1) | (1 << 0); // What's wrong here? ++ PORTB ^= (1 << 2) | (1 << 0); + checkAnswer(0b1001, PORTB); + + newline(); +@@ -92,7 +92,7 @@ + PORTB = 0b1100; + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("^ {b:0>4} // (bitmask)\n", .{0b0011}); +- PORTB ^= (1 << 1) & (1 << 0); // What's wrong here? ++ PORTB ^= (1 << 1) | (1 << 0); + checkAnswer(0b1111, PORTB); + + newline(); +@@ -103,7 +103,7 @@ + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); +- PORTB = PORTB ??? (1 << 2); // What's missing here? ++ PORTB = PORTB | (1 << 2); + checkAnswer(0b1101, PORTB); + + newline(); +@@ -111,7 +111,7 @@ + PORTB = 0b1001; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("| {b:0>4} // (bitmask)\n", .{0b0100}); +- PORTB ??? (1 << 2); // What's missing here? ++ PORTB |= (1 << 2); + checkAnswer(0b1101, PORTB); + + newline(); +@@ -122,7 +122,7 @@ + PORTB = 0b1110; // reset PORTB + print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1011}); +- PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here? ++ PORTB = PORTB & ~@as(u4, 1 << 2); + checkAnswer(0b1010, PORTB); + + newline(); +@@ -130,7 +130,7 @@ + PORTB = 0b0111; // reset PORTB + print(" {b:0>4} // (reset state)\n", .{PORTB}); + print("& {b:0>4} // (bitmask)\n", .{0b1110}); +- PORTB &= ~(1 << 0); // What's missing here? ++ PORTB &= ~@as(u4, 1 << 0); + checkAnswer(0b0110, PORTB); + + newline(); diff --git a/patches/patches/114_packed.patch b/patches/patches/114_packed.patch new file mode 100644 index 0000000..3c79f18 --- /dev/null +++ b/patches/patches/114_packed.patch @@ -0,0 +1,57 @@ +--- exercises/114_packed.zig 2026-03-20 19:23:48.874150121 +0100 ++++ answers/114_packed.zig 2026-04-02 10:51:15.824831910 +0200 +@@ -41,7 +41,7 @@ + + const PackedStruct = packed struct { + a: u2, +- b: u?, ++ b: u4, + }; + + comptime { +@@ -50,7 +50,7 @@ + + const PackedUnion = packed union { + a: bool, +- b: u?, ++ b: u1, + }; + + comptime { +@@ -113,31 +113,31 @@ + pub fn main() void { + { + const expected: Bits = @bitCast(@as(u4, 0b1000)); +- const my_bits: Bits = .{}; ++ const my_bits: Bits = .{ .d = 1 }; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b0001)); +- const my_bits: Bits = .{}; ++ const my_bits: Bits = .{ .a = 1 }; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b0010)); +- const my_bits: Bits = .{}; ++ const my_bits: Bits = .{ .b = 1 }; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b0011)); +- const my_bits: Bits = .{}; ++ const my_bits: Bits = .{ .a = 1, .b = 1 }; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + + { + const expected: Bits = @bitCast(@as(u4, 0b1101)); +- const my_bits: Bits = .{}; ++ const my_bits: Bits = .{ .a = 1, .c = 1, .d = 1 }; + if (my_bits != expected) complain(my_bits, expected, @src()); + } + } diff --git a/patches/patches/115_packed2.patch b/patches/patches/115_packed2.patch new file mode 100644 index 0000000..81c32ab --- /dev/null +++ b/patches/patches/115_packed2.patch @@ -0,0 +1,32 @@ +--- exercises/115_packed2.zig 2026-03-20 19:23:48.874150121 +0100 ++++ answers/115_packed2.zig 2026-04-02 10:51:15.826831949 +0200 +@@ -13,9 +13,9 @@ + const s: S = .{ .a = true, .b = -1 }; + switch (s) { + .{ .a = true, .b = -1 } => {}, // ok! +- .{ .a = true, .b = ??? }, +- .{ .a = ???, .b = 0 }, +- .{ .a = ???, .b = ??? }, ++ .{ .a = true, .b = 0 }, ++ .{ .a = false, .b = 0 }, ++ .{ .a = false, .b = -1 }, + => @compileError("We don't want to end up here!"), + } + } +@@ -39,7 +39,6 @@ + .{ .a = 3 } => {}, // ok! + .{ .a = 2 }, + .{ .b = 1 }, +- .{ .b = -1 }, + .{ .a = 0 }, + => @compileError("We don't want to end up here!"), + } +@@ -65,7 +64,7 @@ + // Reminder: if the sign bit of a float is set, the number is negative! + + var number: Float = .{ .value = 2.34 }; +- number.bits.??? = ???; ++ number.bits.sign = 1; + if (number.value != -2.34) { + std.debug.print("Make it negative!\n", .{}); + } -- cgit v1.2.3