Skip to content

Commit d4c865c

Browse files
committed
[2024] Added Day 09: Disk Fragmenter
1 parent 054f0ad commit d4c865c

File tree

10 files changed

+365
-0
lines changed

10 files changed

+365
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const std = @import("std");
2+
const zbench = @import("zbench");
3+
const disk_fragmenter = @import("disk_fragmenter");
4+
5+
const puzzle_input = @embedFile("puzzle_input");
6+
7+
// Benchmark of part 1
8+
fn task_1(_: std.mem.Allocator) void {
9+
_ = disk_fragmenter.checksum_of_fragmented_disk(puzzle_input) catch {};
10+
}
11+
12+
// Benchmark of part 2
13+
fn task_2(_: std.mem.Allocator) void {
14+
_ = disk_fragmenter.checksum_of_defragmented_disk(puzzle_input) catch {};
15+
}
16+
17+
pub fn main() !void {
18+
const stdout = std.io.getStdOut().writer();
19+
var bench = zbench.Benchmark.init(std.heap.page_allocator, .{});
20+
defer bench.deinit();
21+
22+
try bench.add("Day 09 - Task 1", task_1, .{});
23+
try bench.add("Day 09 - Task 2", task_2, .{});
24+
25+
try stdout.writeAll("\n");
26+
try bench.run(stdout);
27+
}

