andreiz/advent-of-code-2023-swift

Day 2: Cube Conundrum

Opened this issue · 10 comments

Part 1 is pretty simple. Loop through games, then for each try in the game check if any red/green/blue exceeds the max.

Didn't have Copilot working yet, so started with GPT prompting again.

After a bit of back and forth about passing the data and structuring the counts it produced this function that checks each game's validity:

let CUBE_LIMITS: [String: Int] = ["red": 12, "green": 13, "blue": 14]

func isGamePossible(gameData: String) -> (isPossible: Bool, violatingCube: String?, count: Int?) {
    let tries = gameData.split(separator: ";")
    for tryData in tries {
        // Initialize cube counts for each try.
        var cubeCounts = ["red": 0, "green": 0, "blue": 0]

        let colorDataArray = tryData.split(separator: ",")
        for colorData in colorDataArray {
            let parts = colorData.split(separator: " ")
            let count = Int(parts[0].trimmingCharacters(in: .whitespaces)) ?? 0
            let color = String(parts[1].trimmingCharacters(in: .whitespaces))

            cubeCounts[color, default: 0] += count
            if let limit = CUBE_LIMITS[color], cubeCounts[color, default: 0] > limit {
                return (false, color, cubeCounts[color]) // Return false, violating cube color, and count
            }
        }
    }

    return (true, nil, nil) // Return true with no violations
}

I pointed out that it's trying to sum the per-color counts for all the tries, instead of treating them independently. That, and that the cubeCounts is unnecessary if we check against the limits directly. Updated result:

func isGamePossible(gameData: String) -> (isPossible: Bool, violatingCube: String?, count: Int?) {
    let tries = gameData.split(separator: ";")
    for tryData in tries {
        let colorDataArray = tryData.split(separator: ",")
        for colorData in colorDataArray {
            let parts = colorData.split(separator: " ")
            let count = Int(parts[0].trimmingCharacters(in: .whitespaces)) ?? 0
            let color = String(parts[1].trimmingCharacters(in: .whitespaces))

            // Check if the count exceeds the limit for each color.
            if let limit = CUBE_LIMITS[color], count > limit {
                return (false, color, count) // Return false, violating cube color, and count
            }
        }
    }

    return (true, nil, nil) // Return true with no violations
}

I wasn't sure what ?? was, so I asked.

In Swift, the ?? operator is known as the "nil coalescing operator." It is used to provide a default value for an optional in case the optional is nil.

Now we just need to read in a file, iterate over the lines, and sum the game IDs that don't violate the constraints.

func processFile(filename: String) {
// Variable to keep track of the sum of game IDs for possible games.
var possibleGameSum = 0
// Try to read the contents of the file.
if let contents = try? String(contentsOfFile: filename) {
// Split the file contents into individual lines.
let lines = contents.components(separatedBy: .newlines)
for line in lines {
if line.isEmpty { continue }
// Split each line into game number and game data separated by colon.
let parts = line.split(separator: ":")
let gameNumber = Int(parts[0].split(separator: " ")[1].trimmingCharacters(in: .whitespaces)) ?? 0
let gameData = String(parts[1].trimmingCharacters(in: .whitespaces))
// Check if the game is possible and get details about any violations.
let (isPossible, violatingCube, count) = isGamePossible(gameData: gameData)
if isPossible {
print("Game \(gameNumber) [\(gameData)]: possible")
possibleGameSum += gameNumber
} else {
print("Game \(gameNumber) [\(gameData)]: impossible due to \(violatingCube!) cube count \(count!) exceeding limit")
}
}
print("Sum of possible game numbers: \(possibleGameSum)")
} else {
print("Error reading file")
}
}

I do like how you can return multiple (and optional) values from a func and then unwrap them.

Ran this on the test input and the result was:

Game 1 [3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green]: possible
Game 2 [1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue]: possible
Game 3 [8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red]: impossible due to red cube count 20 exceeding limit
Game 4 [1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red]: impossible due to blue cube count 15 exceeding limit
Game 5 [6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green]: possible
Sum of possible game numbers: 8

Which is correct.

Ok, part 2. Need to keep track of max count of each try and return it along with the game result (whether it's possible or impossible).

// Define a function to check if each game is possible and calculate fewest cubes needed.
func analyzeGame(gameData: String) -> (isPossible: Bool, fewestCubes: [String: Int]) {
var maxCubeCounts = ["red": 0, "green": 0, "blue": 0]
var isPossible = true
let tries = gameData.split(separator: ";")
for tryData in tries {
var currentTryCounts = ["red": 0, "green": 0, "blue": 0]
let colorDataArray = tryData.split(separator: ",")
for colorData in colorDataArray {
let parts = colorData.split(separator: " ")
let count = Int(parts[0].trimmingCharacters(in: .whitespaces)) ?? 0
let color = String(parts[1].trimmingCharacters(in: .whitespaces))
// Assign the count for each color in the current try.
currentTryCounts[color] = count
// Check if the count for each color exceeds its respective limit.
if let limit = CUBE_LIMITS[color], count > limit {
isPossible = false
}
}
// Update the maximum count for each color after each try.
for (color, count) in currentTryCounts {
maxCubeCounts[color] = max(maxCubeCounts[color, default: 0], count)
}
}
return (isPossible, maxCubeCounts) // Return if the game is possible and the fewest cubes needed.
}

I wondered how to easily multiply the values of an array and ChatGPT suggested:

let (isPossible, fewestCubes) = analyzeGame(gameData: gameData)
let cubeProduct = fewestCubes.values.reduce(1, *)
cubeProductSum += cubeProduct

Neat. I like that operators are first-class entities.

Printing out cubeProductSum now and... it's passed.