|
| 1 | +// |
| 2 | +// CubeConundrum.swift |
| 3 | +// |
| 4 | +// Created by Matthew Judy on 2023-12-05. |
| 5 | +// |
| 6 | + |
| 7 | + |
| 8 | +import ArgumentParser |
| 9 | +import Foundation |
| 10 | +import Shared |
| 11 | + |
| 12 | + |
| 13 | +/// Day 2 : Cube Conundrum |
| 14 | +/// |
| 15 | +/// # Part One |
| 16 | +/// |
| 17 | +/// You're launched high into the atmosphere! The apex of your trajectory just |
| 18 | +/// barely reaches the surface of a large island floating in the sky. |
| 19 | +/// You gently land in a fluffy pile of leaves. It's quite cold, but you don't |
| 20 | +/// see much snow. An Elf runs over to greet you. |
| 21 | +/// |
| 22 | +/// The Elf explains that you've arrived at **Snow Island** and apologizes for |
| 23 | +/// the lack of snow. He'll be happy to explain the situation, but it's a bit |
| 24 | +/// of a walk, so you have some time. They don't get many visitors up here; |
| 25 | +/// would you like to play a game in the meantime? |
| 26 | +/// |
| 27 | +/// As you walk, the Elf shows you a small bag and some cubes which are either |
| 28 | +/// red, green, or blue. Each time you play this game, he will hide a secret |
| 29 | +/// number of cubes of each color in the bag, and your goal is to figure out |
| 30 | +/// information about the number of cubes. |
| 31 | +/// |
| 32 | +/// To get information, once a bag has been loaded with cubes, the Elf will |
| 33 | +/// reach into the bag, grab a handful of random cubes, show them to you, and |
| 34 | +/// then put them back in the bag. He'll do this a few times per game. |
| 35 | +/// |
| 36 | +/// You play several games and record the information from each game (your |
| 37 | +/// puzzle input). Each game is listed with its ID number (like the `11` in |
| 38 | +/// `Game 11: ...`) followed by a semicolon-separated list of subsets of cubes |
| 39 | +/// that were revealed from the bag (like `3 red, 5 green, 4 blue`). |
| 40 | +/// |
| 41 | +/// For example, the record of a few games might look like this: |
| 42 | +/// |
| 43 | +/// ``` |
| 44 | +/// Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green |
| 45 | +/// Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue |
| 46 | +/// Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red |
| 47 | +/// Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red |
| 48 | +/// Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green |
| 49 | +/// ``` |
| 50 | +/// |
| 51 | +/// In game 1, three sets of cubes are revealed from the bag (and then put back |
| 52 | +/// again). The first set is 3 blue cubes and 4 red cubes; the second set is |
| 53 | +/// 1 red cube, 2 green cubes, and 6 blue cubes; the third set is only |
| 54 | +/// 2 green cubes. |
| 55 | +/// |
| 56 | +/// The Elf would first like to know which games would have been possible if |
| 57 | +/// the bag contained **only 12 red cubes, 13 green cubes, and 14 blue cubes**? |
| 58 | +/// |
| 59 | +/// In the example above, games 1, 2, and 5 would have been **possible** if |
| 60 | +/// the bag had been loaded with that configuration. However, game 3 would have |
| 61 | +/// been **impossible** because at one point the Elf showed you 20 red cubes |
| 62 | +/// at once; similarly, game 4 would also have been impossible because the Elf |
| 63 | +/// showed you 15 blue cubes at once. If you add up the IDs of the games that |
| 64 | +/// would have been possible, you get **`8`**. |
| 65 | +/// |
| 66 | +/// Determine which games would have been possible if the bag had been loaded |
| 67 | +/// with only 12 red cubes, 13 green cubes, and 14 blue cubes. **What is the |
| 68 | +/// sum of the IDs of those games?** |
| 69 | +/// |
| 70 | +/// # Part Two |
| 71 | +/// |
| 72 | +/// The Elf says they've stopped producing snow because they aren't getting any |
| 73 | +/// **water**! He isn't sure why the water stopped; however, he can show you |
| 74 | +/// how to get to the water source to check it out for yourself. It's just |
| 75 | +/// up ahead! |
| 76 | +/// |
| 77 | +/// As you continue your walk, the Elf poses a second question: in each game |
| 78 | +/// you played, what is the **fewest number of cubes of each color** that could |
| 79 | +/// have been in the bag to make the game possible? |
| 80 | +/// |
| 81 | +/// Again consider the example games from earlier. |
| 82 | +/// |
| 83 | +/// * In game 1, the game could have been played with as few as 4 red, 2 green, |
| 84 | +/// and 6 blue cubes. If any color had even one fewer cube, the game would |
| 85 | +/// have been impossible. |
| 86 | +/// * Game 2 could have been played with a minimum of 1 red, 3 green, and |
| 87 | +/// 4 blue cubes. |
| 88 | +/// * Game 3 must have been played with at least 20 red, 13 green, and |
| 89 | +/// 6 blue cubes. |
| 90 | +/// * Game 4 required at least 14 red, 3 green, and 15 blue cubes. |
| 91 | +/// * Game 5 needed no fewer than 6 red, 3 green, and 2 blue cubes in the bag. |
| 92 | +/// |
| 93 | +/// The **power** of a set of cubes is equal to the numbers of red, green, |
| 94 | +/// and blue cubes multiplied together. The power of the minimum set of cubes |
| 95 | +/// in game 1 is `48`. In games 2-5 it was `12`, `1560`, `630`, and `36`, |
| 96 | +/// respectively. Adding up these five powers produces the sum `2286`. |
| 97 | +/// |
| 98 | +/// For each game, find the minimum set of cubes that must have been present. |
| 99 | +/// **What is the sum of the power of these sets?** |
| 100 | +/// |
| 101 | +@main |
| 102 | +struct CubeConundrum: AsyncParsableCommand |
| 103 | +{ |
| 104 | + /// Enumeration for argument which activates "Part Two" behavior |
| 105 | + enum Mode: String, ExpressibleByArgument, CaseIterable |
| 106 | + { |
| 107 | + case sumOfGameIDs |
| 108 | + case sumOfGamePowers |
| 109 | + } |
| 110 | + |
| 111 | + @Option(help: "'sumOfGameIDs' or 'sumOfGamePowers'") |
| 112 | + var mode: Mode = .sumOfGamePowers |
| 113 | +} |
| 114 | + |
| 115 | + |
| 116 | +struct Grab |
| 117 | +{ |
| 118 | + let redCubes : Int |
| 119 | + let greenCubes : Int |
| 120 | + let blueCubes : Int |
| 121 | + |
| 122 | + func isPossible(for loadout: Grab) -> Bool |
| 123 | + { |
| 124 | + [\Grab.redCubes, \Grab.greenCubes, \Grab.blueCubes] |
| 125 | + .reduce(true) { |
| 126 | + $0 && (loadout[keyPath: $1] >= self[keyPath: $1]) |
| 127 | + } |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | + |
| 132 | +extension Grab |
| 133 | +{ |
| 134 | + /// Initialize with segment of input line |
| 135 | + /// - Parameter input: input line representing a single grab, |
| 136 | + /// e.g., `1 green, 3 red, 6 blue` |
| 137 | + init(input: String) throws |
| 138 | + { |
| 139 | + var inputRed = 0 |
| 140 | + var inputGreen = 0 |
| 141 | + var inputBlue = 0 |
| 142 | + |
| 143 | + try input.components(separatedBy: ", ").forEach |
| 144 | + { |
| 145 | + guard case let gemInfo = $0.components(separatedBy: " "), (gemInfo.count == 2), |
| 146 | + let gemCount: Int = Int(gemInfo[0]), |
| 147 | + case let gemColor: String = gemInfo[1] |
| 148 | + else { throw AteShit(whilst: .parsing, input) } |
| 149 | + |
| 150 | + switch gemColor |
| 151 | + { |
| 152 | + case "red" : inputRed = gemCount |
| 153 | + case "green" : inputGreen = gemCount |
| 154 | + case "blue" : inputBlue = gemCount |
| 155 | + default: throw AteShit(whilst: .parsing, input) |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + self = Grab(redCubes: inputRed, greenCubes: inputGreen, blueCubes: inputBlue) |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | + |
| 164 | +struct Game |
| 165 | +{ |
| 166 | + let id : Int |
| 167 | + let grabs : [Grab] |
| 168 | + |
| 169 | + var maxRed : Int { self.grabs.map(\.redCubes) .max() ?? 0 } |
| 170 | + var maxGreen : Int { self.grabs.map(\.greenCubes).max() ?? 0 } |
| 171 | + var maxBlue : Int { self.grabs.map(\.blueCubes) .max() ?? 0 } |
| 172 | + var minLoadout : Grab { Grab(redCubes: self.maxRed, greenCubes: self.maxGreen, blueCubes: self.maxBlue ) } |
| 173 | + |
| 174 | + func isPossible(for loadout: Grab) -> Bool |
| 175 | + { |
| 176 | + self.grabs.first { $0.isPossible(for: loadout) == false } == nil |
| 177 | + } |
| 178 | + |
| 179 | + /// Initialize with a full input line |
| 180 | + /// - Parameter input: input line representing a full game with multiple |
| 181 | + /// grabs, e.g., `Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green` |
| 182 | + init(input: String) throws |
| 183 | + { |
| 184 | + guard case let inputSections : [String] = input.components(separatedBy: ": "), (inputSections.count == 2), |
| 185 | + let gameID : Int = Int(inputSections[0].components(separatedBy: " ")[1]), |
| 186 | + case let grabInputs : [String] = inputSections[1].components(separatedBy: "; "), (grabInputs.count > 0) |
| 187 | + else { throw AteShit(whilst: .parsing, input) } |
| 188 | + |
| 189 | + self.id = gameID |
| 190 | + self.grabs = try grabInputs.reduce(into: []) { $0.append(try Grab(input: $1)) } |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +// MARK: - Command Execution |
| 195 | + |
| 196 | +extension CubeConundrum |
| 197 | +{ |
| 198 | + mutating func run() async throws |
| 199 | + { |
| 200 | + let input : AsyncLineSequence = FileHandle.standardInput.bytes.lines |
| 201 | + let loadout : Grab = Grab(redCubes: 12, greenCubes: 13, blueCubes: 14) |
| 202 | + let output : Int = try await input.reduce(0) |
| 203 | + { |
| 204 | + let game : Game = try Game(input: $1) |
| 205 | + |
| 206 | + switch self.mode |
| 207 | + { |
| 208 | + case .sumOfGameIDs: |
| 209 | + return if game.isPossible(for: loadout) { $0 + game.id } else { $0 } |
| 210 | + |
| 211 | + case .sumOfGamePowers: |
| 212 | + return ( $0 + (game.minLoadout.redCubes * game.minLoadout.greenCubes * game.minLoadout.blueCubes) ) |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + print(output) |
| 217 | + } |
| 218 | +} |
| 219 | + |
0 commit comments