import "dart:convert"; import "dart:io"; import "dart:math"; import "package:args/command_runner.dart"; class Range implements Comparable { // The start and end of this range final int start; final int end; Range(this.start, this.end); @override String toString() { return "[$start..$end]"; } bool inRangeInclusive(int value) { return start <= value && value <= end; } int length() { return end - start + 1; } /// Ordered by start and then by end @override int compareTo(Range other) { int startCompare = start.compareTo(other.start); if (startCompare == 0) { return end.compareTo(other.end); } return startCompare; } } class RangeSet { final List ranges; RangeSet._internal(this.ranges); factory RangeSet(Iterable ranges) { // Create and sort ranges list List rangesList = List.from(ranges, growable: false); rangesList.sort(); // Combine and compact adjacent ranges List compactedList = [rangesList[0]]; for (Range range in rangesList.sublist(1)) { Range lastRange = compactedList.last; if (range.start <= lastRange.end) { compactedList[compactedList.length - 1] = Range( lastRange.start, max(lastRange.end, range.end), ); } else { compactedList.add(range); } } return RangeSet._internal(compactedList); } @override String toString() { return ranges.toString(); } Range operator [](int index) => ranges[index]; bool inRangeSetInclusive(int value) { for (var range in ranges) { if (range.inRangeInclusive(value)) { return true; } } return false; } int length() { return ranges.map((r) => r.length()).reduce((left, right) => left + right); } } class Day5Command extends Command { // The [name] and [description] properties must be defined by every // subclass. @override final name = "day5"; @override final description = "Run Advent of Code 2025 Day 5"; Day5Command() { // we can add command specific arguments here. // [argParser] is automatically created by the parent class. } // [run] may also return a Future. @override Future run() async { // [argResults] is set before [run()] is called and contains the flags/options // passed to this command. if (argResults!.rest.length != 1) { print( "Expected 1 positional arguments, found ${argResults!.rest.length}", ); exit(1); } var filePath = argResults!.rest[0]; print("Parsing file: $filePath"); var inputFile = File(filePath); var listOfRanges = inputFile .openRead() .transform(utf8.decoder) .transform(LineSplitter()) .takeWhile((line) => line.isNotEmpty) .map((line) { var splitLine = line.split("-"); return Range(int.parse(splitLine[0]), int.parse(splitLine[1])); }) .toList(); List listOfIds = await inputFile .openRead() .transform(utf8.decoder) .transform(LineSplitter()) .skipWhile((line) => line.isNotEmpty) .skipWhile((line) => line.isEmpty) .map((line) { return int.parse(line); }) .toList(); RangeSet freshIdRanges = RangeSet(await listOfRanges); //print(freshIdRanges); //print(listOfIds); int freshCount = 0; for (int id in listOfIds) { if (freshIdRanges.inRangeSetInclusive(id)) { freshCount++; } } print("Fresh IDs: $freshCount"); print("Total ingredient ids considered fresh: ${freshIdRanges.length()}"); } }