Skip to content

Commit

Permalink
Merge branch 'main' into day7_perf
Browse files Browse the repository at this point in the history
  • Loading branch information
dancarroll committed Dec 9, 2024
2 parents 46e79e5 + e718744 commit d35fb01
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 1 deletion.
2 changes: 2 additions & 0 deletions bin/aoc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'day4.dart' as day4;
import 'day5.dart' as day5;
import 'day6.dart' as day6;
import 'day7.dart' as day7;
import 'day8.dart' as day8;

void main(List<String> arguments) async {
print('');
Expand All @@ -16,6 +17,7 @@ void main(List<String> arguments) async {
day5.main,
day6.main,
day7.main,
day8.main,
]) {
await day(arguments);
print('');
Expand Down
11 changes: 11 additions & 0 deletions bin/day8.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:aoc_2024/lib.dart';
import 'package:aoc_2024/day8/part_1.dart' as part1;
import 'package:aoc_2024/day8/part_2.dart' as part2;

Future<void> main(List<String> arguments) async {
await runDay(
day: 7,
part1: part1.calculate,
part2: part2.calculate,
);
}
10 changes: 10 additions & 0 deletions lib/day7/part_1.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import 'package:aoc_2024/lib.dart';

import 'shared.dart';

/// Given a list of equations without operators, determine which
/// equations could be valid, and return the sum of all equations
/// which could be valid.
///
/// Equations are given of the form:
/// 3267: 81 40 27
///
/// The first value is the equation result, and the remaining value
/// are the equation operands. The only valid operators are add (`+`)
/// and multiply (`*`).
Future<int> calculate(Resources resources) async {
final equations = await loadData(resources);

Expand Down
2 changes: 2 additions & 0 deletions lib/day7/part_2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:aoc_2024/lib.dart';

import 'shared.dart';

/// Following from part 1, but introduce a new operator:
/// concatenate (`||`).
Future<int> calculate(Resources resources) async {
final equations = await loadData(resources);

Expand Down
17 changes: 17 additions & 0 deletions lib/day8/part_1.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:aoc_2024/lib.dart';

import 'shared.dart';

/// Find the number of antinodes from a map of antenna locations.
///
/// An antinode occurs at any point that is perfectly in line with two
/// antennas of the same frequency - but only when one of the antennas
/// is twice as far away as the other.
///
/// This function should return the number of locations on the map that
/// are antinodes (some locations may be antinodes for multiple
/// frequencies, so only count those once).
Future<int> calculate(Resources resources) async {
final frequencyMap = await loadData(resources);
return frequencyMap.antinodes(includeHarmonics: false).length;
}
18 changes: 18 additions & 0 deletions lib/day8/part_2.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:aoc_2024/lib.dart';

import 'shared.dart';

/// Continuing from part 1, include all harmonics of the frequency
/// when determining the number of antinode locations.
///
/// This mean including any grid position that is exactly in line with
/// at least two antennas of the same frequency, regardless of distance.
/// It also means that the antenna locations themselves can be considered
/// as antinode locations.
///
/// Return value is the same as part 1: number of unique locations that
/// are antinodes.
Future<int> calculate(Resources resources) async {
final frequencyMap = await loadData(resources);
return frequencyMap.antinodes(includeHarmonics: true).length;
}
127 changes: 127 additions & 0 deletions lib/day8/shared.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'dart:math';

import 'package:aoc_2024/lib.dart';

typedef Location = Point<int>;

/// Represents frequencies being broadcast from antennas within a map.
final class FrequencyMap {
/// Map of frequenct to locations with an antenna broadcasting that
/// frequency.
final Map<String, List<Location>> antennaLocations;

/// Height bound of the map.
final int height;

/// Width bound of the map.
final int width;

FrequencyMap(
{required this.antennaLocations,
required this.height,
required this.width});

/// Returns true if the given location fits within the bounds of
/// the map.
bool inBounds(final Location location) {
return location.x >= 0 &&
location.x < height &&
location.y >= 0 &&
location.y < width;
}

/// Generates a list of all antinodes for this map. Antinodes are points
/// in which the broadcast from two antennas of the same frequency are
/// amplified.
///
/// If [includeHarmonics] is false, this will generate two antinodes per
/// pair of antennas on the same frequency. The antinodes will be on either
/// side of each antenna, where one antinode is twice as far from one antenna.
///
/// If [includeHarmonics] is true, this will generate all antinodes along the
/// straightline path between two antennas. Each antinode is spaced out
/// according to the distance between the two antennas.
Set<Location> antinodes({bool includeHarmonics = false}) {
Set<Location> antinodes = {};

for (final antennaLocations in antennaLocations.values) {
for (final pair in pairs(antennaLocations)) {
antinodes.addAll(_generateAntinodesUntilOutOfBounds(
a: pair.$1, b: pair.$2, includeHarmonics: includeHarmonics));
}
}

return antinodes;
}

/// Generates the list of antinodes for a single pair of antenna locations.
/// See [antinodes] for a description of the generation.
Set<Location> _generateAntinodesUntilOutOfBounds(
{required Location a,
required Location b,
required bool includeHarmonics}) {
final hop = a - b;
Set<Location> locations = {};

final List<({Location starting, Location Function(Location) hopFunc})>
directionFunctions = [
(starting: a, hopFunc: (l) => l + hop),
(starting: a, hopFunc: (l) => l - hop),
(starting: b, hopFunc: (l) => l + hop),
(starting: b, hopFunc: (l) => l - hop),
];

for (final direction in directionFunctions) {
var addedOneAntinode = false;
var next = direction.hopFunc(direction.starting);
while (
// If not including all harmonics, only process this loop until
// an antinode has been added.
(includeHarmonics || !addedOneAntinode) &&
// The two antenna locations themselves are only eligible to
// be considered antinodes when including all harmonics.
(includeHarmonics || (next != a && next != b)) &&
// Stop processing when encountering an out-of-bounds
// location.
inBounds(next) &&
// If a location has already been seen, we can stop processing.
// This is because [directionFunctions] will attempt to process
// each direction from both antenna locations. This allows us to
// avoid figuring out which direction to travel from a given
// antenna, but avoid processing the same locations twice.
!locations.contains(next)) {
locations.add(next);
addedOneAntinode = true;
next = direction.hopFunc(next);
}
}

return locations;
}
}

/// Loads data from file, which is a map of frequency to
/// locations (points on a map).
Future<FrequencyMap> loadData(Resources resources) async {
final file = resources.file(Day.day8);
final lines = await file.readAsLines();

Map<String, List<Location>> frequencies = {};

for (int r = 0; r < lines.length; r++) {
final line = lines[r];
for (int c = 0; c < line.length; c++) {
if (line[c] == '.') {
continue;
}

final frequency = frequencies.putIfAbsent(line[c], () => []);
frequency.add(Location(r, c));
}
}

return FrequencyMap(
antennaLocations: frequencies,
height: lines.length,
width: lines[0].length);
}
1 change: 1 addition & 0 deletions lib/lib.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'shared/resources.dart';
export 'shared/runner.dart';
export 'shared/utils.dart';
3 changes: 2 additions & 1 deletion lib/shared/resources.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ enum Day {
day4,
day5,
day6,
day7;
day7,
day8;
}

/// Manager for loading a resource file, based on type and day.
Expand Down
9 changes: 9 additions & 0 deletions lib/shared/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
List<(T, T)> pairs<T>(List<T> items) {
List<(T, T)> records = [];
for (int i = 0; i < items.length - 1; i++) {
for (int j = i + 1; j < items.length; j++) {
records.add((items[i], items[j]));
}
}
return records;
}
12 changes: 12 additions & 0 deletions resources/sample_data/day8.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
30 changes: 30 additions & 0 deletions test/day8_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:aoc_2024/lib.dart';
import 'package:aoc_2024/day8/part_1.dart' as part1;
import 'package:aoc_2024/day8/part_2.dart' as part2;
import 'package:test/test.dart';

void main() {
group('sample data', tags: 'sample-data', () {
final resources = Resources.sample;

test('part1', () async {
expect(await part1.calculate(resources), 14);
});

test('part2', () async {
expect(await part2.calculate(resources), 34);
});
});

group('real data', tags: 'real-data', () {
final resources = Resources.real;

test('part1', () async {
expect(await part1.calculate(resources), 394);
});

test('part2', () async {
expect(await part2.calculate(resources), 1277);
});
});
}

0 comments on commit d35fb01

Please sign in to comment.