diff --git a/Project.toml b/Project.toml index f2a7af2..d6a5d54 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Memoize = "c03570c3-d221-55d1-a50c-7939bbd78826" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] julia = "1.8" diff --git a/README.md b/README.md index e2bf8e0..3c4f012 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ This Julia package contains my solutions for [Advent of Code 2023](https://adven | 22 | [:white_check_mark:](https://adventofcode.com/2023/day/22) | 790.712 ms | 631.26 MiB | [:white_check_mark:](https://github.com/goggle/AdventOfCode2023.jl/blob/master/src/day22.jl) | | 23 | [:white_check_mark:](https://adventofcode.com/2023/day/23) | 2.979 s | 9.69 MiB | [:white_check_mark:](https://github.com/goggle/AdventOfCode2023.jl/blob/master/src/day23.jl) | | 24 | [:white_check_mark:](https://adventofcode.com/2023/day/24) | 41.181 ms | 49.71 MiB | [:white_check_mark:](https://github.com/goggle/AdventOfCode2023.jl/blob/master/src/day24.jl) | - +| 25 | [:white_check_mark:](https://adventofcode.com/2023/day/25) | 148.392 ms | 173.36 MiB | [:white_check_mark:](https://github.com/goggle/AdventOfCode2023.jl/blob/master/src/day25.jl) | The benchmarks have been measured on this machine: diff --git a/src/AdventOfCode2023.jl b/src/AdventOfCode2023.jl index 40b6267..1bd5d57 100644 --- a/src/AdventOfCode2023.jl +++ b/src/AdventOfCode2023.jl @@ -3,7 +3,7 @@ module AdventOfCode2023 using BenchmarkTools using Printf -solvedDays = 1:24 +solvedDays = 1:25 # Include the source files: for day in solvedDays ds = @sprintf("%02d", day) diff --git a/src/day25.jl b/src/day25.jl new file mode 100644 index 0000000..d9dc504 --- /dev/null +++ b/src/day25.jl @@ -0,0 +1,112 @@ +module Day25 + +using AdventOfCode2023 +using DataStructures +using Random + + +function day25(input::String = readInput(joinpath(@__DIR__, "..", "data", "day25.txt"))) + graph, total_size = parse_input(input) + solve(graph, total_size) +end + +function parse_input(input::AbstractString) + i = 1 + translations = Dict{String,Int}() + graph = Dict{Int, Set{Int}}() + for line ∈ eachsplit(rstrip(input), "\n") + nodes = split.(replace(line, ":" => ""), " ") + n = Int[] + for node ∈ nodes + if haskey(translations, node) + push!(n, translations[node]) + else + translations[node] = i + graph[i] = Set{Int}() + push!(n, i) + i += 1 + end + end + node = nodes[begin] + for n ∈ nodes[begin+1:end] + push!(graph[translations[node]], translations[n]) + push!(graph[translations[n]], translations[node]) + end + end + return graph, i - 1 +end + +function solve(graph::Dict{Int,Set{Int}}, total_size::Int) + edge_count = Dict{Tuple{Int,Int},Int}() + for i = 1:total_size + for j = i + 1:total_size + edge_count[(i, j)] = 0 + end + end + while true + for _ ∈ 1:10 + source = rand(1:total_size) + goals = Random.randsubseq(1:total_size, 0.1) + isempty(goals) && continue + prev = dijkstra(graph, source) + for goal ∈ goals + goal == source && continue + u = goal + while haskey(prev, u) + edge_count[minmax(u, prev[u])] += 1 + u = prev[u] + end + end + end + ignore_edges = [x[1] for x ∈ sort(collect(edge_count), by=last, rev=true)[begin:begin+2]] + start1, start2 = ignore_edges[1] + size1 = bfs_count_size(graph, start1, Set(ignore_edges)) + size2 = bfs_count_size(graph, start2, Set(ignore_edges)) + if size1 + size2 == total_size + return size1 * size2 + end + end +end + +function dijkstra(graph::Dict{Int,Set{Int}}, source::Int) + pq = PriorityQueue{Int,Int}() + dist = Dict{Int,Int}() + dist[source] = 0 + prev = Dict{Int,Int}() + for i ∈ eachindex(graph) + if i != source + dist[i] = typemax(Int) + prev[i] = 0 + end + pq[i] = dist[i] + end + while !isempty(pq) + u = dequeue!(pq) + for v ∈ graph[u] + alt = dist[u] + 1 + if alt < dist[v] + dist[v] = alt + prev[v] = u + pq[v] = alt + end + end + end + return prev +end + +function bfs_count_size(graph::Dict{Int,Set{Int}}, start::Int, ignore_edges::Set{Tuple{Int,Int}}) + visited = Set{Int}([start]) + queue = [n for n ∈ graph[start] if minmax(start, n) ∉ ignore_edges] + while !isempty(queue) + node = popfirst!(queue) + push!(visited, node) + for n ∈ graph[node] + if n ∉ visited && minmax(node, n) ∉ ignore_edges + push!(queue, n) + end + end + end + return length(visited) +end + +end # module diff --git a/test/runtests.jl b/test/runtests.jl index 8b41c93..8dcb5b5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -325,3 +325,21 @@ end @test AdventOfCode2023.Day24.part2(hailstones_sample) == 47 @test AdventOfCode2023.Day24.day24() == [20336, 677656046662770] end + +@testset "Day 25" begin + sample = "jqt: rhn xhk nvd\n" * + "rsh: frs pzl lsr\n" * + "xhk: hfx\n" * + "cmg: qnr nvd lhk bvb\n" * + "rhn: xhk bvb hfx\n" * + "bvb: xhk hfx\n" * + "pzl: lsr hfx nvd\n" * + "qnr: nvd\n" * + "ntq: jqt hfx bvb xhk\n" * + "nvd: lhk\n" * + "lsr: lhk\n" * + "rzs: qnr cmg lsr rsh\n" * + "frs: qnr lhk lsr\n" + @test AdventOfCode2023.Day25.day25(sample) == 54 + @test AdventOfCode2023.Day25.day25() == 527790 +end