YIAN7Z2GNJPLAEXUSSJNSOG64GPUDGUYBB77WZKDOH42AMXACDZAC
const std = @import("std");
const fetch = @import("fetch");
const Str = []const u8;
const opts = fetch.Options{
.year = 2024,
.days = 1,
};
const aoc2024 = struct {
const day01 = @import("day01.zig");
};
test {
_ = @import("day01.zig");
}
pub fn main() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
if (!fetch.checkInputs(opts.days)) {
try fetch.fetchInputs(allocator, opts);
}
try solveAll(allocator);
}
fn solveAll(allocator: std.mem.Allocator) anyerror!void {
var timer = try std.time.Timer.start();
var time_sum: usize = 0;
var buffer = std.io.bufferedWriter(std.io.getStdOut().writer());
const stdout = buffer.writer();
try stdout.print("{s:<8}{s:>45}{s:>20}\n", .{ "Day", "Result", "Time" });
comptime var day: u5 = 0;
inline while (day < opts.days) : (day += 1) {
comptime var buf: [5]u8 = undefined;
const day_str = comptime try std.fmt.bufPrint(&buf, "day{d:0>2}", .{day + 1});
timer.reset();
const first_res = @field(aoc2024, day_str).first(allocator) catch unreachable;
const first_time = timer.read() / std.time.ns_per_us;
time_sum += first_time;
{
const runtime_buffer = buf; // comptime -> runtime hack
switch (@TypeOf(first_res)) {
Str => try stdout.print("{s}a: {s:>45}{d:>20} us\n", .{ runtime_buffer, first_res, first_time }),
usize, isize => try stdout.print("{s}a: {d:>45}{d:>20} us\n", .{ runtime_buffer, first_res, first_time }),
else => try stdout.print("{s}a: {s:>45}{d:>20} us\n", .{ runtime_buffer, "", first_time }),
}
}
if (day < 24) {
timer.reset();
const second_res = @field(aoc2024, day_str).second(allocator) catch unreachable;
const second_time = timer.read() / std.time.ns_per_us;
time_sum += second_time;
{
const runtime_buffer = buf; // comptime -> runtime hack
switch (@TypeOf(second_res)) {
Str => try stdout.print("{s}b: {s:>45}{d:>20} us\n", .{ runtime_buffer, second_res, second_time }),
usize, isize => try stdout.print("{s}b: {d:>45}{d:>20} us\n", .{ runtime_buffer, second_res, second_time }),
else => try stdout.print("{s}b: {s:>45}{d:>20} us\n", .{ runtime_buffer, "", second_time }),
}
}
}
}
try stdout.print("Total time: {d} ms\n", .{time_sum / std.time.us_per_ms});
try buffer.flush();
}
const std = @import("std");
const INPUTS_DIR = "inputs";
const SESSION_FILE = "session.txt";
const MAX_INPUT_SIZE = 512 * 512; // Maximum allowed input size (bytes)
pub const Options = struct {
year: usize,
days: usize,
};
pub fn fetchInputs(allocator: std.mem.Allocator, opts: Options) !void {
const cwd = std.fs.cwd();
cwd.makeDir(INPUTS_DIR) catch |err| {
switch (err) {
error.PathAlreadyExists => {},
else => return err,
}
};
const session = cwd.readFileAlloc(allocator, SESSION_FILE, 256) catch |err| {
std.log.err("Unable to open '{s}'.\nMake sure to copy the session cookie value to this file to download the user specific input files.", .{SESSION_FILE});
return err;
};
defer allocator.free(session);
for (1..opts.days + 1) |day| {
const file_name = try std.fmt.allocPrint(allocator, "{s}/day{d:0>2}.txt", .{ INPUTS_DIR, day });
defer allocator.free(file_name);
const fd = cwd.openFile(file_name, std.fs.File.OpenFlags{}) catch |err| {
switch (err) {
error.FileNotFound => {
try fetchDay(allocator, opts, session, day);
continue;
},
else => return err,
}
};
defer fd.close();
}
}
fn fetchDay(allocator: std.mem.Allocator, opts: Options, session: []const u8, day: usize) !void {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
std.debug.print("Downloading day{d:0>2}\r", .{day});
const url = try std.fmt.allocPrint(
allocator,
"https://adventofcode.com/{d}/day/{d}/input",
.{ opts.year, day },
);
defer allocator.free(url);
const uri = try std.Uri.parse(url);
var headers = std.ArrayList(std.http.Header).init(allocator);
defer headers.deinit();
try headers.append(.{
.name = "Cookie",
.value = try std.fmt.allocPrint(allocator, "session={s}", .{session}),
});
defer {
for (headers.items) |header| {
allocator.free(header.value);
}
}
var sh: [512]u8 = undefined;
var req = try client.open(.GET, uri, .{
.server_header_buffer = &sh,
.extra_headers = headers.items,
});
defer req.deinit();
try req.send();
try req.finish();
try req.wait();
const body = try req.reader().readAllAlloc(allocator, MAX_INPUT_SIZE);
defer allocator.free(body);
if (req.response.status != std.http.Status.ok) {
std.log.err("{}", .{req.response.status});
std.log.err("{s}", .{body});
return error.HttpRequestError;
}
const file_name = try std.fmt.allocPrint(allocator, "{s}/day{d:0>2}.txt", .{ INPUTS_DIR, day });
defer allocator.free(file_name);
try std.fs.cwd().writeFile(std.fs.Dir.WriteFileOptions{
.sub_path = file_name,
.data = body,
.flags = .{ .mode = 0o644 },
});
}
/// Checks if `INPUTS_DIR` exists and all the input files are available.
pub fn checkInputs(days: usize) bool {
const cwd = std.fs.cwd();
const inputs = cwd.openDir(INPUTS_DIR, std.fs.Dir.OpenDirOptions{
.iterate = true,
}) catch {
return false;
};
var counter: usize = 0;
var it = inputs.iterate();
while (it.next() catch unreachable) |_| : (counter += 1) {}
if (counter == days) return true else return false;
}
.{
.name = "aoc2024",
.version = "0.0.0",
.minimum_zig_version = "0.13.0",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const fetch_module = b.createModule(std.Build.Module.CreateOptions{
.root_source_file = std.Build.LazyPath{
.src_path = .{ .owner = b, .sub_path = "fetch.zig" },
},
});
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(std.Build.ExecutableOptions{
.name = "aoc2024",
.root_source_file = std.Build.LazyPath{
.src_path = .{ .owner = b, .sub_path = "src/main.zig" },
},
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("fetch", fetch_module);
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run all solver");
run_step.dependOn(&run_cmd.step);
const exe_tests = b.addTest(std.Build.TestOptions{
.root_source_file = std.Build.LazyPath{
.src_path = .{ .owner = b, .sub_path = "src/main.zig" },
},
.target = target,
.optimize = optimize,
});
const test_cmd = b.addRunArtifact(exe_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&test_cmd.step);
}