diff options
| author | DJ O'Leary <dijitol@proton.me> | 2026-06-26 18:04:09 +0200 |
|---|---|---|
| committer | DJ O'Leary <dijitol@proton.me> | 2026-06-26 18:04:09 +0200 |
| commit | 6d8fb621d1e93b83786890d6d9cb6f0d8defa8ae (patch) | |
| tree | 17c90d3984c114aa023600cc5bec6f6004b02243 /ocaml/lib/application | |
| parent | ad4ea5ca4bc9f7540ee1d6a4fa2d881709dc6459 (diff) | |
refactor!: invert repo directory structure
now is language first
Diffstat (limited to 'ocaml/lib/application')
24 files changed, 603 insertions, 0 deletions
diff --git a/ocaml/lib/application/d01p01.ml b/ocaml/lib/application/d01p01.ml new file mode 100644 index 0000000..206e757 --- /dev/null +++ b/ocaml/lib/application/d01p01.ml @@ -0,0 +1,53 @@ +open Domain.D01p01 + +let err_empty_input = "input is empty" + +let rec input_to_lines input = + let lines = + String.split_on_char '\n' input |> List.filter (fun l -> l <> "") + in + pp_lines lines ; lines + +and pp_lines = function + | [] -> + () + | h :: t -> + Logs.debug (fun m -> m "Line=%s" h) ; + pp_lines t + +let parse_line line = + Scanf.sscanf line "%c%d" (fun d n -> + match d with + | 'L' -> + Dial.Left n + | 'R' -> + Dial.Right n + | _ -> + failwith "invalid direction" ) + +let lines_to_turns = + List.map (fun (l : string) -> + Logs.debug (fun m -> m "parsing %s into turn" l) ; + let turn = parse_line l in + Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ; + turn ) + +let rec turn_aux count current = function + | [] -> + count + | h :: t -> + let current' = Dial.turn current h in + Logs.debug (fun m -> m "dial at %d" (Dial.to_int current')) ; + if Dial.to_int current' = 0 then turn_aux (count + 1) current' t + else turn_aux count current' t + +let turn current = turn_aux 0 current + +let solve input () = + if input = "" then Error err_empty_input + else + let lines = input_to_lines input in + let turns = lines_to_turns lines in + let start = Dial.start in + let answer = turn start turns in + Ok answer diff --git a/ocaml/lib/application/d01p01.mli b/ocaml/lib/application/d01p01.mli new file mode 100644 index 0000000..2eecf19 --- /dev/null +++ b/ocaml/lib/application/d01p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the first day of advent of code 2025 for [input]. + The solution for an empty input is [None]. + Requires: [input] to be a multiline string using `\n` as it's line separator. *) diff --git a/ocaml/lib/application/d01p02.ml b/ocaml/lib/application/d01p02.ml new file mode 100644 index 0000000..7910486 --- /dev/null +++ b/ocaml/lib/application/d01p02.ml @@ -0,0 +1,55 @@ +open Domain.D01p02 + +let err_empty_input = "input is empty" + +let rec input_to_lines input = + let lines = + String.split_on_char '\n' input |> List.filter (fun l -> l <> "") + in + pp_lines lines ; lines + +and pp_lines = function + | [] -> + () + | h :: t -> + Logs.debug (fun m -> m "Line=%s" h) ; + pp_lines t + +let parse_line line = + Scanf.sscanf line "%c%d" (fun d n -> + match d with + | 'L' -> + Dial.Left n + | 'R' -> + Dial.Right n + | _ -> + failwith "invalid direction" ) + +let lines_to_turns = + List.map (fun (l : string) -> + Logs.debug (fun m -> m "parsing %s into turn" l) ; + let turn = parse_line l in + Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ; + turn ) + +let rec turn_aux count dial = function + | [] -> + count + | h :: t -> ( + let dial_and_count = Turner.turn dial count h in + match dial_and_count with + | dial', count' -> + Logs.debug (fun m -> + m "(dial=%d, count=%d)" (Dial.to_int dial') count' ) ; + turn_aux count' dial' t ) + +let turn current = turn_aux 0 current + +let solve input () = + if input = "" then Error err_empty_input + else + let lines = input_to_lines input in + let turns = lines_to_turns lines in + let start = Dial.start in + let answer = turn start turns in + Ok answer diff --git a/ocaml/lib/application/d01p02.mli b/ocaml/lib/application/d01p02.mli new file mode 100644 index 0000000..9e9a2f2 --- /dev/null +++ b/ocaml/lib/application/d01p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the first day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a multiline string using `\n` as it's line separator. *) diff --git a/ocaml/lib/application/d02p01.ml b/ocaml/lib/application/d02p01.ml new file mode 100644 index 0000000..f313149 --- /dev/null +++ b/ocaml/lib/application/d02p01.ml @@ -0,0 +1,54 @@ +open Domain.D02p01 + +let err_empty_input = "input is empty" + +(** [input_to_lines input] is a list of strings that have been split on ','. + Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \]. + Requires: [input] to be a string in the format "12-34,56-78,90-100". *) +let input_to_lines input = input |> String.trim |> String.split_on_char ',' + +let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l)) + +(** [lines_to_ranges line] is a list of [Range]s found on [line]. *) +let lines_to_ranges = + Seq.map (fun line -> + Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) ) + +let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r))) + +(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *) +let collect_all = + Seq.concat_map (fun range -> List.to_seq (Range.collect range)) + +let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c))) + +(** [rules] is a list of rules that [ID]s must pass to be considered invalid. *) +let rules = + [ (fun id -> String.length (ID.to_string id) mod 2 = 0) + ; (fun id -> + let id' = ID.to_string id in + let id_len = String.length id' in + let left = String.sub id' 0 (id_len / 2) in + let right = String.sub id' (id_len / 2) (id_len / 2) in + String.equal left right ) ] + +(** [is_invalid id] is [true] for an [id] that passes all of the rules and is considered invalid. *) +let is_invalid = ID.validate rules + +(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *) +let passthrough_with_logs logger lst = logger lst ; lst + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> input_to_lines |> List.to_seq + |> passthrough_with_logs pp_lines + |> lines_to_ranges + |> passthrough_with_logs pp_ranges + |> collect_all + |> passthrough_with_logs pp_collection + |> Seq.filter is_invalid + |> Seq.fold_left (fun acc id -> acc + ID.to_int id) 0 + in + Ok answer diff --git a/ocaml/lib/application/d02p01.mli b/ocaml/lib/application/d02p01.mli new file mode 100644 index 0000000..8a8eb32 --- /dev/null +++ b/ocaml/lib/application/d02p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d02p02.ml b/ocaml/lib/application/d02p02.ml new file mode 100644 index 0000000..d062f7b --- /dev/null +++ b/ocaml/lib/application/d02p02.ml @@ -0,0 +1,86 @@ +open Domain.D02p02 + +(* Errors *) + +let err_empty_input = "input is empty" + +(* Logging *) + +(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *) +let passthrough_with_logs logger lst = logger lst ; lst + +(** [input_to_lines input] is a list of strings that have been split on ','. + Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \]. + Requires: [input] to be a string in the format "12-34,56-78,90-100". *) +let input_to_lines input = input |> String.trim |> String.split_on_char ',' + +let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l)) + +let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r))) + +let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c))) + +(* Core Logic *) + +(** [lines_to_ranges line] is a list of [Range]s found on [line]. *) +let lines_to_ranges = + Seq.map (fun line -> + Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) ) + +(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *) +let collect_all = + Seq.concat_map (fun range -> List.to_seq (Range.collect range)) + +(** [string_to_segments count string] is a list containing [count] string segments with of equal length. + Requires: length of [string] to be divisible by [count] + and [count] to be greater than or equal to zero *) +let rec string_to_segments s = + let segment_lengths = possible_segment_lengths s in + List.map (fun c -> string_to_segments_aux [] c s |> List.rev) segment_lengths + +(** [possible_segment_lengths s] is a list of segment sizes that fit within [s] + that must be checked to rule out a repeating pattern. *) +and possible_segment_lengths s = + List.init (String.length s) (fun x -> x + 1) + |> List.filter (fun x -> x != 1 && String.length s mod x = 0) + +and string_to_segments_aux segments count string = + match List.length segments = count with + | true -> + segments + | false -> + let segment_start = String.length string / count * List.length segments in + let segment_len = String.length string / count in + string_to_segments_aux + (String.sub string segment_start segment_len :: segments) + count string + +(** [cmp_segments segments] is true is all segments are equal *) +let cmp_segments segments = + List.length (List.sort_uniq String.compare segments) = 1 + +(** [is_invalid id] is [true] for an [id] that is considered invalid. *) +let is_invalid id = + Logs.debug (fun m -> m "CHECKING ID=%s" id) ; + let repeating_patterns = + id |> string_to_segments |> List.filter cmp_segments + in + List.iter + (fun l -> Logs.debug (fun m -> m "HAS SUBPATTERN=%s" (List.hd l))) + repeating_patterns ; + List.length repeating_patterns > 0 + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> input_to_lines |> List.to_seq + |> passthrough_with_logs pp_lines + |> lines_to_ranges + |> passthrough_with_logs pp_ranges + |> collect_all + |> passthrough_with_logs pp_collection + |> Seq.map ID.to_string |> Seq.filter is_invalid + |> Seq.fold_left (fun acc id -> acc + int_of_string id) 0 + in + Ok answer diff --git a/ocaml/lib/application/d02p02.mli b/ocaml/lib/application/d02p02.mli new file mode 100644 index 0000000..8a8eb32 --- /dev/null +++ b/ocaml/lib/application/d02p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d03p01.ml b/ocaml/lib/application/d03p01.ml new file mode 100644 index 0000000..522f5d0 --- /dev/null +++ b/ocaml/lib/application/d03p01.ml @@ -0,0 +1,23 @@ +open Domain.D03p01 + +let err_empty_input = "input is empty" + +let to_lines s = + s |> String.split_on_char '\n' |> List.map String.trim + |> List.filter (fun l -> l <> "") + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> to_lines |> List.to_seq + |> Seq.map BatteryBank.of_string + |> Seq.mapi (fun idx bb -> + let joltage = BatteryBank.highest_joltage bb in + Logs.debug (fun m -> + m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb) + joltage ) ; + joltage ) + |> Seq.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d03p01.mli b/ocaml/lib/application/d03p01.mli new file mode 100644 index 0000000..31dd0cd --- /dev/null +++ b/ocaml/lib/application/d03p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the third day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d03p02.ml b/ocaml/lib/application/d03p02.ml new file mode 100644 index 0000000..743c658 --- /dev/null +++ b/ocaml/lib/application/d03p02.ml @@ -0,0 +1,23 @@ +open Domain.D03p02 + +let err_empty_input = "input is empty" + +let to_lines s = + s |> String.split_on_char '\n' |> List.map String.trim + |> List.filter (fun l -> l <> "") + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> to_lines |> List.to_seq + |> Seq.map BatteryBank.of_string + |> Seq.mapi (fun idx bb -> + let joltage = BatteryBank.highest_joltage bb in + Logs.debug (fun m -> + m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb) + joltage ) ; + joltage ) + |> Seq.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d03p02.mli b/ocaml/lib/application/d03p02.mli new file mode 100644 index 0000000..93d3b98 --- /dev/null +++ b/ocaml/lib/application/d03p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the third day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d04p01.ml b/ocaml/lib/application/d04p01.ml new file mode 100644 index 0000000..7c69de4 --- /dev/null +++ b/ocaml/lib/application/d04p01.ml @@ -0,0 +1,48 @@ +open Domain + +let err_empty_input = "input is empty" + +let rec solve input () = + if input = "" then Error err_empty_input + else + let matrix = Matrix.M.of_string input in + let count_paper lst = List.length @@ List.filter (fun c -> c <> ".") lst in + let accessible_rolls_of_paper = ref 0 in + for y = 0 to Array.length matrix - 1 do + for x = 0 to Array.length matrix.(y) - 1 do + if matrix.(y).(x) = "." then () + else + let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in + let neighbours = + List.map + (fun (x', y') -> Matrix.M.get matrix x' y') + neighbour_coords + in + let paper_count = count_paper neighbours in + let is_accessible = paper_count < 4 in + if is_accessible then + accessible_rolls_of_paper := !accessible_rolls_of_paper + 1 ; + print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible + done + done ; + Ok !accessible_rolls_of_paper + +and print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible = + Logs.debug (fun m -> + m + "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \ + is_accessible=%B" + x y + (List.fold_left + (fun acc (x, y) -> + if acc = "" then + " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " + else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " ) + "" neighbour_coords ) + (List.fold_left + (fun acc neighbour -> + if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " ) + "" neighbours ) + paper_count is_accessible ) diff --git a/ocaml/lib/application/d04p01.mli b/ocaml/lib/application/d04p01.mli new file mode 100644 index 0000000..873acff --- /dev/null +++ b/ocaml/lib/application/d04p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the fourth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d04p02.ml b/ocaml/lib/application/d04p02.ml new file mode 100644 index 0000000..75b1236 --- /dev/null +++ b/ocaml/lib/application/d04p02.ml @@ -0,0 +1,64 @@ +open Domain + +let err_empty_input = "input is empty" + +let rec count_accessible_rolls matrix = + let count_paper lst = List.length @@ List.filter (fun c -> c = "@") lst in + let accessible_rolls_of_paper = ref [] in + let no_change = ref false in + while not !no_change do + let accessible_rolls_of_paper' = !accessible_rolls_of_paper in + count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper ; + if + List.length accessible_rolls_of_paper' + = List.length !accessible_rolls_of_paper + then no_change := true + done ; + List.length !accessible_rolls_of_paper + +and count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper = + for y = 0 to Array.length matrix - 1 do + for x = 0 to Array.length matrix.(y) - 1 do + if matrix.(y).(x) = "." then () + else + let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in + let neighbours = + List.map (fun (nx, ny) -> Matrix.M.get matrix nx ny) neighbour_coords + in + let paper_count = count_paper neighbours in + let is_accessible = paper_count < 4 in + if is_accessible then + accessible_rolls_of_paper := (x, y) :: !accessible_rolls_of_paper ; + print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible + done + done ; + List.iter + (fun (x', y') -> Matrix.M.set matrix x' y' ".") + !accessible_rolls_of_paper + +and print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible = + Logs.debug (fun m -> + m + "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \ + is_accessible=%B" + x y + (List.fold_left + (fun acc (x, y) -> + if acc = "" then + " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " + else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " ) + "" neighbour_coords ) + (List.fold_left + (fun acc neighbour -> + if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " ) + "" neighbours ) + paper_count is_accessible ) + +let solve input () = + if input = "" then Error err_empty_input + else + let matrix = Matrix.M.of_string input in + let accessible_rolls_of_paper = count_accessible_rolls matrix in + Ok accessible_rolls_of_paper diff --git a/ocaml/lib/application/d04p02.mli b/ocaml/lib/application/d04p02.mli new file mode 100644 index 0000000..388ec56 --- /dev/null +++ b/ocaml/lib/application/d04p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the fourth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d05p01.ml b/ocaml/lib/application/d05p01.ml new file mode 100644 index 0000000..4bf79ab --- /dev/null +++ b/ocaml/lib/application/d05p01.ml @@ -0,0 +1,3 @@ +let err_empty_input = "input is empty" + +let solve input () = if input = "" then Error err_empty_input else Ok 0 diff --git a/ocaml/lib/application/d05p01.mli b/ocaml/lib/application/d05p01.mli new file mode 100644 index 0000000..85c3dd5 --- /dev/null +++ b/ocaml/lib/application/d05p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the five day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d06p01.ml b/ocaml/lib/application/d06p01.ml new file mode 100644 index 0000000..9425810 --- /dev/null +++ b/ocaml/lib/application/d06p01.ml @@ -0,0 +1,50 @@ +let err_empty_input = "input is empty" + +let log_matrix matrix = + let open Domain.Matrix in + Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt))) + +let log_list lst = + Logs.debug (fun m -> + m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) ) + +type operation = Mult | Add + +let solve input () = + if input = "" then Error err_empty_input + else + let input' = Parser.columns_of_string input in + assert (input' <> []) ; + log_matrix input' ; + let nums = + List.map (List.filter (fun i -> i <> "*" && i <> "+")) input' + |> List.map + @@ List.map (fun n -> + Logs.debug (fun m -> m "peeking: %s" n) ; + int_of_string n ) + and ops = + List.map (List.filter (fun i -> i = "*" || i = "+")) input' + |> List.concat + |> List.map (fun op -> + match op with + | "*" -> + Mult + | "+" -> + Add + | _ -> + failwith "case not covered" ) + in + let answer = + List.map2 + (fun op num_lst -> + match op with + | Add -> + List.fold_left (fun acc i -> acc + i) 0 num_lst + | Mult -> + List.fold_left (fun acc i -> acc * i) 1 num_lst ) + ops nums + |> fun lst -> + log_list @@ List.map string_of_int lst ; + lst |> List.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d06p01.mli b/ocaml/lib/application/d06p01.mli new file mode 100644 index 0000000..b3abc1e --- /dev/null +++ b/ocaml/lib/application/d06p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the sixth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d06p02.ml b/ocaml/lib/application/d06p02.ml new file mode 100644 index 0000000..9425810 --- /dev/null +++ b/ocaml/lib/application/d06p02.ml @@ -0,0 +1,50 @@ +let err_empty_input = "input is empty" + +let log_matrix matrix = + let open Domain.Matrix in + Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt))) + +let log_list lst = + Logs.debug (fun m -> + m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) ) + +type operation = Mult | Add + +let solve input () = + if input = "" then Error err_empty_input + else + let input' = Parser.columns_of_string input in + assert (input' <> []) ; + log_matrix input' ; + let nums = + List.map (List.filter (fun i -> i <> "*" && i <> "+")) input' + |> List.map + @@ List.map (fun n -> + Logs.debug (fun m -> m "peeking: %s" n) ; + int_of_string n ) + and ops = + List.map (List.filter (fun i -> i = "*" || i = "+")) input' + |> List.concat + |> List.map (fun op -> + match op with + | "*" -> + Mult + | "+" -> + Add + | _ -> + failwith "case not covered" ) + in + let answer = + List.map2 + (fun op num_lst -> + match op with + | Add -> + List.fold_left (fun acc i -> acc + i) 0 num_lst + | Mult -> + List.fold_left (fun acc i -> acc * i) 1 num_lst ) + ops nums + |> fun lst -> + log_list @@ List.map string_of_int lst ; + lst |> List.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d06p02.mli b/ocaml/lib/application/d06p02.mli new file mode 100644 index 0000000..49444de --- /dev/null +++ b/ocaml/lib/application/d06p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the sixth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/dune b/ocaml/lib/application/dune new file mode 100644 index 0000000..b78613a --- /dev/null +++ b/ocaml/lib/application/dune @@ -0,0 +1,14 @@ +(library + (name application) + (libraries + domain + fmt + logs) + (inline_tests) + (preprocess + (pps + ppx_deriving.show + ppx_deriving.eq + ppx_deriving.ord + ppx_inline_test + ppx_expect))) diff --git a/ocaml/lib/application/parser.ml b/ocaml/lib/application/parser.ml new file mode 100644 index 0000000..099900b --- /dev/null +++ b/ocaml/lib/application/parser.ml @@ -0,0 +1,36 @@ +(** [rows_of_string s] is a list of strings where each element is a row of [s]. *) +let rows_of_string s = + s |> String.split_on_char '\n' + |> List.filter_map (fun l -> + let trimmed = String.trim l in + if trimmed <> "" then Some trimmed else None ) + +(** [columns_of_string ?sep s] is a list of strings where each element is + a column of [s] where each column is separated by [sep]. *) +let rec columns_of_string ?(sep = ' ') s = + rows_of_string s + |> List.map (fun l -> + l |> String.split_on_char sep |> List.filter (fun l -> l <> "") ) + |> transpose + +(** [group_columns lst] is a list where the first element of each + sublist of [lst] is grouped in [acc] and so on for the rest of the elements. + Example: [group_columns [] \[\["abc"; "def"\]; \["ghi"; "jkl"\]\]] is the list [\[\["abc";"ghi"\];\["def";"jkl"\]\]]. *) +and transpose lst = + assert (lst <> []) ; + if List.mem [] lst then [] + else List.map List.hd lst :: transpose (List.map List.tl lst) + +let%expect_test "rows correctly parsed" = + let have = {| abc def + ghi jkl |} in + let rows = rows_of_string have in + List.iter (fun c -> print_string (c ^ " ")) rows ; + [%expect {| abc def ghi jkl |}] + +let%expect_test "columns correctly parsed" = + let have = {| abc def + ghi jkl |} in + let cols = columns_of_string have in + List.iter (fun row -> List.iter (fun c -> print_string (c ^ " ")) row) cols ; + [%expect {| abc ghi def jkl |}] |
