summaryrefslogtreecommitdiff
path: root/2024/OCaml/lib
diff options
context:
space:
mode:
Diffstat (limited to '2024/OCaml/lib')
-rw-r--r--2024/OCaml/lib/day_01/part_01.ml40
-rw-r--r--2024/OCaml/lib/day_01/part_02.ml43
-rw-r--r--2024/OCaml/lib/day_02/part_01.ml42
-rw-r--r--2024/OCaml/lib/day_03/part_01.ml33
-rw-r--r--2024/OCaml/lib/day_04/part_01.ml84
-rw-r--r--2024/OCaml/lib/day_04/php/Grid.php67
-rw-r--r--2024/OCaml/lib/day_04/php/Input.php25
-rw-r--r--2024/OCaml/lib/day_04/php/Position.php77
-rw-r--r--2024/OCaml/lib/day_04/php/part_01.php94
-rw-r--r--2024/OCaml/lib/day_05/part_01.ml23
-rw-r--r--2024/OCaml/lib/dune5
-rw-r--r--2024/OCaml/lib/file/read_input.ml11
12 files changed, 544 insertions, 0 deletions
diff --git a/2024/OCaml/lib/day_01/part_01.ml b/2024/OCaml/lib/day_01/part_01.ml
new file mode 100644
index 0000000..100b12a
--- /dev/null
+++ b/2024/OCaml/lib/day_01/part_01.ml
@@ -0,0 +1,40 @@
+(* Day 1: Part 1 *)
+
+let calculate_distance x y = Stdlib.abs (x - y)
+
+(** Require: left and right to be the same length *)
+let solve left right =
+ let sorted_left = List.sort Stdlib.compare left in
+ let sorted_right = List.sort Stdlib.compare right in
+ let distances = List.map2 calculate_distance sorted_left sorted_right in
+ List.fold_left ( + ) 0 distances
+
+let left line =
+ let pos = 0 in
+ let len = String.index line ' ' in
+ String.sub line pos len
+
+let right line =
+ let pos = String.rindex line ' ' + 1 in
+ let len = String.length line - pos in
+ String.sub line pos len
+
+let line_to_tuple line = (left line, right line)
+
+let lines_to_lists lines =
+ let tuples = List.map line_to_tuple lines in
+ let left =
+ List.map
+ (fun t ->
+ let l, _ = t in
+ int_of_string l)
+ tuples
+ in
+ let right =
+ List.map
+ (fun t ->
+ let _, r = t in
+ int_of_string r)
+ tuples
+ in
+ (left, right)
diff --git a/2024/OCaml/lib/day_01/part_02.ml b/2024/OCaml/lib/day_01/part_02.ml
new file mode 100644
index 0000000..532b344
--- /dev/null
+++ b/2024/OCaml/lib/day_01/part_02.ml
@@ -0,0 +1,43 @@
+(* Day 1: Part 2 *)
+
+let rec count_occurrances_help element acc lst =
+ match lst with
+ | [] -> acc
+ | h :: t ->
+ if h = element then count_occurrances_help element (acc + 1) t
+ else count_occurrances_help element acc t
+
+let count_occurrances element lst = count_occurrances_help element 0 lst
+
+let solve left right =
+ List.map (fun x -> x * count_occurrances x right) left |> List.fold_left ( + ) 0
+
+let left line =
+ let pos = 0 in
+ let len = String.index line ' ' in
+ String.sub line pos len
+
+let right line =
+ let pos = String.rindex line ' ' + 1 in
+ let len = String.length line - pos in
+ String.sub line pos len
+
+let line_to_tuple line = (left line, right line)
+
+let lines_to_lists lines =
+ let tuples = List.map line_to_tuple lines in
+ let left =
+ List.map
+ (fun t ->
+ let l, _ = t in
+ int_of_string l)
+ tuples
+ in
+ let right =
+ List.map
+ (fun t ->
+ let _, r = t in
+ int_of_string r)
+ tuples
+ in
+ (left, right)
diff --git a/2024/OCaml/lib/day_02/part_01.ml b/2024/OCaml/lib/day_02/part_01.ml
new file mode 100644
index 0000000..32d7ab8
--- /dev/null
+++ b/2024/OCaml/lib/day_02/part_01.ml
@@ -0,0 +1,42 @@
+(* Day 2: Part 1 *)
+
+type level = int
+type reports = level list list
+
+let rec compare_report_levels_helper comparison prev report =
+ match report with
+ | [] -> true
+ | h :: t -> comparison prev h && compare_report_levels_helper comparison h t
+
+let compare_report_levels comparison report =
+ match report with
+ | [] -> true
+ | h :: t -> compare_report_levels_helper comparison h t
+
+let all_increasing report = compare_report_levels ( < ) report
+let all_decreasing report = compare_report_levels ( > ) report
+
+let at_least_one report =
+ compare_report_levels (fun prev curr -> prev <> curr) report
+
+let at_most_three report =
+ compare_report_levels (fun prev curr -> abs (prev - curr) <= 3) report
+
+let check_report_safety report =
+ if not (all_increasing report || all_decreasing report) then false
+ else if not (at_least_one report && at_most_three report) then false
+ else true
+
+let rec count_safe_reports acc lst =
+ match lst with
+ | [] -> acc
+ | h :: t ->
+ if h then count_safe_reports (acc + 1) t else count_safe_reports acc t
+
+let solve reports =
+ reports |> List.map check_report_safety |> count_safe_reports 0
+
+let line_to_report line =
+ line |> String.split_on_char ' ' |> List.map int_of_string
+
+let lines_to_reports lines = List.map line_to_report lines
diff --git a/2024/OCaml/lib/day_03/part_01.ml b/2024/OCaml/lib/day_03/part_01.ml
new file mode 100644
index 0000000..c12b68a
--- /dev/null
+++ b/2024/OCaml/lib/day_03/part_01.ml
@@ -0,0 +1,33 @@
+(* Day 3: Part 1 *)
+
+let find_mul text =
+ let regex_or_err = Re2.create "mul\\(([0-9]{1,3}),([0-9]{1,3})\\)" in
+ match regex_or_err with
+ | Ok regex -> (
+ let matches = Re2.get_matches regex text in
+ match matches with Ok matches -> matches | Error _ -> [])
+ | Error _ -> []
+
+let extract_pairs (matches : Re2.Match.t list) : (int * int) list =
+ List.map
+ (fun x ->
+ let left = Re2.Match.get x ~sub:(`Index 1) in
+ match left with
+ | None -> (0, 0)
+ | Some left -> (
+ let left_num = int_of_string left in
+ let right = Re2.Match.get x ~sub:(`Index 2) in
+ match right with
+ | None -> (0, 0)
+ | Some right ->
+ let right_num = int_of_string right in
+ (left_num, right_num)))
+ matches
+
+let solve input =
+ input |> find_mul |> extract_pairs
+ |> List.fold_left
+ (fun acc pair ->
+ let left, right = pair in
+ acc + (left * right))
+ 0
diff --git a/2024/OCaml/lib/day_04/part_01.ml b/2024/OCaml/lib/day_04/part_01.ml
new file mode 100644
index 0000000..63c1cfd
--- /dev/null
+++ b/2024/OCaml/lib/day_04/part_01.ml
@@ -0,0 +1,84 @@
+(* Day 4: Part 1 *)
+
+type character = { index : int; character : char }
+type direction = N | NE | E | SE | S | SW | W | NW
+
+let all_directions = [ N; NE; E; SE; S; SW; W; NW ]
+let needle = "XMAS"
+
+(** [calculate_row_offset lines] is the offset used to move between the rows of the concatenated variant of [lines] as though it were a grid.
+ Requires: values of [lines] all have the same length *)
+let calculate_row_offset lines =
+ match lines with [] -> 0 | h :: _ -> String.length h
+
+let join_lines lines =
+ let concat_trim acc line = acc ^ String.trim line in
+ List.fold_left concat_trim "" lines
+
+let explode s = List.init (String.length s) (String.get s)
+let is_in_bounds length index = index >= 0 && index < length
+
+let get_at_index line index =
+ let length = String.length line in
+ if not (is_in_bounds length index) then "."
+ else
+ let character = String.get line index in
+ Char.escaped character
+
+let get_direction_index row_offset direction index =
+ match direction with
+ | N -> index - row_offset
+ | NE -> index - row_offset + 1
+ | E -> index + 1
+ | SE -> index + row_offset + 1
+ | S -> index + row_offset
+ | SW -> index + row_offset - 1
+ | W -> index - 1
+ | NW -> index - row_offset - 1
+
+let get_direction_string get_char get_offset_index index =
+ let first = get_char index in
+ let second_index = get_offset_index index in
+ let second = get_char second_index in
+ let third_index = get_offset_index second_index in
+ let third = get_char third_index in
+ let fourth_index = get_offset_index third_index in
+ let fourth = get_char fourth_index in
+ first ^ second ^ third ^ fourth
+
+let check_direction get_from_line get_offset_index index direction =
+ get_direction_string get_from_line (get_offset_index direction) index = needle
+
+let calculate_num_of_matches line row_offset character =
+ let get_from_line_at_index = get_at_index line in
+ let get_offset_direction_index = get_direction_index row_offset in
+ let check_direction_from_index =
+ check_direction get_from_line_at_index get_offset_direction_index
+ character.index
+ in
+ all_directions
+ |> List.map check_direction_from_index
+ |> List.map (fun x -> match x with true -> 1 | false -> 0)
+ |> List.fold_left ( + ) 0
+
+(* Tried:
+ 2544 -> too high
+ 1272 (previous halved) -> too low *)
+let solve lines =
+ let row_offset = calculate_row_offset lines in
+ let line = join_lines lines in
+ let chars = explode line in
+ let characters = List.mapi (fun i c -> { index = i; character = c }) chars in
+ let xs =
+ List.filter
+ (fun c -> match c.character with 'X' -> true | _ -> false)
+ characters
+ in
+ let calculate_num_of_surrounding_matches_in_line =
+ calculate_num_of_matches line row_offset
+ in
+ let num_of_matches =
+ List.map calculate_num_of_surrounding_matches_in_line xs
+ |> List.fold_left ( + ) 0
+ in
+ num_of_matches
diff --git a/2024/OCaml/lib/day_04/php/Grid.php b/2024/OCaml/lib/day_04/php/Grid.php
new file mode 100644
index 0000000..c0382df
--- /dev/null
+++ b/2024/OCaml/lib/day_04/php/Grid.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+class Grid implements Stringable
+{
+ /**
+ * @param string[][] $data
+ */
+ public function __construct(public array $data) {}
+
+ public function __toString(): string
+ {
+ $rowToLine = fn(array $row): string => array_reduce(
+ array: $row,
+ callback: fn(string $acc, string $character): string => $acc . $character,
+ initial: "",
+ );
+
+ return array_reduce(
+ array: $this->data,
+ callback: fn(string $acc, array $row): string => $acc . $rowToLine($row) . PHP_EOL,
+ initial: "",
+ );
+ }
+
+ public function getCharacterAtPosition(Position $position): string
+ {
+ if ($this->isInBounds($position)) {
+ return $this->data[$position->y][$position->x];
+ }
+
+ return ".";
+ }
+
+ /**
+ * @param Position[] $positions
+ */
+ public function getStringFromPositions(array $positions): string
+ {
+ $characters = array_map(
+ callback: fn(Position $position): string => $this->getCharacterAtPosition($position),
+ array: $positions,
+ );
+
+ $string = array_reduce(
+ array: $characters,
+ callback: fn(string $acc, string $character): string => $acc . $character,
+ initial: "",
+ );
+
+ return $string;
+ }
+
+ public function isInBounds(Position $position): bool
+ {
+ if (!array_key_exists($position->y, $this->data)) {
+ return false;
+ }
+
+ if (!array_key_exists($position->x, $this->data[$position->y])) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/2024/OCaml/lib/day_04/php/Input.php b/2024/OCaml/lib/day_04/php/Input.php
new file mode 100644
index 0000000..f7e03c1
--- /dev/null
+++ b/2024/OCaml/lib/day_04/php/Input.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+class Input
+{
+ public string $contents;
+
+ public function __construct(private string $fileName)
+ {
+ $contents = file_get_contents($this->fileName, use_include_path: true);
+ $this->contents = $contents === false ? "" : $contents;
+ }
+
+ public function toGrid(): Grid
+ {
+ $lines = explode("\n", $this->contents);
+ $gridArray = array_map(
+ callback: fn(string $line): array => str_split(trim($line)),
+ array: $lines
+ );
+
+ return new Grid($gridArray);
+ }
+}
diff --git a/2024/OCaml/lib/day_04/php/Position.php b/2024/OCaml/lib/day_04/php/Position.php
new file mode 100644
index 0000000..182da7e
--- /dev/null
+++ b/2024/OCaml/lib/day_04/php/Position.php
@@ -0,0 +1,77 @@
+<?php
+
+declare(strict_types=1);
+
+class Position implements Stringable
+{
+ public function __construct(public $x, public $y) {}
+
+ public function __toString(): string
+ {
+ return "({$this->x}, {$this->y})";
+ }
+
+ /**
+ * Not validated
+ */
+ public function getNorth(): self
+ {
+ return new self($this->x, $this->y - 1);
+ }
+
+ /**
+ * Not validated
+ */
+ public function getNorthEast(): self
+ {
+ return new self($this->x + 1, $this->y - 1);
+ }
+
+ /**
+ * Not validated
+ */
+ public function getEast(): self
+ {
+ return new self($this->x + 1, $this->y);
+ }
+
+ /**
+ * Not validated
+ */
+ public function getSouthEast(): self
+ {
+ return new self($this->x + 1, $this->y + 1);
+ }
+
+ /**
+ * Not validated
+ */
+ public function getSouth(): self
+ {
+ return new self($this->x, $this->y + 1);
+ }
+
+ /**
+ * Not validated
+ */
+ public function getSouthWest(): self
+ {
+ return new self($this->x - 1, $this->y + 1);
+ }
+
+ /**
+ * Not validated
+ */
+ public function getWest(): self
+ {
+ return new self($this->x - 1, $this->y);
+ }
+
+ /**
+ * Not validated
+ */
+ public function getNorthWest(): self
+ {
+ return new self($this->x - 1, $this->y - 1);
+ }
+}
diff --git a/2024/OCaml/lib/day_04/php/part_01.php b/2024/OCaml/lib/day_04/php/part_01.php
new file mode 100644
index 0000000..250da0c
--- /dev/null
+++ b/2024/OCaml/lib/day_04/php/part_01.php
@@ -0,0 +1,94 @@
+<?php
+
+declare(strict_types=1);
+
+spl_autoload_register(fn(string $className) => require "{$className}.php");
+
+// Assumes executed from workspace root
+$input = new Input('input/day_04.txt');
+if ($input->contents === "") {
+ throw new RuntimeException("File not found");
+}
+
+$grid = $input->toGrid();
+
+$matchCount = 0;
+foreach ($grid->data as $y => $row) {
+ foreach ($row as $x => $char) {
+ if ($char !== 'X') {
+ continue;
+ }
+
+ $start = new Position($x, $y);
+
+ $possibleMatches = [];
+
+ $possibleMatches["north"] = $grid->getStringFromPositions([
+ $start,
+ $start->getNorth(),
+ $start->getNorth()->getNorth(),
+ $start->getNorth()->getNorth()->getNorth(),
+ ]);
+
+ $possibleMatches["east"] = $grid->getStringFromPositions([
+ $start,
+ $start->getEast(),
+ $start->getEast()->getEast(),
+ $start->getEast()->getEast()->getEast(),
+ ]);
+
+ $possibleMatches["south"] = $grid->getStringFromPositions([
+ $start,
+ $start->getSouth(),
+ $start->getSouth()->getSouth(),
+ $start->getSouth()->getSouth()->getSouth(),
+ ]);
+
+ $possibleMatches["west"] = $grid->getStringFromPositions([
+ $start,
+ $start->getWest(),
+ $start->getWest()->getWest(),
+ $start->getWest()->getWest()->getWest(),
+ ]);
+
+ $possibleMatches["north-east"] = $grid->getStringFromPositions([
+ $start,
+ $start->getNorthEast(),
+ $start->getNorthEast()->getNorthEast(),
+ $start->getNorthEast()->getNorthEast()->getNorthEast(),
+ ]);
+
+ $possibleMatches["north-west"] = $grid->getStringFromPositions([
+ $start,
+ $start->getNorthWest(),
+ $start->getNorthWest()->getNorthWest(),
+ $start->getNorthWest()->getNorthWest()->getNorthWest(),
+ ]);
+
+ $possibleMatches["south-east"] = $grid->getStringFromPositions([
+ $start,
+ $start->getSouthEast(),
+ $start->getSouthEast()->getSouthEast(),
+ $start->getSouthEast()->getSouthEast()->getSouthEast(),
+ ]);
+
+ $possibleMatches["south-west"] = $grid->getStringFromPositions([
+ $start,
+ $start->getSouthWest(),
+ $start->getSouthWest()->getSouthWest(),
+ $start->getSouthWest()->getSouthWest()->getSouthWest(),
+ ]);
+
+ $matchCount += array_reduce(
+ array: $possibleMatches,
+ callback: fn(int $acc, string $possibleMatch): int => match ($possibleMatch) {
+ "XMAS" => $acc + 1,
+ default => $acc,
+ },
+ initial: 0,
+ );
+ }
+}
+
+// ANSWER: 2517
+echo $matchCount;
diff --git a/2024/OCaml/lib/day_05/part_01.ml b/2024/OCaml/lib/day_05/part_01.ml
new file mode 100644
index 0000000..92bb105
--- /dev/null
+++ b/2024/OCaml/lib/day_05/part_01.ml
@@ -0,0 +1,23 @@
+(* Day 5: Part 1 *)
+
+(*
+ 01. Parse pages that need updating into list, e.g. "75,47,61,53,29" -> [ 75; 47; 61; 53; 29; ]
+ 02. Parse page ordering rules into DTOs, e.g. "47|53" -> {"before": 47, "after": 53}
+ 03. Find all rules that have pages from the parsed page list on both sides, e.g. "47|53" is valid but "97|47" and "47|13" are not
+ - filter down to "before" values that are in page list
+ - filter down to "after" values that are in page list
+ 04. Map across the page list and ensure that all rules for each page are upheld
+ 05. Map across the rules for the current page and ensure that all rules are upheld
+ - filter rules down to rules with "before" equal to current page
+ - walk the page list and ensure that all "after" values do no appear before the "before" value
+ - return true if all rules are valid for the current page
+ 06. Reduce result down to single boolean
+ 07. IF FALSE, move on to next page in page list
+ 08. IF TRUE, continue until all rules are validated
+ 09. IF ALL RULES ARE NOT VALID FOR PAGE LIST, move on to next page list
+ 10. IF ALL RULES ARE VALID FOR PAGE LIST, save middle number in page list
+ 11. ONCE ALL PAGE LISTS HAVE BEEN PROCESSED, add all saved middle numbers
+ 12. Return sum of middle numbers
+*)
+
+let solve _ = ()
diff --git a/2024/OCaml/lib/dune b/2024/OCaml/lib/dune
new file mode 100644
index 0000000..84e62ca
--- /dev/null
+++ b/2024/OCaml/lib/dune
@@ -0,0 +1,5 @@
+(library
+ (name AoC_2024)
+ (libraries re2))
+
+(include_subdirs qualified)
diff --git a/2024/OCaml/lib/file/read_input.ml b/2024/OCaml/lib/file/read_input.ml
new file mode 100644
index 0000000..0f7470b
--- /dev/null
+++ b/2024/OCaml/lib/file/read_input.ml
@@ -0,0 +1,11 @@
+let rec lines_from_channel ic =
+ try
+ let line = input_line ic in
+ line :: lines_from_channel ic
+ with End_of_file -> []
+
+let lines_from_file filename =
+ let channel = open_in filename in
+ let lines = lines_from_channel channel in
+ close_in channel;
+ lines