day_11.jl (5173B)
1 #!/usr/bin/env julia 2 # https://adventofcode.com/2022/day/11 3 using AdventOfCode 4 5 example = readlines(IOBuffer(""" 6 Monkey 0: 7 Starting items: 79, 98 8 Operation: new = old * 19 9 Test: divisible by 23 10 If true: throw to monkey 2 11 If false: throw to monkey 3 12 13 Monkey 1: 14 Starting items: 54, 65, 75, 74 15 Operation: new = old + 6 16 Test: divisible by 19 17 If true: throw to monkey 2 18 If false: throw to monkey 0 19 20 Monkey 2: 21 Starting items: 79, 60, 97 22 Operation: new = old * old 23 Test: divisible by 13 24 If true: throw to monkey 1 25 If false: throw to monkey 3 26 27 Monkey 3: 28 Starting items: 74 29 Operation: new = old + 3 30 Test: divisible by 17 31 If true: throw to monkey 0 32 If false: throw to monkey 1""")) 33 input = readlines("data/day_11.txt") 34 35 # Parsing this input is going to be a job, so I'm going to give myself 36 # permission to write as many tiny functions as I want to (easier to debug). 37 38 module MonkeyBusiness 39 mutable struct Monkey 40 id::Int 41 items::Vector{Int} 42 operation::Tuple{<:AbstractString,<:AbstractString} 43 divisible::Int 44 throw_id::Tuple{Int,Int} 45 considerations::Int 46 end 47 48 # This parses the text and creates a Monkey. Not sure if this is the normal 49 # Julia approach, but outer constructors are so flexible it seems 50 # reasonable? 51 function Monkey(monkeytext::Vector{<:AbstractString}) 52 monkeyregexes = [ 53 r"^Monkey (\d+):$", 54 r"^ Starting items: ([0-9, ]*)$", 55 r"^ Operation: new = old ([+*]) (old|\d+)$", 56 r"^ Test: divisible by (\d+)$", 57 r"^ If true: throw to monkey (\d+)$", 58 r"^ If false: throw to monkey (\d+)$" 59 ] 60 monkeymatches = map(match, monkeyregexes, monkeytext) 61 @assert !any(map(isnothing, monkeymatches)) 62 63 Monkey( 64 parse(Int, monkeymatches[1].captures[1]), 65 parse.(Int, split(monkeymatches[2].captures[1], ", ")), 66 Tuple(monkeymatches[3].captures), 67 parse(Int, monkeymatches[4].captures[1]), 68 ( 69 parse(Int, monkeymatches[5].captures[1]), 70 parse(Int, monkeymatches[6].captures[1]) 71 ), 72 0 73 ) 74 end 75 76 """ 77 Apply the operation to see how the worry level changes. 78 """ 79 function monkeyinspection!(monkey::Monkey, worry::Int) 80 if monkey.operation[2] == "old" 81 operand = worry 82 else 83 operand = parse(Int, monkey.operation[2]) 84 end 85 86 if monkey.operation[1] == "*" 87 worry * operand 88 else 89 worry + operand 90 end 91 end 92 93 """ 94 Test worry level and return the id of the monkey that will receive the item. 95 """ 96 function monkeytest(monkey::Monkey, worry::Int) 97 worry % monkey.divisible == 0 ? monkey.throw_id[1] : monkey.throw_id[2] 98 end 99 100 """ 101 Process one monkey's turn. 102 """ 103 function monkeyturn!(monkey::Monkey, 104 monkeys::Dict{Int, Monkey}, 105 part::Int) 106 while !isempty(monkey.items) 107 item = mod(monkeyinspection!(monkey, popfirst!(monkey.items)), 108 mapreduce(x->x.divisible, *, values(monkeys))) 109 part == 1 && (item รท= 3) 110 push!(monkeys[monkeytest(monkey, item)].items, item) 111 monkey.considerations += 1 112 end 113 end 114 115 function monkeyrounds!(monkeys::Dict{Int, Monkey}, numrounds::Int, 116 part::Int = 1) 117 monkeyids = sort(collect(keys(monkeys))) 118 for _ = 1:numrounds 119 for monkeyid = monkeyids 120 monkeyturn!(monkeys[monkeyid], monkeys, part) 121 end 122 end 123 end 124 125 """ 126 Split the input lines into (unparsed text representations) of each monkey. 127 128 Returns an array of string arrays. 129 """ 130 function split_monkeys(input::Vector{<:AbstractString}) 131 monkeystart = 1 132 monkeys = Vector{Vector{<:AbstractString}}() 133 for monkeyend = [findall(==(""), input); lastindex(input)+1] 134 push!(monkeys, input[monkeystart:monkeyend-1]) 135 monkeystart = monkeyend+1 136 end 137 monkeys 138 end 139 140 """ 141 Read the input and return a dictionary of monkeys. 142 """ 143 function makemonkeys(input::Vector{<:AbstractString}) 144 monkeys = map(Monkey, split_monkeys(input)) 145 Dict(zip([m.id for m in monkeys], monkeys)) 146 end 147 end 148 149 function part_1(input) 150 monkeys = MonkeyBusiness.makemonkeys(input) 151 MonkeyBusiness.monkeyrounds!(monkeys, 20) 152 reduce( 153 *, 154 partialsort(map(x->x.considerations, values(monkeys)), 155 1:2, rev = true) 156 ) 157 end 158 @assert part_1(example) == 10605 159 @info part_1(input) 160 161 function part_2(input) 162 monkeys = MonkeyBusiness.makemonkeys(input) 163 MonkeyBusiness.monkeyrounds!(monkeys, 10_000, 2) 164 reduce( 165 *, 166 partialsort(map(x->x.considerations, values(monkeys)), 167 1:2, rev = true) 168 ) 169 end 170 @assert part_2(example) == 2713310158 171 @info part_2(input)