Mercurial > lbo > hg > aoc22
changeset 70:0fc36f81531e
Day 14 Part 2
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Thu, 28 Dec 2023 14:07:43 +0100 |
parents | 64fc8f99bddd |
children | 936b17a8e4ff |
files | 2023/day14.ml |
diffstat | 1 files changed, 138 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/2023/day14.ml Thu Dec 28 12:08:35 2023 +0100 +++ b/2023/day14.ml Thu Dec 28 14:07:43 2023 +0100 @@ -2,20 +2,39 @@ open Base open Core -type tile = Round | Cube | Empty [@@deriving show, eq] -type field = { rows : int; cols : int; fields : tile array } [@@deriving show] +type tile = Round | Cube | Empty [@@deriving eq, sexp] + +let string_of_tile = function Round -> "O" | Cube -> "#" | Empty -> "." + +type field = { rows : int; cols : int; fields : tile array } +[@@deriving sexp, eq] let rc_to_ix field r c = (r * field.cols) + c let field_get field r c = field.fields.(rc_to_ix field r c) +let field_set field r c v = field.fields.(rc_to_ix field r c) <- v let field_column field c = Array.init field.rows ~f:(fun r -> field_get field r c) +let field_row field r = + Array.sub field.fields ~pos:(r * field.cols) ~len:field.cols + let field_of_tiles r c tiles = { rows = r; cols = c; fields = Array.of_list tiles } +let string_of_field field = + let rows = Sequence.range 0 field.rows in + let row_strings = + Sequence.map rows ~f:(fun r -> + let row = field_row field r in + Array.map row ~f:string_of_tile |> Array.to_list |> String.concat) + in + Sequence.to_list row_strings |> String.concat ~sep:"\n" + let print_field ?(ch = Out_channel.stdout) field = - Out_channel.(output_string ch (show_field field)) + Out_channel.( + output_string ch (string_of_field field); + output_string ch "\n\n") module Parse = struct let parse_tile = function @@ -48,20 +67,131 @@ since = since + 1; } in - let result = Array.foldi col ~init:{ sum = 0; last_stop = -1; since = 0 } ~f in + let result = + Array.foldi col ~init:{ sum = 0; last_stop = -1; since = 0 } ~f + in result.sum let calculate_column_loads field = - Array.init field.cols ~f:(fun c -> calculate_column_load field.rows (field_column field c)) + Array.init field.cols ~f:(fun c -> + calculate_column_load field.rows (field_column field c)) let calculate_load field = let column_loads = calculate_column_loads field in - Array.fold column_loads ~init:0 ~f:Int.(+) + Array.fold column_loads ~init:0 ~f:Int.( + ) +end + +module Part2 = struct + (* with round boulders already aligned, we can use a simplified algorithm. *) + let simplified_column_load field col = + let f ix sum item = + match item with Round -> sum + (field.rows - ix) | _ -> sum + in + Array.foldi (field_column field col) ~init:0 ~f + + let calculate_column_loads field = + let cols = Sequence.range 0 field.cols in + Sequence.map cols ~f:(simplified_column_load field) |> Sequence.to_array + + (* Calculate load on northern pole *) + let simplified_load field = + let column_loads = calculate_column_loads field in + Array.fold column_loads ~init:0 ~f:Int.( + ) + + type state = { last_stop : int } + + (* shift a row or column rightwards, in-place. The getter/setter functions + determine whether a row or column is shifted, and in which direction *) + let shifter (getter : int -> tile) (setter : int -> tile -> unit) (max : int) + = + let f st ix = + match getter ix with + | Empty -> st + | Cube -> { last_stop = ix } + | Round -> + setter ix Empty; + setter (st.last_stop + 1) Round; + { last_stop = st.last_stop + 1 } + in + let ixs = Sequence.range 0 max in + let _ = Sequence.fold ixs ~init:{ last_stop = -1 } ~f in + () + + let shift_row_left field r = + let getter ix = field_get field r ix and setter ix = field_set field r ix in + shifter getter setter field.cols + + let shift_left field = + let rows = Sequence.range 0 field.rows in + Sequence.iter rows ~f:(shift_row_left field) + + let shift_row_right field r = + let getter ix = field_get field r (field.cols - 1 - ix) + and setter ix = field_set field r (field.cols - 1 - ix) in + shifter getter setter field.cols + + let shift_right field = + let rows = Sequence.range 0 field.rows in + Sequence.iter rows ~f:(shift_row_right field) + + let shift_column_up field c = + let getter ix = field_get field ix c and setter ix = field_set field ix c in + shifter getter setter field.rows + + let shift_up field = + let cols = Sequence.range 0 field.cols in + Sequence.iter cols ~f:(shift_column_up field) + + let shift_column_down field c = + let getter ix = field_get field (field.rows - 1 - ix) c + and setter ix = field_set field (field.rows - 1 - ix) c in + shifter getter setter field.rows + + let shift_down field = + let cols = Sequence.range 0 field.cols in + Sequence.iter cols ~f:(shift_column_down field) + + (* Run one cycle of the field. *) + let cycle field = + shift_up field; + shift_left field; + shift_down field; + shift_right field + + (* Run the cycle function n times, printing the field after each 1000 completed cycles. *) + let cycle_n field n = + let rng = Sequence.range 0 n in + Sequence.iter rng ~f:(fun i -> + cycle field; + if Int.(i < 10) then print_field field; + (* opportunity for stupid off-by-one error: if i = 9, then 10 cycles are completed. *) + if Int.((i + 1) % 1000 = 0) then + Out_channel.printf "load? %d -> %d\n" i (simplified_load field); + Out_channel.flush stdout) + + (* idea: the load numbers repeat cyclically. For the test input, with a period of 7. + for my puzzle input, with a period of 9, when checked after every 1000 completed cycles. + So we only need to observe the load number cycle, and then calculate back from + the desired cycle number 1e9 back to the load number expected there: + + load_number = load_sequence[ (1e9 / 1000) * period ] + + For 1e9, the index is = 1, thus the solution is 84238. For the test input, similarly, the index is 1 + and the solution is 64. + *) + let test_load_sequence = [ 65; 64; 65; 69; 69; 68; 63 ] + + let load_sequence = + [ 84239; 84328; 84202; 84342; 84210; 84332; 84237; 84299; 84276 ] end let () = let inp = In_channel.(input_all stdin) in let field = Parse.parse_field inp in let load = Part1.calculate_load field in - Out_channel.printf "load: %d\n" load - + Out_channel.printf "load: %d\n" load; + print_field field; + Part2.cycle_n field 1000000; + let load2 = Part2.simplified_load field in + print_field field; + Out_channel.printf "load after: %d\n" load2