AoC/2020/day19/main.swift

148 lines
3.5 KiB
Swift
Raw Normal View History

2020-12-19 09:40:07 +01:00
import Foundation
let input = loadData(day: 19)
let scanner = Scanner(string: input)
scanner.charactersToBeSkipped = .whitespaces
enum Rule: Equatable {
2020-12-19 09:40:07 +01:00
case character(Character)
indirect case sequence(Rule, Rule)
indirect case alternative(Rule, Rule)
case reference(Int)
}
typealias RuleSet = [Int: Rule]
extension Scanner {
func parseRuleSet() -> RuleSet {
var result: [Int: Rule] = [:]
while !string("\n") {
guard let (index, rule) = parseRuleDefinition() else { fatalError() }
result[index] = rule
}
return result
}
func parseRuleDefinition() -> (Int, Rule)? {
guard let label = parseLabel(),
let rule = parseRule(),
string("\n")
else { return nil }
return (label, rule)
}
func parseLabel() -> Int? {
guard let num = scanInt(),
string(":")
else { return nil }
return num
}
func parseRule() -> Rule? {
if string("\""), let character = scanCharacter(), string("\"") {
return .character(character)
}
guard let sequence = parseSequence() else { return nil }
if string("|"), let rhs = parseSequence() {
return .alternative(sequence, rhs)
}
return sequence
}
func parseReference() -> Rule? {
guard let num = scanInt() else { return nil }
return .reference(num)
}
func parseSequence() -> Rule? {
guard var sequence = parseReference() else {
return nil
}
while let rhs = parseReference() {
sequence = .sequence(sequence, rhs)
}
return sequence
}
}
let rules = scanner.parseRuleSet()
extension Rule {
2020-12-19 13:20:10 +01:00
func matches(rules: RuleSet, _ s: Substring) -> Set<Substring> {
2020-12-19 09:40:07 +01:00
switch self {
case .character(let ch):
if s.first == ch {
2020-12-19 13:17:41 +01:00
return [s.dropFirst()]
2020-12-19 09:40:07 +01:00
}
2020-12-19 13:17:41 +01:00
return []
2020-12-19 09:40:07 +01:00
case let .sequence(first, second):
2020-12-19 13:20:10 +01:00
let firstMatches = first.matches(rules: rules, s)
return Set(firstMatches.flatMap { second.matches(rules: rules, $0) })
2020-12-19 09:40:07 +01:00
case let .alternative(first, second):
2020-12-19 13:20:10 +01:00
let firstMatch = first.matches(rules: rules, s)
let secondMatch = second.matches(rules: rules, s)
2020-12-19 09:40:07 +01:00
2020-12-19 13:20:10 +01:00
return firstMatch.union(secondMatch)
2020-12-19 09:40:07 +01:00
case .reference(let index):
2020-12-19 13:20:10 +01:00
return rules[index]!.matches(rules: rules, s)
2020-12-19 09:40:07 +01:00
}
}
2020-12-19 13:20:10 +01:00
func matches(rules: RuleSet, _ s: String) -> Bool {
let result = matches(rules: rules, s[...])
2020-12-19 13:17:41 +01:00
return result.contains("")
2020-12-19 09:40:07 +01:00
}
}
extension Scanner {
func scanLine() -> String? {
guard let result = scanUpToCharacters(from: .newlines),
scanCharacters(from: .newlines) != nil else {
return nil
}
return result
}
func readLines() -> [String] {
var lines = [String]()
while let line = scanner.scanLine() {
lines.append(line)
}
return lines
}
}
let messages = scanner.readLines()
2020-12-19 13:17:41 +01:00
let start = Rule.reference(0)
2020-12-19 13:20:10 +01:00
print("part 1:", messages.lazy.filter { start.matches(rules: rules, $0) }.count)
let changedRules = """
8: 42 | 42 8
11: 42 31 | 42 11 31
"""
let changeScanner = Scanner(string: changedRules)
changeScanner.charactersToBeSkipped = .whitespaces
let ruleUpdates = changeScanner.parseRuleSet()
let newRules = rules.merging(ruleUpdates) { $1 }
2020-12-19 09:40:07 +01:00
2020-12-19 13:20:10 +01:00
print("part 2:", messages.filter { start.matches(rules: newRules, $0) }.count)