Files
org_roam/Notes/20241210001150-aoc_notes.org
2026-03-27 15:39:35 +00:00

1087 lines
37 KiB
Org Mode

:PROPERTIES:
:ID: e7f2302b-16eb-476d-a7b9-be12f077819d
:END:
#+title: AOC Notes
#+filetags: :notes:aoc:technical:
#+STARTUP: overview
Here are the notes for the AOC (Advent Of Code) for the year 2024.
Workspace:
#+BEGIN_SRC haskell
import System.Process (callCommand)
main :: IO ()
main = do
putStrLn "Launching IntelliJ in gnome-terminal with zsh..."
_ <- callCommand "gnome-terminal -- zsh -c 'idea /home/zaine/Documents/projects/advent_of_code'"
putStrLn "Terminal launched."
#+END_SRC
#+RESULTS:
* Day 3
** point 1
A lot of basic string and numeric manipulation was tested.
When you have a input string, and want to test weather there contains a substring, you can use
#+BEGIN_SRC java[[id:8fa3f476-6152-45f4-b618-50f1e4bce46c][Emacs Stuff]]
// input is the string
if (input.startsWith("mul(", i)) {
int endIndex = input.indexOf(")", i);
if (endIndex != -1) {
String candidate = input.substring(i, endIndex + 1);
}
}
#+END_SRC
Here we can see that through the loop, an if check is being done to check if the string starts with "mul(", if so, it gets the endIndex of it too. Then it stores that "candidate" value into a String, using the substring method.
** point 2
some regex below: this checks if the input is of the correct format.
#+BEGIN_SRC java
public static boolean isValidMul(String input) {
// Regex to validate mul(X,Y) where X and Y are 1-3 digit numbers
return input.matches("mul\\(\\d{1,3},\\d{1,3}\\)");
}
#+END_SRC
* Day 4
** part 1
was extremely difficult. i had to load the input as a 2d array,
#+BEGIN_SRC java
int cols = lines.get(0).length(); //where lines is var
int cols = grid[0].length; // where grid is a 2d array
#+END_SRC
this allows you to get the vertical length of the 2d array.
*** countWordOccurrences Method
Iterates through the grid to check for occurrences of the word "XMAS" in all possible directions (horizontal, vertical, and diagonal).
Directions: Defined by the directions array, which contains 8 possible ways to traverse:
{0, 1}: Right
{0, -1}: Left
{1, 0}: Down
{-1, 0}: Up
{1, 1}: Diagonal down-right
{1, -1}: Diagonal down-left
{-1, 1}: Diagonal up-right
{-1, -1}: Diagonal up-left
For each starting position (row, col) in the grid:
The program checks each direction by calling isWordFound.
*** Checking for the Word
isWordFound Method Validates if the word exists starting from a specific position (row, col) in the grid, moving in the specified direction (dx, dy).
For each character in the word:
- compute the new position (newRow, newCol) based on the direction.
- Check bounds to ensure the position is valid (not out of the grid).
- Compare the character at the position with the corresponding character in the word.
- If any check fails, return false.
If all characters match, the word is found, and the method returns true.
*** Counting Matches
For each occurrence of the word found by isWordFound, increment the count variable.
After scanning all positions and directions in the grid, the total count is returned.
** part 2
was alot easier, i looped through the whole grid, and wherever there is the letter 'A', i want to check around it, it can be in the form:
#+BEGIN_SRC
M.S
.A.
M.S
#+END_SRC
and this would count as one, as there is MAS twice (diagonally in the shape of an X)
* Day 5
** Part One: Identifying Correctly Ordered Updates
*** Splitting Input into Rules and Updates
Parsing the input file into two distinct sections (rules and updates) required identifying the empty line separator.
#+BEGIN_SRC java
for (String line : lines) {
if (line.trim().isEmpty()) {
emptyLineFound = true;
continue; // Skip the empty line itself
}
if (!emptyLineFound) {
firstList.add(line);
} else {
secondList.add(line);
}
}
#+END_SRC
*** Dependency Graph Representation
Representing the rules as a directed graph with each rule defining an edge (X|Y as X -> Y). This graph maps each page to a list of pages that must follow it.
#+BEGIN_SRC java
Map<Integer, List<Integer>> graph = new HashMap<>();
graph.putIfAbsent(from, new ArrayList<>());
graph.get(from).add(to);
#+END_SRC
*** Checking Update Validity
Verifying if an update follows the rules using a map of page positions for quick lookup.
#+BEGIN_SRC java
for (Map.Entry<Integer, List<Integer>> entry : graph.entrySet()) {
int from = entry.getKey();
for (int to : entry.getValue()) {
if (positions.get(from) >= positions.get(to)) {
return false; // Rule violated
}
}
}
#+END_SRC
*** Finding the Middle Page
Calculating the middle page for each correctly ordered update using list indexing.
#+BEGIN_SRC java
int middlePage = pages.get(pages.size() / 2);
#+END_SRC
** Part Two: Reordering Incorrect Updates
*** Topological Sorting
Reordering pages required implementing a topological sort, which ensures all dependencies (rules) are respected.
#+BEGIN_SRC java
visiting.add(node);
for (int neighbor : graph.getOrDefault(node, Collections.emptyList())) {
if (!dfs(neighbor, graph, visited, visiting, sorted)) {
return false; // Cycle detected
}
}
visiting.remove(node);
visited.add(node);
sorted.add(node);
#+END_SRC
*** Subgraph Creation
Only rules involving pages in the current update were considered. This required dynamically building a subgraph for each update.
#+BEGIN_SRC java
for (int page : pages) {
if (graph.containsKey(page)) {
for (int dependent : graph.get(page)) {
if (pages.contains(dependent)) {
subGraph.get(page).add(dependent);
}
}
}
}
#+END_SRC
*** Cycle Detection
Ensuring no cycles existed in the dependency graph was critical for valid sorting.
#+BEGIN_SRC java
if (visiting.contains(node)) {
return false; // Cycle detected
}
#+END_SRC
*** Finding Middle Page After Reordering
Same approach as in Part One but applied after sorting.
#+BEGIN_SRC java
int correctedMiddlePage = reorderedPages.get(reorderedPages.size() / 2);
#+END_SRC
* Day 6
** Part 1
*** Input Parsing
#+BEGIN_SRC java
List<String> input = Files.readAllLines(Paths.get("aoc_24/src/day_6/input"));
int rows = input.size();
int cols = input.get(0).length();
char[][] map = new char[rows][cols];
#+END_SRC
*** Guard Initialization
#+BEGIN_SRC java
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if ("^v<>".indexOf(map[r][c]) != -1) {
guardRow = r;
guardCol = c;
guardFacing = map[r][c];
map[r][c] = '.'; // Clear the guard's position
}
}
}
#+END_SRC
*** Movement Directions
#+BEGIN_SRC java
Map<Character, int[]> directions = Map.of(
'^', new int[] {-1, 0},
'v', new int[] {1, 0},
'<', new int[] {0, -1},
'>', new int[] {0, 1}
);
#+END_SRC
*** Turning Logic
#+BEGIN_SRC java
Map<Character, Character> turnRight = Map.of(
'^', '>',
'>', 'v',
'v', '<',
'<', '^'
);
#+END_SRC
*** Visited Positions Tracking
#+BEGIN_SRC java
Set<String> visited = new HashSet<>();
visited.add(guardRow + "," + guardCol);
#+END_SRC
*** Movement and Termination Logic
#+BEGIN_SRC java
while (true) {
int[] move = directions.get(guardFacing);
int nextRow = guardRow + move[0];
int nextCol = guardCol + move[1];
if (nextRow < 0 || nextRow >= rows || nextCol < 0 || nextCol >= cols) {
break; // Guard leaves the map
}
if (map[nextRow][nextCol] == '#') {
guardFacing = turnRight.get(guardFacing); // Turn right
} else {
guardRow = nextRow;
guardCol = nextCol;
visited.add(guardRow + "," + guardCol);
}
}
#+END_SRC
** Part 2
*** Input Parsing
#+BEGIN_SRC java
List<String> input = Files.readAllLines(Paths.get("aoc_24/src/day_6/input"));
int rows = input.size();
int cols = input.get(0).length();
char[][] map = new char[rows][cols];
#+END_SRC
*** Guard Initialization
#+BEGIN_SRC java
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if ("^v<>".indexOf(map[r][c]) != -1) {
guardRow = r;
guardCol = c;
guardFacing = map[r][c];
map[r][c] = '.'; // Clear the guard's position
}
}
}
#+END_SRC
*** Movement Directions
#+BEGIN_SRC java
Map<Character, int[]> directions = Map.of(
'^', new int[] {-1, 0},
'v', new int[] {1, 0},
'<', new int[] {0, -1},
'>', new int[] {0, 1}
);
#+END_SRC
*** Turning Logic
#+BEGIN_SRC java
Map<Character, Character> turnRight = Map.of(
'^', '>',
'>', 'v',
'v', '<',
'<', '^'
);
#+END_SRC
*** Valid Obstruction Positions
#+BEGIN_SRC java
Set<String> validObstructions = new HashSet<>();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (map[r][c] == '.' && !(r == guardRow && c == guardCol)) {
map[r][c] = '#'; // Temporarily place obstruction
if (causesLoop(map, guardRow, guardCol, guardFacing, directions, turnRight)) {
validObstructions.add(r + "," + c);
}
map[r][c] = '.'; // Remove obstruction
}
}
}
System.out.println("Number of valid obstruction positions: " + validObstructions.size());
#+END_SRC
*** Loop Detection Helper Function
#+BEGIN_SRC java
private static boolean causesLoop(char[][] map, int guardRow, int guardCol, char guardFacing,
Map<Character, int[]> directions, Map<Character, Character> turnRight) {
Set<String> seenStates = new HashSet<>();
int rows = map.length;
int cols = map[0].length;
while (true) {
String state = guardRow + "," + guardCol + "," + guardFacing;
if (seenStates.contains(state)) {
return true; // Loop detected
}
seenStates.add(state);
int[] move = directions.get(guardFacing);
int nextRow = guardRow + move[0];
int nextCol = guardCol + move[1];
if (nextRow < 0 || nextRow >= rows || nextCol < 0 || nextCol >= cols) {
return false; // Guard leaves the map
}
if (map[nextRow][nextCol] == '#') {
guardFacing = turnRight.get(guardFacing); // Turn right
} else {
guardRow = nextRow;
guardCol = nextCol;
}
}
}
#+END_SRC
* Day 7
** Part 1
*** Input Parsing
- Parse the input file, where each line is in the format `<testValue>: <number1> <number2> ...`
- `testValue` is a target number, and we determine if it can be computed by combining the given numbers with `+` or `*`.
#+BEGIN_SRC java
List<String> input = Files.readAllLines(Paths.get("aoc_24/src/day_7/input"));
long totalCalibrationResult = 0;
for (String line : input) {
String[] parts = line.split(": ");
long testValue = Long.parseLong(parts[0]); // Target value
String[] numbers = parts[1].split(" ");
}
#+END_SRC
*** Validation Logic
- The `isValidEquation` function checks if any combination of operators (`+` or `*`) between the numbers matches the `testValue`.
- Evaluation is performed left-to-right.
#+BEGIN_SRC java
private static boolean isValidEquation(long testValue, String[] numbers) {
List<String> operators = Arrays.asList("+", "*");
List<String[]> operatorCombinations = generateOperatorCombinations(numbers.length - 1, operators);
for (String[] operatorCombination : operatorCombinations) {
long result = Long.parseLong(numbers[0]);
for (int i = 1; i < numbers.length; i++) {
String operator = operatorCombination[i - 1];
long num = Long.parseLong(numbers[i]);
if (operator.equals("+")) {
result += num;
} else if (operator.equals("*")) {
result *= num;
}
}
if (result == testValue) {
return true; // Equation is valid
}
}
return false; // No valid equation found
}
#+END_SRC
*** Operator Combination Generator
- Generate all possible combinations of `+` and `*` for `n-1` positions (where `n` is the number of numbers).
#+BEGIN_SRC java
private static List<String[]> generateOperatorCombinations(int numOperators, List<String> operators) {
List<String[]> combinations = new ArrayList<>();
generateOperatorCombinationsRecursive(new String[numOperators], 0, operators, combinations);
return combinations;
}
private static void generateOperatorCombinationsRecursive(String[] current, int index, List<String> operators, List<String[]> combinations) {
if (index == current.length) {
combinations.add(current.clone());
return;
}
for (String operator : operators) {
current[index] = operator;
generateOperatorCombinationsRecursive(current, index + 1, operators, combinations);
}
}
#+END_SRC
*** Main Logic
- Iterate over each line of input.
- Parse `testValue` and numbers.
- If a valid equation exists for the line, add the `testValue` to the total calibration result.
#+BEGIN_SRC java
for (String line : input) {
String[] parts = line.split(": ");
long testValue = Long.parseLong(parts[0]);
String[] numbers = parts[1].split(" ");
if (isValidEquation(testValue, numbers)) {
totalCalibrationResult += testValue;
}
}
System.out.println("Total Calibration Result: " + totalCalibrationResult);
#+END_SRC
** Part 2
*** Input Parsing
- Reads an input file where each line is formatted as `<testValue>: <number1> <number2> ...`.
- Parses `testValue` and the numbers to evaluate equations that could result in `testValue`.
#+BEGIN_SRC java
List<String> input = Files.readAllLines(Paths.get("aoc_24/src/day_7/input"));
long totalCalibrationResult = 0;
for (String line : input) {
String[] parts = line.split(": ");
long testValue = Long.parseLong(parts[0]); // Target value
String[] numbers = parts[1].split(" ");
}
#+END_SRC
*** Validation Logic
- The `isValidEquation` function checks if any combination of operators (`+`, `*`, or `||`) between numbers can match the `testValue`.
- Includes support for a new operator `||`:
- Concatenates the current `result` and the next number as strings.
- Converts the concatenated string back to `long` to update the result.
- Evaluation is performed left-to-right.
#+BEGIN_SRC java
private static boolean isValidEquation(long testValue, String[] numbers) {
List<String> operators = Arrays.asList("+", "*", "||");
List<String[]> operatorCombinations = generateOperatorCombinations(numbers.length - 1, operators);
for (String[] operatorCombination : operatorCombinations) {
long result = Long.parseLong(numbers[0]);
for (int i = 1; i < numbers.length; i++) {
String operator = operatorCombination[i - 1];
long num = Long.parseLong(numbers[i]);
if (operator.equals("+")) {
result += num;
} else if (operator.equals("*")) {
result *= num;
} else if (operator.equals("||")) {
result = Long.parseLong(Long.toString(result) + Long.toString(num));
}
}
if (result == testValue) {
return true;
}
}
return false;
}
#+END_SRC
*** Operator Combination Generator
- Generates all possible combinations of `+`, `*`, and `||` operators for `n-1` positions (where `n` is the number of numbers).
- Recursively builds the combinations.
#+BEGIN_SRC java
private static List<String[]> generateOperatorCombinations(int numOperators, List<String> operators) {
List<String[]> combinations = new ArrayList<>();
generateOperatorCombinationsRecursive(new String[numOperators], 0, operators, combinations);
return combinations;
}
private static void generateOperatorCombinationsRecursive(String[] current, int index, List<String> operators, List<String[]> combinations) {
if (index == current.length) {
combinations.add(current.clone());
return;
}
for (String operator : operators) {
current[index] = operator;
generateOperatorCombinationsRecursive(current, index + 1, operators, combinations);
}
}
#+END_SRC
*** Main Logic
- Iterates over each line of input.
- Parses `testValue` and numbers.
- Adds `testValue` to the total if a valid equation exists for the line.
#+BEGIN_SRC java
for (String line : input) {
String[] parts = line.split(": ");
long testValue = Long.parseLong(parts[0]);
String[] numbers = parts[1].split(" ");
if (isValidEquation(testValue, numbers)) {
totalCalibrationResult += testValue;
}
}
System.out.println("Total Calibration Result: " + totalCalibrationResult);
#+END_SRC
* Day 8
Resonant Collinearity
** Part One
- Objective: Identify unique antinode locations within a map, considering two antennas of the same frequency in a specific configuration.
- Key Condition: Antinode occurs if two antennas of the same frequency are aligned such that one is twice as far from the antinode as the other.
- Steps:
1. Parse the input map to locate antennas grouped by their frequency.
2. For each frequency group, iterate through all antenna pairs.
3. Calculate potential antinode positions based on the defined condition.
4. Use a Set to store unique antinode positions.
5. Return the size of the Set as the total unique antinode count.
- Code Snippet:
#+BEGIN_SRC java
for (int i = 0; i < locations.size(); i++) {
for (int j = i + 1; j < locations.size(); j++) {
int[] a = locations.get(i);
int[] b = locations.get(j);
// Calculate midpoints and validate conditions
if ((b[0] - a[0]) % 2 == 0 && (b[1] - a[1]) % 2 == 0) {
int midRow = (a[0] + b[0]) / 2;
int midCol = (a[1] + b[1]) / 2;
antinodes.add(midRow + "," + midCol);
}
}
}
#+END_SRC
** Part Two
- Objective: Update the model to include all positions perfectly aligned with at least two antennas of the same frequency.
- Key Changes:
- Antinodes occur at all positions along the straight line between antennas of the same frequency.
- Antennas themselves are also antinodes unless they are the only instance of their frequency.
- Steps:
1. Parse the input map and group antennas by frequency.
2. For each pair of antennas of the same frequency:
- Calculate direction vectors (reduced using GCD).
- Traverse along the direction vector in both forward and backward directions, marking all valid positions as antinodes.
3. Add each antenna location directly to the set of antinodes.
4. Return the size of the unique antinode set.
- Code Snippet:
#+BEGIN_SRC java
int dr = b[0] - a[0];
int dc = b[1] - a[1];
int gcd = gcd(Math.abs(dr), Math.abs(dc));
dr /= gcd;
dc /= gcd;
// Traverse along the line
int row = a[0], col = a[1];
while (isWithinBounds(row, col, rows, cols)) {
antinodes.add(row + "," + col);
row += dr;
col += dc;
}
#+END_SRC
** Notes on Implementation
- Data Structures:
- Map<Character, List<int[]>>: Stores antenna positions by frequency.
- Set<String>: Tracks unique antinode positions.
- Utility Functions:
- isWithinBounds: Ensures coordinates are within map dimensions.
- gcd: Simplifies direction vectors to avoid redundant calculations.
* Day 9
** Part 1: Manipulating and Calculating Disk Placement
Concept: Creating and manipulating a disk structure based on input.
Input is split into alternating "id" and "space" values.
Example of creating the disk:
#+BEGIN_SRC java
for (String character : lines.getFirst().split("")) {
int num = Integer.parseInt(character);
if (space) {
for (int i = 0; i < num; i++) disk.add(-1);
} else {
for (int i = 0; i < num; i++) disk.add(id);
id++;
}
space = !space;
}
#+END_SRC
- Key Learning: Understanding alternating patterns in input and their translation to a data structure.
- Problem Solving: Adjusting misplaced items.
- Utilize a while loop to locate and correct misplaced "-1" values in the disk.
- Example:
#+BEGIN_SRC java
if (disk.get(i) == -1) {
int val = -1;
while (val == -1) {
val = disk.removeLast();
}
disk.add(i, val);
}
#+END_SRC
- Final Calculation: Using BigInteger for large numbers.
- Formula: index * value for each position in the disk.
- Example:
#+BEGIN_SRC java
BigInteger count = BigInteger.ZERO;
for (int i = 0; i < disk.size(); i++) {
count = count.add(BigInteger.valueOf(i).multiply(BigInteger.valueOf(disk.get(i))));
}
#+END_SRC
** Part 2: Advanced Disk Rearrangement with Blocks
- Concept: Representing disk as a list of Block objects.
- Block stores size and id.
- Example:
#+BEGIN_SRC java
public static class Block {
private int size;
private int id;
public Block(int size, int id) {
this.size = size;
this.id = id;
}
}
#+END_SRC
- Key Learning: Encapsulating logic into objects improves clarity and scalability.
- Space Management: Finding and fitting blocks into available spaces.
- Utilize a fit method to split or match blocks.
- Example:
#+BEGIN_SRC java
public List<Block> fit(Block work) {
if (work.size > this.size) return null;
List<Block> newList = new ArrayList<>();
newList.add(work);
if (work.size < this.size) {
newList.add(new Block(this.size - work.size, -1));
}
return newList;
}
#+END_SRC
- Problem Solving: Iterating backward through the disk to find and rearrange blocks into spaces.
- Restart loop when a fit is found to ensure proper placement.
- Example:
#+BEGIN_SRC java
for (int i = 0; i < diskPlace; i++) {
Block possibleSpace = disk.get(i);
if (possibleSpace.getId() == -1) {
List<Block> blocks = possibleSpace.fit(work);
if (blocks != null) {
disk.remove(diskPlace);
disk.add(diskPlace, new Block(work.getSize(), -1));
for (int j = blocks.size() - 1; j >= 0; j--) {
disk.add(i, blocks.get(j));
}
break;
}
}
}
#+END_SRC
- Final Calculation: Summing placements using block properties.
- Ensure large calculations are done efficiently with BigInteger.
- Example:
#+BEGIN_SRC java
BigInteger count = BigInteger.ZERO;
int placement = 0;
for (Block block : disk) {
if (block.getId() != -1) {
for (int j = 0; j < block.getSize(); j++) {
count = count.add(BigInteger.valueOf(placement).multiply(BigInteger.valueOf(block.getId())));
placement++;
}
} else {
placement += block.getSize();
}
}
#+END_SRC
* Day 10
** Part 1: Counting Trails in a 2D Grid
- Concept: Navigating and processing a 2D grid based on specific rules.
- Input is parsed into a 2D map from a list of strings.
- Conversion logic for parsing:
#+BEGIN_SRC java
int[] map = new int[width * height];
int i = 0;
for (String line : lines) {
if (line.isBlank()) continue;
for (String character : line.trim().split("")) {
map[i] = Integer.parseInt(character);
i++;
}
}
#+END_SRC
- Recursive Approach: Traversing paths with a helper function countTrails.
- Recursion halts on boundaries, invalid values, or when a sequence completes.
- Example:
#+BEGIN_SRC java
private static Set<Point> countTrails(int[] map, int x, int y, int width, int height, int val) {
if (x >= width || y >= height || x < 0 || y < 0 || map[y * width + x] != val) return new HashSet<>();
if (val == 9) return Set.of(new Point(9, x, y));
Set<Point> result = new HashSet<>();
result.addAll(countTrails(map, x + 1, y, width, height, val + 1));
result.addAll(countTrails(map, x - 1, y, width, height, val + 1));
result.addAll(countTrails(map, x, y + 1, width, height, val + 1));
result.addAll(countTrails(map, x, y - 1, width, height, val + 1));
return result;
}
#+END_SRC
- Key Learning: Recursive exploration of a grid with stateful logic for trail validity.
- Result Calculation: Sum the size of all unique trail sets.
- Example:
#+BEGIN_SRC java
long count = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Set<Point> set = countTrails(map, x, y, width, height, 0);
count += set.size();
}
}
#+END_SRC
** Part 2: Enhanced Trail Counting with Weighted Points
- Concept: Counting trails with weights using a Map<Point, Integer> for aggregation.
- Modified helper function countTrails2 tracks weights for each point.
- Example:
#+BEGIN_SRC java
private static Map<Point, Integer> countTrails2(int[] map, int x, int y, int width, int height, int val) {
if (x >= width || y >= height || x < 0 || y < 0 || map[y * width + x] != val) return new HashMap<>();
if (val == 9) return Map.of(new Point(1, x, y), 1);
Map<Point, Integer> result = new HashMap<>();
checkDirection(map, x + 1, y, width, height, val, result);
checkDirection(map, x - 1, y, width, height, val, result);
checkDirection(map, x, y + 1, width, height, val, result);
checkDirection(map, x, y - 1, width, height, val, result);
return result;
}
#+END_SRC
- Helper Method: checkDirection facilitates merging results for trail continuity.
- Example:
#+BEGIN_SRC java
private static void checkDirection(int[] map, int x, int y, int width, int height, int val, Map<Point, Integer> result) {
Map<Point, Integer> res = countTrails2(map, x, y, width, height, val + 1);
for (Point p : res.keySet()) {
result.merge(p, res.get(p), Integer::sum);
}
}
#+END_SRC
- Key Learning: Using a Map to manage complex trail state and weights for precise calculations.
- Result Calculation: Sum weighted trail counts.
- Example:
#+BEGIN_SRC java
long count = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Map<Point, Integer> res = countTrails2(map, x, y, width, height, 0);
for (int value : res.values()) {
count += value;
}
}
}
#+END_SRC
- Encapsulation of grid points as objects (Point) simplifies hash-based operations and improves code clarity.
- Using collections like Set and Map effectively is crucial for aggregating results in a structured way.
Would you like to make any adjustments or add further examples?
* Day 12
** Core Logic and Functionality
1. Coordinates Mapping:
- A map (coords) stores coordinates of each character from the input. For each character, all the coordinates where it appears are stored as Point objects.
#+BEGIN_SRC java
for (int i = 0; i < in.size(); i++) {
for (int j = 0; j < in.get(i).length(); j++) {
coords.putIfAbsent(in.get(i).charAt(j), new ArrayList<>());
coords.get(in.get(i).charAt(j)).add(new Point(i, j));
}
}
#+END_SRC
2. Flood Fill Algorithm with Stack:
- A flood-fill algorithm is used to explore areas connected by the same character. The algorithm uses a stack to explore adjacent points recursively.
#+BEGIN_SRC java
ArrayDeque<Point> stack = new ArrayDeque<>();
stack.push(co);
while (!stack.isEmpty()) {
var cur = stack.pop();
// explore neighbors
}
#+END_SRC
3. Fence Counting:
- For each region, the number of "fences" (edges where the character changes) is counted. The algorithm checks for boundaries or differing characters adjacent to each point.
#+BEGIN_SRC java
if (nd.x < 0 || nd.y < 0 || nd.x >= in.size() || nd.y >= in.get(0).length()) {
fence++;
} else if (in.get(nd.x).charAt(nd.y) != ch) {
fence++;
}
#+END_SRC
4. Side Fetching Logic:
- The fetchSides() method computes the number of "sides" based on the placement of fences, counting how the fences are arranged along rows and columns.
#+BEGIN_SRC java
for (var xx : cols.keySet()) {
var xl = cols.get(xx);
Collections.sort(xl);
// logic for sorting and counting sides
}
#+END_SRC
5. Area and Fence Calculations:
- For each character, the area (number of connected points) and the number of fences are calculated. The result is a product of area and fence count.
#+BEGIN_SRC java
ret += area * fence;
p2 += area * fetchSides(fences);
#+END_SRC
*** Core Logic and Functionality
- Coordinates mapping using a Map<Character, List<Point>> to track character positions.
- Flood-fill algorithm using a stack to explore regions of connected characters.
- Fence counting logic to identify boundaries and different characters.
- Side fetching logic to count the number of fences arranged along rows and columns.
- Area and fence calculations for each region to compute the final result.
* Day 13
** Part 1: Parsing Input and Solving Simultaneous Equations
- Goal: Parse input, extract button coefficients and prize values, solve simultaneous equations to find valid token costs.
- Key Concepts:
- Input parsing using BufferedReader.
- Using determinants to solve simultaneous equations.
- Validating solutions for constraints (non-negative integers, valid m/n).
- Code Snippets:
- Parsing Input:
#+BEGIN_SRC java
if (line.startsWith("Button A:")) {
String[] parts = line.split(":")[1].split(",");
current = new ButtonPrize();
current.buttonAX = Integer.parseInt(parts[0].trim().split("\\+")[1]);
current.buttonAY = Integer.parseInt(parts[1].trim().split("\\+")[1]);
}
#+END_SRC
- Solving Simultaneous Equations:
#+BEGIN_SRC java
int determinant = buttonAX * buttonBY - buttonBX * buttonAY;
if (determinant == 0) return 0; // No solution
long mNumerator = prizeX * buttonBY - prizeY * buttonBX;
long nNumerator = prizeY * buttonAX - prizeX * buttonAY;
if (mNumerator % determinant != 0 || nNumerator % determinant != 0) return 0;
long m = mNumerator / determinant;
long n = nNumerator / determinant;
return (m < 0 || n < 0) ? 0 : m * 3 + n; // Calculate token costs
#+END_SRC
- Challenges Faced:
- Edge cases with determinant = 0 or invalid input format.
- Ensuring no negative values for m/n.
** Part 2: Transforming Input Data
- Goal: Modify prize values with a fixed offset before calculations.
- Key Concepts:
- Transforming input data programmatically.
- Reusing the existing calculation logic after transformation.
- Code Snippets:
- Prepending Offset to Prize Values:
#+BEGIN_SRC java
private void prependZeroes(ButtonPrize bp) {
bp.setPrizeX(bp.getPrizeX() + 10000000000000L);
bp.setPrizeY(bp.getPrizeY() + 10000000000000L);
}
#+END_SRC
- Reusing Logic:
#+BEGIN_SRC java
for (ButtonPrize bp : data) {
prependZeroes(bp);
count_part2 += calculateSimultaneousEquations(bp.getButtonAX(), bp.getButtonAY(),
bp.getButtonBX(), bp.getButtonBY(),
bp.getPrizeX(), bp.getPrizeY());
}
#+END_SRC
- Challenges Faced:
- Avoiding modification of original input logic while adding transformations.
- Maintaining readability and modularity.
* Day 14
** Part 1: Simulating Robot Movement
- Concept: Simulating the movement of robots on a grid, wrapping their positions around the edges.
- The grid has dimensions 101x103, and the robot positions wrap around when they move out of bounds.
- Wrapping is implemented using a helper method:
#+BEGIN_SRC java
public static int wrap(int value, int max) {
return ((value % max) + max) % max;
}
#+END_SRC
- Key Learning: Efficiently handling movement on a toroidal grid (wrap-around behavior).
- Quadrant Assignment:
- Robots are excluded from the middle row and column (x=50, y=51).
- Quadrant assignments are based on the x and y positions:
#+BEGIN_SRC java
if (x < 50 && y < 51) {
quadrantCounts[0]++; // Top-left
} else if (x >= 50 && y < 51) {
quadrantCounts[1]++; // Top-right
} else if (x < 50 && y >= 51) {
quadrantCounts[2]++; // Bottom-left
} else if (x >= 50 && y >= 51) {
quadrantCounts[3]++; // Bottom-right
}
#+END_SRC
- Safety Factor Calculation:
- The safety factor is the product of the number of robots in each quadrant:
#+BEGIN_SRC java
int safetyFactor = 1;
for (int count : quadrantCounts) {
safetyFactor *= count;
}
#+END_SRC
** Part 2: Identifying Patterns in Robot Positions
- Concept: Simulating grid states to find a specific pattern of robot alignment.
- Robots move based on their initial velocity, and their positions are updated iteratively.
- A grid is used to track robot positions, and columns are checked for specific patterns.
- Key Learning: Efficiently detecting consecutive robot positions in a grid column.
- Grid Initialization:
- A helper method initializes a 2D grid with given dimensions:
#+BEGIN_SRC java
private int[][] initializeGrid(int rows, int cols) {
return new int[rows][cols];
}
#+END_SRC
- Position Calculation:
- New positions are computed using the robot's velocity and current step, with wrapping:
#+BEGIN_SRC java
private int[] calculateNewPosition(int[] position, int[] velocity, int step, int[] tileDimensions) {
return new int[] {
(position[0] + step * (tileDimensions[0] + velocity[0])) % tileDimensions[0],
(position[1] + step * (tileDimensions[1] + velocity[1])) % tileDimensions[1]
};
}
#+END_SRC
- Pattern Detection:
- A helper method checks for consecutive robots in a column:
#+BEGIN_SRC java
private boolean hasConsecutiveInRow(List<Integer> positions, int requiredConsecutive) {
Collections.sort(positions);
int consecutiveCount = 0;
for (int i = 1; i < positions.size(); i++) {
if (positions.get(i) - positions.get(i - 1) == 1) {
consecutiveCount++;
if (consecutiveCount >= requiredConsecutive) {
return true;
}
} else {
consecutiveCount = 0;
}
}
return false;
}
#+END_SRC
- Stopping Condition:
- Simulation stops when a column has at least requiredConsecutive robots aligned.
---
### Supporting Components
- Data Representation:
- PointAndVelocity encapsulates robot data, including position (PX, PY) and velocity (VX, VY):
#+BEGIN_SRC java
public static class PointAndVelocity {
private int PX;
private int PY;
private int VX;
private int VY;
// Getters and setters
public int getVX() { return VX; }
public void setVX(int vX) { VX = vX; }
public int getVY() { return VY; }
public void setVY(int vY) { VY = vY; }
public int getPX() { return PX; }
public void setPX(int pX) { PX = pX; }
public int getPY() { return PY; }
public void setPY(int pY) { PY = pY; }
}
#+END_SRC
- Input Parsing:
- Robot data is parsed from a file or input list. Each robot's position and velocity are extracted:
#+BEGIN_SRC java
private List<PointAndVelocity> getPointAndVelocities() {
List<PointAndVelocity> pvs = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(fetchFilePath()))) {
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(" ");
PointAndVelocity pav = new PointAndVelocity();
pav.setPX(Integer.parseInt(parts[0].split(",")[0].replace("p=", "")));
pav.setPY(Integer.parseInt(parts[0].split(",")[1].replace("p=", "")));
pav.setVX(Integer.parseInt(parts[1].split(",")[0].replace("v=", "")));
pav.setVY(Integer.parseInt(parts[1].split(",")[1].replace("v=", "")));
pvs.add(pav);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return pvs;
}
#+END_SRC
- Simulation Control:
- Part 1 iterates for 100 steps, while Part 2 continues until a pattern is found or the maximum steps are reached.
*