Mercurial > lbo > hg > aoc22
changeset 67:b4d590e70ccd
Day 13 Part 2
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 23 Dec 2023 21:36:45 +0100 |
parents | 2746741a49f6 |
children | cd6e3d6c1338 |
files | 2023/day13.ml |
diffstat | 1 files changed, 76 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/2023/day13.ml Sat Dec 23 19:58:38 2023 +0100 +++ b/2023/day13.ml Sat Dec 23 21:36:45 2023 +0100 @@ -13,6 +13,8 @@ let ix_of_rc field r c = (r * field.cols) + c let rc_of_ix field ix = (ix / field.cols, ix mod field.cols) let tile_at field r c = field.tiles.(ix_of_rc field r c) +let get_column field c = Array.init field.rows ~f:(fun r -> tile_at field r c) +let get_row field r = Array.init field.cols ~f:(fun c -> tile_at field r c) module Parse = struct let maybe p = p >>| (fun _ -> ()) <|> return () @@ -32,9 +34,6 @@ end module Part1 = struct - let get_column field c = Array.init field.rows ~f:(fun r -> tile_at field r c) - let get_row field r = Array.init field.cols ~f:(fun c -> tile_at field r c) - (* check if mirror axis exists between columns c and c+1 (one-based) *) let check_mirror_v field c = let rec check_with_off field c off = @@ -96,17 +95,86 @@ List.map symmetries ~f:symmetry_to_score |> List.fold ~init:0 ~f:( + ) end +module Part2 = struct + (* throws on index error, ensure that there is only one differing element *) + let rec find_different_pos ?(ix = 0) a b = + if equal_tile a.(ix) b.(ix) then ix else find_different_pos a b ~ix:(ix + 1) + + (* tracking is a bit more complex than it seems: when comparing two rows/columns, + the result is either No smudge: we can continue looking; One smudge: we found the smudge + but there can't be a second one; or more than one difference, in which case we're not + looking at a valid symmetry axis. *) + type continue = No_smudge | One_smudge of int | Symmetry_fail + + (* find the difference between two rows/columns, and detect a smudge if present. *) + let find_diff a b = + match + Array.fold2_exn a b ~init:0 ~f:(fun acc a b -> + if equal_tile a b then acc else acc + 1) + with + | 0 -> No_smudge + | 1 -> + let ix = find_different_pos a b in + One_smudge ix + | _ -> Symmetry_fail + + let find_smudge getter max field ix = + let rec find_with_off field ix off = + if ix - off - 1 < 0 || ix + off >= max then No_smudge + else + let left = getter field (ix - off - 1) + and right = getter field (ix + off) + and rest = find_with_off field ix (off + 1) in + match find_diff left right with + | One_smudge ix -> ( + match rest with No_smudge -> One_smudge ix | _ -> Symmetry_fail) + | No_smudge -> rest + | Symmetry_fail -> Symmetry_fail + in + find_with_off field ix 0 + + (* find a smudge in column c (one-based). *) + let find_smudge_v field c = find_smudge get_column field.cols field c + + (* find a smudge in row r (one-based). *) + let find_smudge_h field r = find_smudge get_row field.rows field r + + let find_any_smudge max smudgefinder field = + let open Option in + let indices = List.range 1 (max + 1) in + List.find_map indices ~f:(fun ix -> + match smudgefinder field ix with One_smudge _ -> Some ix | _ -> None) + + (* find a new vertical symmetry axis in a field, ignoring exactly one smudge. *) + let find_any_smudge_v field = find_any_smudge field.cols find_smudge_v field + + (* find a new horizontal symmetry axis in a field, ignoring exactly one smudge. *) + let find_any_smudge_h field = find_any_smudge field.rows find_smudge_h field + + (* find a new symmetry axis in a field, ignoring exactly one smudge. *) + let find_new_symmetry field = + let open Part1 in + match (find_any_smudge_h field, find_any_smudge_v field) with + | Some h, Some v -> { horizontal = Some h; vertical = Some v } + | Some h, None -> { horizontal = Some h; vertical = None } + | None, Some v -> { horizontal = None; vertical = Some v } + | None, None -> assert false + + (* find all new symmetries in a list of fields. *) + let find_all_symmetries fields = List.map fields ~f:find_new_symmetry +end + let () = let inp = In_channel.(input_all stdin) in let fields = Angstrom.parse_string ~consume:All Parse.parse_fields inp |> Result.ok_or_failwith in - (* let fields_str = List.map fields ~f:show_field |> String.concat ~sep:"\n\n" in *) let symmetries = Part1.check_all fields in - (* let symmetries_str = - List.map symmetries ~f:Part1.show_symmetry |> String.concat ~sep:"\n" *) let score = Part1.symmetries_to_score symmetries in - Out_channel.((*printf "field: %s\n" fields_str;*) - printf "score = %d\n" score) + let new_symmetries = Part2.find_all_symmetries fields in + let new_score = Part1.symmetries_to_score new_symmetries in + Out_channel.( + printf "score = %d\n" score; + printf "new score = %d\n" new_score)