2024/09/disk_fragmenter/build.zig

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.Build) void {
4+
const target = b.standardTargetOptions(.{});
5+
const optimize = b.standardOptimizeOption(.{});
6+
7+
// -------------------------- Solution module --------------------------- \\
8+
const disk_fragmenter = b.addModule("disk_fragmenter", .{
9+
.root_source_file = b.path("src/disk_fragmenter.zig"),
10+
});
11+
12+
// -------------------------- Main executable --------------------------- \\
13+
const disk_fragmenter_exe = b.addExecutable(.{
14+
.name = "disk_fragmenter",
15+
.root_source_file = b.path("src/main.zig"),
16+
.target = target,
17+
.optimize = optimize,
18+
});
19+
20+
const yazap = b.dependency("yazap", .{});
21+
disk_fragmenter_exe.root_module.addImport("yazap", yazap.module("yazap"));
22+
disk_fragmenter_exe.root_module.addImport("disk_fragmenter", disk_fragmenter);
23+
b.installArtifact(disk_fragmenter_exe);
24+
25+
const run_cmd = b.addRunArtifact(disk_fragmenter_exe);
26+
run_cmd.step.dependOn(b.getInstallStep());
27+
if (b.args) |args| {
28+
run_cmd.addArgs(args);
29+
}
30+
31+
const run_step = b.step("run", "Run the disk_fragmenter (day 09) app");
32+
run_step.dependOn(&run_cmd.step);
33+
34+
// --------------------------- Example tests ---------------------------- \\
35+
const disk_fragmenter_tests = b.addTest(.{
36+
.name = "disk_fragmenter_tests",
37+
.root_source_file = b.path("tests/example_tests.zig"),
38+
.target = target,
39+
.optimize = optimize,
40+
});
41+
42+
disk_fragmenter_tests.root_module.addImport("disk_fragmenter", disk_fragmenter);
43+
disk_fragmenter_tests.root_module.addAnonymousImport("example_input", .{
44+
.root_source_file = b.path("input/example_input.txt"),
45+
});
46+
b.installArtifact(disk_fragmenter_tests);
47+
48+
const test_step = b.step("test", "Run disk_fragmenter (day 09) tests");
49+
test_step.dependOn(&b.addRunArtifact(disk_fragmenter_tests).step);
50+
51+
// ------------------------- Puzzle benchmarks -------------------------- \\
52+
const disk_fragmenter_benchmarks = b.addExecutable(.{
53+
.name = "disk_fragmenter_benchmarks",
54+
.root_source_file = b.path("benchmarks/puzzle_benchmarks.zig"),
55+
.target = target,
56+
.optimize = optimize,
57+
});
58+
59+
const zbench = b.dependency("zbench", .{
60+
.target = target,
61+
.optimize = optimize,
62+
});
63+
disk_fragmenter_benchmarks.root_module.addImport("zbench", zbench.module("zbench"));
64+
disk_fragmenter_benchmarks.root_module.addImport("disk_fragmenter", disk_fragmenter);
65+
disk_fragmenter_benchmarks.root_module.addAnonymousImport("puzzle_input", .{
66+
.root_source_file = b.path("input/puzzle_input.txt"),
67+
});
68+
b.installArtifact(disk_fragmenter_benchmarks);
69+
70+
const benchmark_step = b.step("benchmark", "Run disk_fragmenter (day 09) benchmarks");
71+
benchmark_step.dependOn(&b.addRunArtifact(disk_fragmenter_benchmarks).step);
72+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.{
2+
.name = "disk_fragmenter",
3+
.version = "0.1.0",
4+
.minimum_zig_version = "0.13.0",
5+
.dependencies = .{
6+
.yazap = .{
7+
.url = "git+https://github.com/prajwalch/yazap#c2e3122d5dd6192513ba590f229dbc535110efb8",
8+
.hash = "122054439ec36ac10987c87ae69f3b041b40b2e451af3fe3ef1fc578b3bad756a800",
9+
},
10+
.zbench = .{
11+
.url = "git+https://github.com/hendriknielaender/zBench#fb3ecae5d035091fd2392a2ec21970c06fc375fa",
12+
.hash = "122095b73930ff5d627429295c669905d85bb9b54394ddc185ad2d61295cc65819e5",
13+
},
14+
},
15+
.paths = .{
16+
"build.zig",
17+
"build.zig.zon",
18+
"src",
19+
"input",
20+
"tests",
21+
"benchmarks",
22+
},
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2333133121414131402

2024/09/disk_fragmenter/input/puzzle_input.txt

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
const std = @import("std");
2+
const Allocator = std.mem.Allocator;
3+
const ArrayList = std.ArrayList;
4+
const string = []const u8;
5+
6+
/// Task 1 - Compress the files on the disk while actively fragmenting the
7+
/// files. Return the checksum of the compressed disk.
8+
///
9+
/// Arguments:
10+
/// - `contents`: Input file contents.
11+
///
12+
/// Returns:
13+
/// - Checksum of the compressed (fragmented) disk.
14+
pub fn checksum_of_fragmented_disk(contents: string) !usize {
15+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
16+
defer arena.deinit();
17+
18+
const allocator = arena.allocator();
19+
var files, var gaps = try parse_initial_disk(contents, allocator);
20+
21+
var checksum: usize = 0;
22+
23+
var files_front_idx: usize = 0;
24+
var files_back_idx: usize = files.items.len - 1;
25+
var disk_idx: usize = 0;
26+
var eof_idx: usize = 0;
27+
while (files_front_idx <= files_back_idx) {
28+
// Insert file
29+
eof_idx = disk_idx + files.items[files_front_idx] - 1;
30+
checksum += files_front_idx * partial_gauss_sum(disk_idx, eof_idx);
31+
disk_idx = eof_idx + 1;
32+
33+
// Fill gap
34+
while (gaps.items[files_front_idx] > 0 and files_front_idx < files_back_idx) {
35+
if (files.items[files_back_idx] <= gaps.items[files_front_idx]) {
36+
eof_idx = disk_idx + files.items[files_back_idx] - 1;
37+
checksum += files_back_idx * partial_gauss_sum(disk_idx, eof_idx);
38+
disk_idx = eof_idx + 1;
39+
40+
gaps.items[files_front_idx] -= files.items[files_back_idx];
41+
files_back_idx -= 1;
42+
} else {
43+
eof_idx = disk_idx + gaps.items[files_front_idx] - 1;
44+
checksum += files_back_idx * partial_gauss_sum(disk_idx, eof_idx);
45+
disk_idx = eof_idx + 1;
46+
47+
files.items[files_back_idx] -= gaps.items[files_front_idx];
48+
gaps.items[files_front_idx] = 0;
49+
}
50+
}
51+
52+
files_front_idx += 1;
53+
}
54+
55+
return checksum;
56+
}
57+
58+
/// Task 2 - Compress the files on the disk while preventing fragmentation of
59+
/// the files. Return the checksum of the compressed disk.
60+
///
61+
/// Arguments:
62+
/// - `contents`: Input file contents.
63+
///
64+
/// Returns:
65+
/// - Checksum of the compressed (defragmented) disk.
66+
pub fn checksum_of_defragmented_disk(contents: string) !usize {
67+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
68+
defer arena.deinit();
69+
70+
const allocator = arena.allocator();
71+
const files, const gaps = try parse_initial_disk(contents, allocator);
72+
73+
// Calculate initial disk locations
74+
var file_disk_locations = ArrayList(usize).init(allocator);
75+
var gap_disk_locations = ArrayList(usize).init(allocator);
76+
var disk_idx: usize = 0;
77+
for (0..files.items.len) |i| {
78+
try file_disk_locations.append(disk_idx);
79+
disk_idx += files.items[i];
80+
if (i < gaps.items.len) {
81+
try gap_disk_locations.append(disk_idx);
82+
disk_idx += gaps.items[i];
83+
}
84+
}
85+
86+
// Move files where possible
87+
var file_idx = files.items.len;
88+
while (file_idx > 0) {
89+
file_idx -= 1;
90+
for (0..gaps.items.len) |gap_idx| {
91+
if (gap_disk_locations.items[gap_idx] > file_disk_locations.items[file_idx]) {
92+
break;
93+
}
94+
if (files.items[file_idx] <= gaps.items[gap_idx]) {
95+
file_disk_locations.items[file_idx] = gap_disk_locations.items[gap_idx];
96+
gap_disk_locations.items[gap_idx] += files.items[file_idx];
97+
gaps.items[gap_idx] -= files.items[file_idx];
98+
break;
99+
}
100+
}
101+
}
102+
103+
// Calculate checksum
104+
var checksum: usize = 0;
105+
for (0..files.items.len) |i| {
106+
checksum += i * partial_gauss_sum(
107+
file_disk_locations.items[i],
108+
file_disk_locations.items[i] + files.items[i] - 1,
109+
);
110+
}
111+
112+
return checksum;
113+
}
114+
115+
// -------------------------------------------------------------------------- \\
116+
117+
/// Sum all natural numbers between two given numbers.
118+
///
119+
/// Arguments:
120+
/// - `first`: The first number (lower bound).
121+
/// - `last`: The last number (upper bound).
122+
///
123+
/// Returns:
124+
/// - The sum of all natural numbers within the bounds.
125+
fn partial_gauss_sum(first: usize, last: usize) usize {
126+
return gauss_sum(last) - gauss_sum(first) + first;
127+
}
128+
129+
/// Sum all natural numbers up to the given number.
130+
///
131+
/// Arguments:
132+
/// - `num`: The give number.
133+
///
134+
/// Returns:
135+
/// - The Gauss sum of the given number.
136+
fn gauss_sum(num: usize) usize {
137+
return num * (num + 1) / 2;
138+
}
139+
140+
/// Parse the file contents into a list of file sizes and a list of gap sizes.
141+
///
142+
/// Arguments:
143+
/// - `contents`: Input file contents.
144+
/// - `allocator`: Allocator for the containers.
145+
///
146+
/// Returns:
147+
/// - Two lists with files sizes and gap sizes.
148+
fn parse_initial_disk(contents: string, allocator: Allocator) !struct { ArrayList(u32), ArrayList(u32) } {
149+
var files = ArrayList(u32).init(allocator);
150+
var gaps = ArrayList(u32).init(allocator);
151+
152+
for (0..contents.len) |i| {
153+
if (i % 2 == 0) {
154+
try files.append(try std.fmt.parseInt(u32, contents[i..(i + 1)], 10));
155+
} else {
156+
try gaps.append(try std.fmt.parseInt(u32, contents[i..(i + 1)], 10));
157+
}
158+
}
159+
160+
return .{ files, gaps };
161+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const std = @import("std");
2+
const yazap = @import("yazap");
3+
const disk_fragmenter = @import("disk_fragmenter");
4+
5+
const allocator = std.heap.page_allocator;
6+
const log = std.log;
7+
const App = yazap.App;
8+
const Arg = yazap.Arg;
9+
const string = []const u8;
10+
11+
pub fn main() !void {
12+
var app = App.init(allocator, "Day 09", "Day 09: Disk Fragmenter");
13+
defer app.deinit();
14+
15+
var cmd = app.rootCommand();
16+
cmd.setProperty(.help_on_empty_args);
17+
try cmd.addArg(Arg.singleValueOption(
18+
"filename",
19+
'f',
20+
"Input file (e.g. input/puzzle_input.txt)",
21+
));
22+
23+
const matches = try app.parseProcess();
24+
25+
const stdout_file = std.io.getStdOut().writer();
26+
var bw = std.io.bufferedWriter(stdout_file);
27+
const stdout = bw.writer();
28+
29+
var file_content: string = undefined;
30+
if (matches.getSingleValue("filename")) |filename| {
31+
const file = try std.fs.cwd().openFile(filename, .{});
32+
defer file.close();
33+
34+
const file_size = try file.getEndPos();
35+
const buffer: []u8 = try allocator.alloc(u8, file_size);
36+
defer allocator.free(buffer);
37+
38+
_ = try file.readAll(buffer);
39+
file_content = std.mem.Allocator.dupe(
40+
allocator,
41+
u8,
42+
std.mem.trim(u8, buffer, "\n"),
43+
) catch unreachable;
44+
} else {
45+
try app.displayHelp();
46+
return;
47+
}
48+
49+
const result_1 = disk_fragmenter.checksum_of_fragmented_disk(file_content);
50+
try stdout.print("Checksum of fragmented compressed disk: {!}\n", .{result_1});
51+
try bw.flush();
52+
53+
const result_2 = disk_fragmenter.checksum_of_defragmented_disk(file_content);
54+
try stdout.print("Checksum of defragmented compressed disk: {!}\n", .{result_2});
55+
try bw.flush();
56+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const std = @import("std");
2+
const disk_fragmenter = @import("disk_fragmenter");
3+
const testing = std.testing;
4+
5+
// Test of part 1
6+
test "task_1" {
7+
const example_input = @embedFile("example_input");
8+
try testing.expectEqual(
9+
1928,
10+
disk_fragmenter.checksum_of_fragmented_disk(example_input),
11+
);
12+
}
13+
14+
// Test of part 2
15+
test "task_2" {
16+
const example_input = @embedFile("example_input");
17+
try testing.expectEqual(
18+
2858,
19+
disk_fragmenter.checksum_of_defragmented_disk(example_input),
20+
);
21+
}

2024/build.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ pub fn build(b: *std.Build) void {
3636
add_subproject(b, target, optimize, test_step, benchmark_step, "02", "red_nosed_reports");
3737
add_subproject(b, target, optimize, test_step, benchmark_step, "03", "mull_it_over");
3838
add_subproject(b, target, optimize, test_step, benchmark_step, "06", "guard_gallivant");
39+
add_subproject(b, target, optimize, test_step, benchmark_step, "09", "disk_fragmenter");
3940
}

2024/build.zig.zon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
.day_02_red_nosed_reports = .{ .path = "02/red_nosed_reports" },
99
.day_03_mull_it_over = .{ .path = "03/mull_it_over" },
1010
.day_06_guard_gallivant = .{ .path = "06/guard_gallivant" },
11+
.day_09_disk_fragmenter = .{ .path = "09/disk_fragmenter" },
1112
},
1213
.paths = .{
1314
"build.zig",
@@ -17,5 +18,6 @@
1718
"02",
1819
"03",
1920
"06",
21+
"09",
2022
},
2123
}

0 commit comments

Comments
 (0)