Solve day 9 of AoC 2024
This commit is contained in:
parent
0c6bf7e054
commit
4e7c94b473
7 changed files with 333 additions and 0 deletions
1
advent-of-code/2024/day_09/.gitignore
vendored
Normal file
1
advent-of-code/2024/day_09/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
19
advent-of-code/2024/day_09/Makefile
Normal file
19
advent-of-code/2024/day_09/Makefile
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
TARGET_EXEC := main
|
||||||
|
BUILD_DIR := ./build
|
||||||
|
CFLAGS := -Wall -Wextra -g
|
||||||
|
|
||||||
|
SRCS := $(shell find . -name '*.c')
|
||||||
|
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
|
||||||
|
|
||||||
|
# The final build step.
|
||||||
|
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
|
||||||
|
$(CC) $(OBJS) -o $@
|
||||||
|
|
||||||
|
# Build step for C source
|
||||||
|
$(BUILD_DIR)/%.c.o: %.c
|
||||||
|
mkdir -p $(dir $@)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -r $(BUILD_DIR)
|
103
advent-of-code/2024/day_09/README.md
Normal file
103
advent-of-code/2024/day_09/README.md
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
# Day 9: Disk Fragmenter
|
||||||
|
|
||||||
|
[Advent of Code 2024](https://adventofcode.com/2024/day/9)
|
||||||
|
|
||||||
|
Another push of the button leaves you in the familiar hallways of some friendly amphipods!
|
||||||
|
Good thing you each somehow got your own personal mini submarine. The Historians jet away in search of the Chief,
|
||||||
|
mostly by driving directly into walls.
|
||||||
|
|
||||||
|
While The Historians quickly figure out how to pilot these things, you notice an amphipod in the corner struggling with his computer.
|
||||||
|
He's trying to make more contiguous free space by compacting all of the files, but his program isn't working; you offer to help.
|
||||||
|
|
||||||
|
He shows you the disk map (your puzzle input) he's already generated. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
2333133121414131402
|
||||||
|
```
|
||||||
|
|
||||||
|
The disk map uses a dense format to represent the layout of files and free space on the disk.
|
||||||
|
The digits alternate between indicating the length of a file and the length of free space.
|
||||||
|
|
||||||
|
So, a disk map like 12345 would represent a one-block file, two blocks of free space, a three-block file, four blocks of free space,
|
||||||
|
and then a five-block file. A disk map like 90909 would represent three nine-block files in a row (with no free space between them).
|
||||||
|
|
||||||
|
Each file on disk also has an ID number based on the order of the files as they appear before they are rearranged, starting with ID 0.
|
||||||
|
So, the disk map 12345 has three files: a one-block file with ID 0, a three-block file with ID 1, and a five-block file with ID 2.
|
||||||
|
Using one character for each block where digits are the file ID and . is free space, the disk map 12345 represents these individual blocks:
|
||||||
|
|
||||||
|
```
|
||||||
|
0..111....22222
|
||||||
|
```
|
||||||
|
|
||||||
|
The first example above, `2333133121414131402`, represents these individual blocks:
|
||||||
|
|
||||||
|
```
|
||||||
|
00...111...2...333.44.5555.6666.777.888899
|
||||||
|
```
|
||||||
|
|
||||||
|
The amphipod would like to move file blocks one at a time from the end of the disk to the leftmost free space block
|
||||||
|
(until there are no gaps remaining between file blocks). For the disk map 12345, the process looks like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
0..111....22222
|
||||||
|
02.111....2222.
|
||||||
|
022111....222..
|
||||||
|
0221112...22...
|
||||||
|
02211122..2....
|
||||||
|
022111222......
|
||||||
|
```
|
||||||
|
|
||||||
|
The first example requires a few more steps:
|
||||||
|
|
||||||
|
```
|
||||||
|
00...111...2...333.44.5555.6666.777.888899
|
||||||
|
009..111...2...333.44.5555.6666.777.88889.
|
||||||
|
0099.111...2...333.44.5555.6666.777.8888..
|
||||||
|
00998111...2...333.44.5555.6666.777.888...
|
||||||
|
009981118..2...333.44.5555.6666.777.88....
|
||||||
|
0099811188.2...333.44.5555.6666.777.8.....
|
||||||
|
009981118882...333.44.5555.6666.777.......
|
||||||
|
0099811188827..333.44.5555.6666.77........
|
||||||
|
00998111888277.333.44.5555.6666.7.........
|
||||||
|
009981118882777333.44.5555.6666...........
|
||||||
|
009981118882777333644.5555.666............
|
||||||
|
00998111888277733364465555.66.............
|
||||||
|
0099811188827773336446555566..............
|
||||||
|
```
|
||||||
|
|
||||||
|
The final step of this file-compacting process is to update the filesystem checksum.
|
||||||
|
To calculate the checksum, add up the result of multiplying each of these blocks' position with the file ID number it contains.
|
||||||
|
The leftmost block is in position 0. If a block contains free space, skip it instead.
|
||||||
|
|
||||||
|
Continuing the first example, the first few blocks' position multiplied by its file ID number are
|
||||||
|
0 * 0 = 0, 1 * 0 = 0, 2 * 9 = 18, 3 * 9 = 27, 4 * 8 = 32, and so on. In this example, the checksum is the sum of these, 1928.
|
||||||
|
|
||||||
|
Compact the amphipod's hard drive using the process he requested. What is the resulting filesystem checksum?
|
||||||
|
|
||||||
|
|
||||||
|
## Part Two
|
||||||
|
|
||||||
|
Upon completion, two things immediately become clear.
|
||||||
|
First, the disk definitely has a lot more contiguous free space, just like the amphipod hoped.
|
||||||
|
Second, the computer is running much more slowly! Maybe introducing all of that file system fragmentation was a bad idea?
|
||||||
|
|
||||||
|
The eager amphipod already has a new plan: rather than move individual blocks,
|
||||||
|
he'd like to try compacting the files on his disk by moving whole files instead.
|
||||||
|
|
||||||
|
This time, attempt to move whole files to the leftmost span of free space blocks that could fit the file.
|
||||||
|
Attempt to move each file exactly once in order of decreasing file ID number starting with the file with the highest file ID number.
|
||||||
|
If there is no span of free space to the left of a file that is large enough to fit the file, the file does not move.
|
||||||
|
|
||||||
|
The first example from above now proceeds differently:
|
||||||
|
|
||||||
|
```
|
||||||
|
00...111...2...333.44.5555.6666.777.888899
|
||||||
|
0099.111...2...333.44.5555.6666.777.8888..
|
||||||
|
0099.1117772...333.44.5555.6666.....8888..
|
||||||
|
0099.111777244.333....5555.6666.....8888..
|
||||||
|
00992111777.44.333....5555.6666.....8888..
|
||||||
|
```
|
||||||
|
|
||||||
|
The process of updating the filesystem checksum is the same; now, this example's checksum would be 2858.
|
||||||
|
|
||||||
|
Start over, now compacting the amphipod's hard drive using this new method instead. What is the resulting filesystem checksum?
|
1
advent-of-code/2024/day_09/input.txt
Normal file
1
advent-of-code/2024/day_09/input.txt
Normal file
File diff suppressed because one or more lines are too long
139
advent-of-code/2024/day_09/lib.c
Normal file
139
advent-of-code/2024/day_09/lib.c
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "lib.h"
|
||||||
|
|
||||||
|
struct Vec expand_disk_map(char* input) {
|
||||||
|
size_t input_length = strlen(input);
|
||||||
|
size_t required_length = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < input_length; i++) {
|
||||||
|
required_length += input[i] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
int* data = malloc(sizeof(int) * required_length);
|
||||||
|
int id = 0;
|
||||||
|
int data_idx = 0;
|
||||||
|
for (size_t i = 0; i < input_length; i++) {
|
||||||
|
size_t digit = input[i] - '0';
|
||||||
|
|
||||||
|
for (size_t j = 0; j < digit; j++) {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
data[data_idx] = id;
|
||||||
|
} else {
|
||||||
|
data[data_idx] = -1;
|
||||||
|
}
|
||||||
|
data_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Vec vec = { required_length, data };
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void compress_fs(struct Vec vec) {
|
||||||
|
size_t last_digit_search_start = vec.length - 1;
|
||||||
|
for (size_t i = 0; i < vec.length; i++) {
|
||||||
|
if (vec.data[i] != -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t j;
|
||||||
|
for (j = last_digit_search_start; j > i; j--) {
|
||||||
|
if (vec.data[j] == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j == i) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_digit_search_start = j - 1;
|
||||||
|
vec.data[i] = vec.data[j];
|
||||||
|
vec.data[j] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a range of spaces of the required length.
|
||||||
|
* Returns the index of the start of the range.
|
||||||
|
* Returns 0 if the range is not found.
|
||||||
|
*/
|
||||||
|
size_t find_space(struct Vec vec, size_t max_idx, size_t len) {
|
||||||
|
size_t counter = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i <= max_idx; i++) {
|
||||||
|
if (vec.data[i] != -1) {
|
||||||
|
counter = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
if (counter == len) {
|
||||||
|
return i + 1 - counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void compress_fs_by_blocks(struct Vec vec) {
|
||||||
|
int id = -1;
|
||||||
|
size_t block_end;
|
||||||
|
for (size_t i = vec.length - 1; i > 0; i--) {
|
||||||
|
if (vec.data[i] == id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last digit of the block was found.
|
||||||
|
if (id == -1) {
|
||||||
|
id = vec.data[i];
|
||||||
|
block_end = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First digit of the block was found.
|
||||||
|
size_t block_start = i + 1;
|
||||||
|
size_t block_len = block_end - block_start + 1;
|
||||||
|
|
||||||
|
size_t space_idx = find_space(vec, block_start - 1, block_len);
|
||||||
|
// Space was found.
|
||||||
|
if (space_idx != 0) {
|
||||||
|
for (size_t si = space_idx; si < space_idx + block_len; si++) {
|
||||||
|
vec.data[si] = id;
|
||||||
|
}
|
||||||
|
for (size_t bi = block_start; bi <= block_end; bi++) {
|
||||||
|
vec.data[bi] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id = vec.data[i];
|
||||||
|
block_end = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long calc_checksum(struct Vec vec) {
|
||||||
|
long sum = 0;
|
||||||
|
for (size_t i = 0; i < vec.length; i++) {
|
||||||
|
if (vec.data[i] == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum += i * vec.data[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Vec copy_vec(struct Vec vec) {
|
||||||
|
int data_size = sizeof(int) * vec.length;
|
||||||
|
int* data_copy = malloc(data_size);
|
||||||
|
memcpy(data_copy, vec.data, data_size);
|
||||||
|
struct Vec vec_copy = { vec.length, data_copy };
|
||||||
|
return vec_copy;
|
||||||
|
}
|
26
advent-of-code/2024/day_09/lib.h
Normal file
26
advent-of-code/2024/day_09/lib.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct Vec{
|
||||||
|
size_t length;
|
||||||
|
int* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand compressed disk map.
|
||||||
|
* For example, expand_blocks("123") returns [0, -1, -1, 1, 1, 1].
|
||||||
|
* The caller must free the memory of the returned array.
|
||||||
|
*/
|
||||||
|
struct Vec expand_disk_map(char*);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress the data in-place.
|
||||||
|
*/
|
||||||
|
void compress_fs(struct Vec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the blocks in-place.
|
||||||
|
*/
|
||||||
|
void compress_fs_by_blocks(struct Vec);
|
||||||
|
|
||||||
|
long calc_checksum(struct Vec);
|
||||||
|
struct Vec copy_vec(struct Vec);
|
44
advent-of-code/2024/day_09/main.c
Normal file
44
advent-of-code/2024/day_09/main.c
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "lib.h"
|
||||||
|
|
||||||
|
#define LINE_LEN 20000
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
FILE* file_ptr = fopen("input.txt", "r");
|
||||||
|
if (file_ptr == NULL) {
|
||||||
|
printf("Can't open input file\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char input[LINE_LEN];
|
||||||
|
fread(input, LINE_LEN, 1, file_ptr);
|
||||||
|
|
||||||
|
size_t input_length = strlen(input);
|
||||||
|
if (input_length < 1) {
|
||||||
|
printf("Empty input.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_length > 0 && input[input_length-1] == '\n') {
|
||||||
|
input[input_length-1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Vec blocks = expand_disk_map(input);
|
||||||
|
struct Vec blocks_copy = copy_vec(blocks);
|
||||||
|
|
||||||
|
compress_fs(blocks);
|
||||||
|
long checksum = calc_checksum(blocks);
|
||||||
|
printf("Part 1: %ld\n", checksum);
|
||||||
|
|
||||||
|
compress_fs_by_blocks(blocks_copy);
|
||||||
|
checksum = calc_checksum(blocks_copy);
|
||||||
|
printf("Part 2: %ld\n", checksum);
|
||||||
|
|
||||||
|
free(blocks.data);
|
||||||
|
free(blocks_copy.data);
|
||||||
|
|
||||||
|
fclose(file_ptr);
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in a new issue