#AOC in Ocaml
1 messages ยท Page 1 of 1 (latest)
I'm doing this year in OCaml and I'm using an expect test workflow
What does it mean, expect test workflow? Like, unit test stuff, or something else?
I believe they're referring to https://github.com/janestreet/ppx_expect
That's correct. First I define a test for each of the sample inputs, make sure they all pass. Then dune runtest --auto-promote will fill in the result for the actual input automatically.
Since we are at day 2, I wanted to share my approach to solve the first problems of day 1.
Feedback welcome:
let top_three_elves f =
let chan = open_in f in
let sum_list =
In_channel.input_all chan
|> Str.split (Str.regexp "\n\n")
|> List.map (fun s ->
String.split_on_char '\n' s
|> List.filter (fun s -> s <> "")
|> List.map int_of_string |> List.fold_left ( + ) 0)
in
List.sort compare sum_list |> List.rev |> function
| a :: b :: c :: _tl -> a + b + c
| _ -> 0
Doing adventofcode.com in OCaml this year :) we'll explore some new stuff
Prime's discord: https://discord.gg/ThePrimeagen
Shoutout: https://buttondown.email/anmonteiro
Thanks, let me know what you think!
#adventofcode #aoc #nvim #ocaml #programming
Hereโs my day 1. https://github.com/dangdennis/advent-of-code/blob/main/2022/ocaml/bin/day1.ml
I really Iike your solution @late rune . I hardly know any stdlibs so I had to use recursion. It felt so primitive, almost imperative like coding.
My solution for Day 1 is here https://github.com/bbatsov/praxis/blob/master/ocaml/aoc2022/bin/day1.ml I'm pretty new to OCaml, so I'm far from thinking I'm doing things optimally, but at least I'm having fun. ๐
(I've got a few helper functions in util.ml)
@late rune Nice solution indeed! I just realized I overdid the matching for the first 3 elements. ๐ Btw, I was surprised to see a usage of the Str module, as I've heard here and there than it was considered semi-deprecated or something like this.
Thanks!
Str.split is the only way to split on strings and not on characters, without writing your own function
I didn't knew about the semi-deprecation ๐ค
Yeah, I know it's the only option in the standard library. But it's not technically in the standard library either. Weird stuff! ๐ See http://ocamlverse.net/content/regular_expressions.html
OCaml comes with the Str module. This module is not recommended because it is not part of the standard library, it uses global states and is not particularly fast, but its availability makes it useful when you donโt have access to anything better.
Finding a string in another [fast] is not easy, the compiler devs don't really want to maintain a large stdlib
Yeah, I get totally get it.
This is my first year doing AOC, and first real foray into OCaml. I'm honestly not sure I'll be able to keep up with this due to time constraints, but I'll share my solutions.
oh hey
Last year I tried the AoC for the first time and sadly I made it only to day 4, but I hope I'll have more time and more discipline this time around. ๐
My solution today is quite ugly, need to spend more time with API:
open Util
open Printf
let input = lines "-"
let split_string str =
let len = String.length str in
let mid = len / 2 in
let part1 = (String.sub str 0 mid) in
let part2 = (String.sub str mid mid) in
(part1, part2)
let overlap str1 str2 =
String.fold_left
(fun res c ->
if String.contains str2 c then c::res else res)
[] str1
let overlap3 str1 str2 str3 =
String.fold_left
(fun res c ->
if (String.contains str2 c) && (String.contains str3 c) then c::res else res)
[] str1
let score ch =
if (int_of_char ch >= 97) then (int_of_char ch) - 96
else (int_of_char ch) - 38
let part1 = input |> List.map split_string |> List.map (fun pair -> let (s1, s2) = pair in (overlap s1 s2)) |> List.map List.hd |> List.map score |> sum
let part2 = input |> CCList.sublists_of_len 3 |> List.map (fun triplet -> let (s1 :: s2 :: s3 :: _) = triplet in (overlap3 s1 s2 s3)) |> List.map List.hd |> List.map score |> sum
let () = printf "Part1: %d\n" part1
let () = printf "Part2: %d\n" part2
Probably overlap should operate on list, so it's generic in this sense and I guess I overdid the nested List.maps. ๐
I'm also very curious if there's a way to let bind a list without generating warnings. E.g. in this case I know all lists have the same length, so I don't really need to match against other cases.
I kind of miss the ability to deconstruct data structures directly in the function definition (something like fun (s1, s2) ->, but it's not a big deal.
I'm definitely curious to see how people with more experience have tackled this and produced simpler code.
O(n^2)
Here is mine
let score_item s =
match Char.code s with
| c when c >= 97 && c <= 122 -> c - 96
| c when c >= 65 && c <= 90 -> c - 38 (* 65 - 27 *)
| _ -> failwith "Character out of range"
let incr_array_at array i = array.(i) <- array.(i) + 1
(* use array to store elements *)
let find_common_element s1 s2 =
let mem_1 = Array.make (2 * 26) 0 in
let mem_2 = Array.make (2 * 26) 0 in
let rec loop i len =
match i with
| i when i = len -> () (* end of strings *)
| i ->
incr_array_at mem_1 (score_item s1.[i] - 1);
incr_array_at mem_2 (score_item s2.[i] - 1);
loop (succ i) len
in
loop 0 (String.length s1);
let sum = ref 0 in
Array.iter2i
(fun i a b -> if a >= 1 && b >= 1 then sum := !sum + (i + 1))
mem_1 mem_2;
!sum
let read_n_sum f =
let chan = open_in f in
let rec loop_read sum =
match In_channel.input_line chan with
| None -> sum
| Some s ->
let len = String.length s in
let s1 = String.sub s 0 (len / 2) in
let s2 = String.sub s (len / 2) (len / 2) in
loop_read (sum + find_common_element s1 s2)
in
loop_read 0
I had to extend the standard library to keep track of the index in iter2i
module Array = struct
include Array
let iter2i f a b =
if length a <> length b then
invalid_arg "Array.iter2: arrays must have the same length"
else
for i = 0 to length a - 1 do
f i (unsafe_get a i) (unsafe_get b i)
done
end
just finished day2 @ https://github.com/dangdennis/advent-of-code/blob/main/2022/ocaml/bin/day2.ml
onto 3!
(note that I've already turned the input into a list, so the code is a little shorter than it actually would be if I was getting it from the file directly. However, the algorithms should remain the same otherwise)
(Day3) I just used Sets to avoid writing string intersection:
module CharSet = struct
include Set.Make(Char)
let of_string s = of_seq (String.to_seq s)
end
Clearly, turning a string into a char Seq into a CharSet is not going to win any performance records... but I am happy to use CharSet.inter twice instead of iterating over a string index
(Day 4) https://github.com/hellopatrick/xmas/blob/v2022/day04/main.ml. Not much to it other than Scanf will probably be helpful for some input parsing.
Indeed. Scanf was definitely useful in this one. Here's my solution.
open Util
open Printf
let input = lines "-"
let transform_input lines =
List.map (fun line -> Scanf.sscanf line "%d-%d,%d-%d" (fun a b c d -> (a, b), (c, d))) lines
let part1 = transform_input input |> List.map (fun ((a, b), (c, d)) -> if ((a <= c && b >= d) || (c <= a && d >= b)) then 1 else 0) |> sum
let part2 = transform_input input |> List.map (fun ((a, b), (c, d)) -> if (Stdlib.max a c <= min b d) then 1 else 0) |> sum
let () =
printf "Part1: %d\n" part1;
printf "Part2: %d\n" part2
Probably I should have used a fold to get rid of one intermediary step, but I was too lazy to optimize anything today.
Yeah, Scanf is a godsend. Here's a solution with composition of scan functions: https://github.com/quernd/Advent-of-Code/blob/main/2022/04/day04.ml
I like how concise your solution is
Ah, had no idea you could compose them. Like that. Time to update my solution ๐
Here is my solution for day 4. https://seoushi.dynu.net/sean/aoc2022/-/blob/master/day4/bin/main.ml , Not as concise as others I'm seeing here, still learning :). scan is a good find, I did the naive approach with String.split_on_char
I'm really enjoying seeing everyone's solutions. It's so good for helping me to learn the language better. Today (day 4) was the fastest one for me to implement, since it didn't have me writing any new recursive functions (at which I'm nascent) and I'm also just getting more familiar.
https://github.com/jaycle/aoc-2022/blob/main/bin/day4.ml
reading ASCII art as data is not my idea of a good time
Yeah, I've just resigned to altering the input to just be lines of the initial stacks... instead of the vertical columns. https://github.com/hellopatrick/xmas/blob/v2022/day05/main.ml (https://github.com/hellopatrick/xmas/blob/v2022/day05/test.t/test)
@sharp osprey I really like your project structure, btw! I went for putting the solution in bin to be able to run them with dune exec, but it turned out this way it's not easy to work with dune utop. (seems it loads only library code?) This approach with the many libraries and cram-style tests seems pretty cool!
haha @dapper dune that was my first thought exactly.
I didn't bother to parse the ASCII art, but if someone knows a good approach for something like this - please do tell.
I "zipped" the lines to make reading them easier
It's still nasty though: https://github.com/quernd/Advent-of-Code/blob/main/2022/05/day05.ml#L3-L12
This is the only drawback of AOC I find, these unusually complicated pattern that need to be parsed
I finished my day5. I did do the parsing. So far I feel like the hardest part has been getting the input into a usable form then it's just a minor follow the logic. https://seoushi.dynu.net/sean/aoc2022/-/blob/master/day5/bin/main.ml
my idea to parse was based on that for each cell of the ascii matrix there are 4 chars, still didn't ended up very clear https://github.com/azimut/challenges/blob/master/ocaml/aoc2022/day5.ml#L17
Yeah, that is what I ended up doing too. Tho I read the last row with the stack numbers first because I was unsure if there would be trailing spaces or not on some rows
From parsing ASCII art to just a string... .https://github.com/hellopatrick/xmas/blob/v2022/day06/main.ml Today's would be interesting with some benchmarks and see where can claw back some time (not that it's slow.)
My solutions for today:
https://github.com/arxaqapi/aoc/blob/main/2022/solutions/lib/day06.ml
I've been bashing my head against AoC this year as well, in OCaml as usual!: https://github.com/p1xelHer0/advent-of-code-2022-ocaml/blob/main/lib/day_6/day_6.ml
Still haven't done Day 5, spent too much time tinkering with angstrom and achieved nothing 
day 5 horribleness:
if it looks like I gave up trying to parse things manually and switched to regex halfway through, you'd be correct
day 6 was much better:
day 4:
day 3:
Today again it seems that most of the complexity is going to be parsing the input. (like in Day 5)
First time this year I bring in the parser combinators
||https://github.com/hellopatrick/xmas/blob/v2022/day07/main.ml avoided making a tree structure and just basically pushed/popped the current directory onto a stack and kept track of the sizes of each dir in a table.||
same here, except I used ||Map|| and ||parser combinators||
Isn't ||Map|| just a representation of a tree structure?
I guess you could make it flat
I was limited by having begun, then abandoned, a tree representation using that ๐
||It's implemented using trees but I just use it for lookup instead of Hashtbl - I don't build the directory tree||
I had thought of building the tree structure using it while solving, and spoke before I thought of the obvious way to flatten
|| I was prepared to build the tree structure for part 2, but for once the shortcut was sufficient ||
I'm curious to see how it looks like using parser combinators, never used them before
https://github.com/quernd/Advent-of-Code/blob/main/2022/07/day07.ml#L10-L27 this is my parsing code
What a great day to learn how to use || the Bigarray module ||
Ooh good tip!
On second thought, maybe not ๐ฅต
I did part 1 its ugly as hell ๐
https://github.com/hellopatrick/xmas/blob/v2022/day08/main.ml I just use || a map of (x,y) coordinates to the height||. Perhaps not super efficient, but clean enough code. More useful in past years when ||the space was infinite in all directions.||
I remember those past years ๐ and I almost did what you did
That's what I feared part 2 was ๐
wasted time trying to put together a solution that did not use brute force: ||https://github.com/tdimhcsleumas/advent-of-code-2022/blob/main/day8/day8.ml||. ||part 1 was easier in that regard, simply considering the max height with respect to the traversed row and column||. ||part2 was trickier. At first I got distracted by considering only the most recent height in the score. My (kinda ugly) solution ended up maintaining a most recent coordinate for each height that was traversed, and then searching that 10-element array in lieu of the entire row or column to get the score||. In theory, ||they're asymptotically faster, but i have yet to actually compare||
https://github.com/hellopatrick/xmas/blob/v2022/day09/main.ml -- Enjoyed day9. ||Happy to be able to refactor it afterwards to just a single version for part1 & part2.||
I also quite enjoyed today's puzzle. || I was able to use the same logic for both part1 and 2: https://github.com/quernd/Advent-of-Code/blob/main/2022/09/day09.ml ||
Today was also fun. || https://github.com/quernd/Advent-of-Code/blob/main/2022/10/day10.ml ||
An overly imperative solution (at the moment, before I refactor it ๐ ) https://github.com/hellopatrick/xmas/blob/v2022/day10/main.ml
Wow, now it's really concise. I saw it evolve through the commits ๐
https://github.com/hellopatrick/xmas/blob/v2022/day11/main.ml -- Most spent on learning Angstrom... ||Then figuring how to keep the worry level restrained after a quick attempt to use big ints.||
Similar in style, no mutation but twice as verbose: || https://github.com/quernd/Advent-of-Code/blob/main/2022/11/day11.ml ||
I hard coded the modulus to be appropriate for my "real input" then spent an hour trying to figure out why it wasn't working with the test data
when converting the item weights from int to Z.t I just had to find all the places the LSP told me weren't typechecking, it ran correctly the first time it compiled ๐
Another day, another || Dijkstra: https://github.com/quernd/Advent-of-Code/blob/main/2022/12/day12.ml ||
https://github.com/hellopatrick/xmas/blob/v2022/day12/main.ml, ||BFS & Brute-forced BFS...||
is this the day I learn some Angstrom? [[[[[[[[[[[][][][][]]]]]]]]]]
|| https://github.com/quernd/Advent-of-Code/blob/main/2022/13/day13.ml || lots of tests bring up the line count
๐๐ป Yeah, bulk of my time was spent understand ||Angstrom.fix||. Once I had the data structure, the problem itself came naturally. https://github.com/hellopatrick/xmas/blob/v2022/day13/main.ml
|| Angstrom.fix || is magic. || I love its documentation in the mli file ||
Somebody in a different channel suggested || parsing the input as JSON ||
that was my first thought after "you could just eval this in python"
A good day to use || the Either module: https://github.com/quernd/Advent-of-Code/blob/main/2022/14/day14.ml ||
https://github.com/hellopatrick/xmas/blob/v2022/2022/day14/main.ml ||I just drew an exceptionally long line at the bottom for part 2...||
https://github.com/hellopatrick/xmas/blob/main/2022/day15/main.ml ||Reused the Range type from an earlier day... so far gotten solution down to 1s.||
Itโs about time I start building a small AoC library, I keep repeating patterns
Part 1 done. But the runtime doesn't bode well...
Yeah, Part 1 I got... but going to have to figure out some better pruning strategy for Part 2.
I have a solution for part 2 that works on the example, but it's still running on the real input ๐
That was tough. I needed to rewrite it for part2 but now it works.
||I tried A* first for pt 1, it worked but it was still slow. Then a sort of BFS and a trick for pt 2 https://github.com/quernd/Advent-of-Code/blob/main/2022/16/day16.ml ||
Eventually restarted from scratch -- https://github.com/hellopatrick/xmas/blob/main/2022/day16/main.ml -- ||Floyd-Warshall to find shortest path between all points. This is used to compress the graph (we can now skip all valves with 0 flow rate!) For part1, we can just DFS over this for the largest. For part2, it was fast enough to attempt to just brute-force DFS both me & the elephant, slowly reserving more and more nodes for the elephant.||
(that's a strange last sentence, ๐ )
Very instructive, our solutions had almost nothing in common this time!
https://github.com/hellopatrick/xmas/blob/main/2022/day17/main.ml -- Tetris! Part 1 is simple simulation, but I had an inordinate amount of bugs along the way. Part 2 is ||find the cycle, then use that information to skip to the end.||
I have implemented || cycle detection || but I can't reproduce the example yet ๐ฆ
Eventually I got it to work but I can't prove that my solution is correct ๐
I mean, the || cycle detection || gives me ||working cycles for both example and input || but I'm using || only (rock index, jet index) to find the cycle || and that just sounds like it shouldn't be enough
So either I got lucky or it's possible to prove the algorithm is correct on all inputs, and I don't know which...
https://github.com/hellopatrick/xmas/blob/main/2022/day18/main.ml A much needed break in difficulty. ๐
I felt the same! https://github.com/quernd/Advent-of-Code/blob/main/2022/18/day18.ml
https://github.com/hellopatrick/xmas/blob/main/2022/day19/main.ml
https://github.com/hellopatrick/xmas/blob/main/2022/day20/main.ml
For day 20, there must be a better data structure.
What's your runtime for day 19? Mine is 25 seconds. I'm still cleaning up
I fell behind a bit
part1=2301;part2=10336
________________________________________________________
Executed in 5.21 secs fish external
usr time 5.15 secs 59.00 micros 5.15 secs
sys time 0.02 secs 637.00 micros 0.02 secs```
I think most of it comes from two rules: ||There's often points at which you cannot possibly be get to your running maximum of geodes, in which case you can prune early. Also, if geode robot needs N obsidian, you only ever need N obsidian robots. You can apply this rule to all the robots to avoid going paths where you build more robots than output that can be possibly used.||
Thanks, those are some good insights. I also used || a pruning strategy for states that can't catch up with the running maximum, and that helped a lot. The only other strategy is that I "fast-forward" from a state directly to the next four timestamps where there are enough resources to build the next one of each type of robot, so I only explore a spare graph of states. ||
Ah yeah, I should probably implement the ||fast-forward||.
For day 20, || doubly-linked list with mutable record fields || worked well for me. || It's a bit of an abomination but I'll clean it up tomorrow and post it. ||
I find it curious that you didn't need || rational number arithmetic - I got off by one errors because of incorrect truncation with integer math ||
Ugly and hardcoded to my inputs, https://github.com/hellopatrick/xmas/blob/main/2022/day22/main.ml. day22 was an exercise in perseverance.
I fell behind a bit, only part 1 so far. Thought about preprocessing part 2 manually too...
||Annual Conway's Game of Life round!|| https://github.com/hellopatrick/xmas/blob/main/2022/day23/main.ml
https://github.com/hellopatrick/xmas/blob/main/2022/day24/main.ml ||Year of BFS. Moving maze...||
Merry Christmas! ๐ https://github.com/hellopatrick/xmas/blob/main/2022/day25/main.ml
Merry Christmas! I have some catching up to do, Iโm still missing five stars.
Still two stars missing from last year, and only two weeks to go ๐ฎ
Hello 
I might have time to learn Angstrom before this years starts 
I'm also doing AoC in OCaml again!
Same here !
good luck!
I am learning Angstrom in anger 
I also used Angstrom. Was it necessary? Probably not. Was it wise? Who knows...
Fell right into a trap, too!
|| I am having a problem with the eightwo part, where I need to ignore wo since t is already "consumed" from eight || Would love any pointers:
This is my FIRST time using any parser whatsoever ๐
||```
module P = struct
open Angstrom
let is_whitespace = function ' ' | '\t' -> true | _ -> false
let is_digit = function '0' .. '9' -> true | _ -> false
let is_char = function 'a' .. 'z' -> true | _ -> false
let whitespace = take_while is_whitespace
let digit = take_while1 is_digit >>| Int.of_string_exn
let char = skip is_char
let p1 = string "one" *> return 1
let p2 = string "two" *> return 2
let p3 = string "three" *> return 3
let p4 = string "four" *> return 4
let p5 = string "five" *> return 5
let p6 = string "six" *> return 6
let p7 = string "seven" *> return 7
let p8 = string "eight" *> return 8
let p9 = string "nine" *> return 9
let number =
whitespace
*> (digit <|> p1 <|> p2 <|> p3 <|> p4 <|> p5 <|> p6 <|> p7 <|> p8 <|> p9)
<* whitespace
let parse input =
parse_string ~consume:All (many1 number) input |> Result.get_or_failwith
end
|| My idea is that if none of those criteria's are met I want to advance the parser one step and try again but I don't know how to do that ๐ฌ ||
|| I added an any_char parser to the big conjunction of parsers as a "fallback" ||
|| Yeah but I don't understand how because adding it afterwards, before the <* whitespace results in a not enough input crash. Is that because I force the parser to look at a character when it's at the end of the string, or something else?
||
||I don't even understand your whitespace to be honest, there's no whitespace in my puzzle input? Did we solve the same puzzle? ๐ค ||
|| it's just there cuz every example uses it in their "hello world" parser with a whitespace *> int <* whitespace kinda thing
bad copy paste from my side, it is not needed! ||
I went back to R E C U R S I O N
Unedited and raw, extremely noisy ๐
That isn't so bad, certainly shorter than my Angstrom solution!
You didn't fall for the trap like I did. || List.tl line instead of tail avoids it.||
rather than that I would just reverse the list and look for slaremun (eno, owt, eerht...)
That's what I ended up doing as well. The downside is it needs two passes?
Then again, you could short-circuit so it's likely to be more performant still ๐
and exploding strings is wasteful
yes, looking from each end lets you stop on success
I ended up bashing my head against angstrom's peek_string because I got the first part done in angstrom and I thought it would be an easier adaptation
but I feel like I don't know how to handle "junk" that I don't want to produce anything, I had to use option int and produce None for each character that wasn't a digit or start of a numeral
That sounds about right, no?
it felt like I was missing a way to say "eat the character and restart this same parser"
or an infinite list of "eat 0 and try p; eat 1 and try p; eat 2 and try p..."
I might give it a try with peek_string and any_char
I had that one as well, u can see that || I haven't even removed the tail part because I used that before || ๐
Yeah the "handling junk" was my problem as well if you see my comments as well. || All the p1 <|> ... <|> p9 being int and I felt trapped not knowing how to throw away unneeded chars ||
I used || option along with any_char *> return None and a List.filter_map Fun.id at the end || for the junk
I'll have to look at that later!
Now I think of it my Angstrom solution wouldn't work: || I consumed the whole text of the word digit and wouldn't be able to spot overlapping ones! ||
day one, part one
Does anyone recommend a better regexp for ocaml, I'm having issues with the lookahead
sadly missing out on the "eighthree" parsing to 83
let re = Str.regexp "\\(one\\|two\\|three\\|four\\|five\\|six\\|seven\\|eight\\|nine\\|[0-9]\\)" in
let regex_matches = Str.full_split re line in
let matches = List.map ~f:(fun match_ ->
match match_ with
| Str.Delim s -> Some (to_digit s)
| Str.Text _ -> None
) regex_matches in
List.filter_opt matches
;;```
if I had to use regexps to solve this problem I would do it in forward and reverse passes
day one, part two: just one extra line!
(well, it looks like two because it wraps so far...)
Very interesting, I think that would make sense. How would you do when the word is in the middle of the two passes ie if you have three numbers spelled out that overlap
one of them necessarily is uniquely the first and one is uniquely the last
twoneight should reduce to 28
@kali clever, but do you need the prefixes for something I'm not seeing?
oh, early replacements can mangle later ones, that makes sense
stuck thinking linearly
let extract_digits_and_numbers line =
let re = Str.regexp "\\(one\\|two\\|three\\|four\\|five\\|six\\|seven\\|eight\\|nine\\|[0-9]\\)" in
let rev_re = Str.regexp "\\(eno\\|owt\\|eerht\\|ruof\\|evif\\|xis\\|neves\\|thgie\\|enin\\|[0-9]\\)" in
let regex_matches = Str.full_split re line in
let matches = List.map ~f:(fun match_ ->
match match_ with
| Str.Delim s -> Some (to_digit s)
| Str.Text _ -> None
) regex_matches in
let rev_regex_matches = Str.full_split rev_re (String.rev line) in
let rev_matches = List.map ~f:(fun match_ ->
match match_ with
| Str.Delim s -> Some (to_digit (String.rev s))
| Str.Text _ -> None
) rev_regex_matches in
let all_matches = List.append matches (List.rev rev_matches) in
List.filter_opt all_matches
;;
Good call on the left to right and right to left @dapper dune
this is probably the ugliest code I've ever produced
what could be more beautiful than "thgie"
angstrom starting to click fr, or maybe this format is just easy to write a parser for
Agreed, the Angstrom experience was very smooth today
anyone know of a good resource to learn angstrom? I'll give it a college try today
day 2 part 1 solution
||```ocaml
open List let s = Scanf.sscanf let sp = String.split_on_char let i = Fun.id
let () =
In_channel.input_all stdin
|> sp '\n'
|> map (sp ':')
|> map (fun [x;y] -> s x "Game %d" i, concat_map (sp ',') @@ sp ';' y)
|> filter (fun (_,y) -> let a,b,c = fold_left (fun (a,b,c) str ->
max a (try s str " %d red" i with _ -> 0),
max b (try s str " %d green" i with _ -> 0),
max c (try s str " %d blue" i with _ -> 0)) (0,0,0) y in a <= 12 && b <= 13 && c <= 14)
|> map fst
|> fold_left (+) 0
|> print_int
perhaps somewhat ironically, the day 2 part 2 solution is even shorter
||```ocaml
open List let s = Scanf.sscanf let sp = String.split_on_char let i = Fun.id
let () =
In_channel.input_all stdin
|> sp '\n'
|> map (sp ':')
|> map (fun [_;y] -> concat_map (sp ',') @@ sp ';' y)
|> map (fun y -> let a,b,c = fold_left (fun (a,b,c) str ->
max a (try s str " %d red" i with _ -> 0),
max b (try s str " %d green" i with _ -> 0),
max c (try s str " %d blue" i with _ -> 0)) (0,0,0) y in a * b * c)
|> fold_left (+) 0
|> print_int
@lime mica I just alternated going through the json parser and reading documentation for operators I couldn't make sense of
it's an extremely specific view of turning a stream of characters into some kind of meaning, and because it's inside a type system you have to make sure you have a type general enough that all the parsers you want to pick between can produce the same type
probably a good day for taking a crack at it though
Has anyone dealt with (Failure "Parsing error: : end_of_input") error in angstrom? My parser must be bugging out somehow
let eol = skip_many (choice [char '\n'; char '\r'])
let number = space *> take_while1 (function '0' .. '9' -> true | _ -> false) <* space >>| int_of_string
let color = space *> take_while1 (function 'a' .. 'z' | 'A' .. 'Z' -> true | _ -> false) <* space
let game_prefix = string "Game " *> number *> string ": " *> return ()
let color_count =
lift2 (fun count color -> ({color = color; count})) number (char ' ' *> color)
let group = sep_by (space *> char ',' <* space) color_count
let line_parser = game_prefix *> sep_by (space *> char ';' <* space) group <* eol
let aggregate_max_counts groups =
let table = Hashtbl.create (module String) in
List.iter ~f:(fun group ->
List.iter ~f:(fun {color; count} ->
let current_count = Hashtbl.find table color |> Option.value ~default:0 in
Hashtbl.set table ~key:color ~data:(max current_count count)
) group
) groups;
table
let parse_line line =
match parse_string ~consume:All line_parser line with
| Ok groups -> aggregate_max_counts groups
| Error msg -> failwith ("Parsing error: " ^ msg)
;;
I've been parsing lines only so I can't speak directly. I think you're consuming all the lines until EOF but your parser doesn't have any idea what to do
docs say to use ~consume:Prefix to not require consuming the whole thing, which might be fine for not having to deal with eof?
but I would guess you will only get 1 line because you're not using many or sep_by
I was able to convert my line-parser game into a parser for the whole file with this definition: let file = (sep_by1 end_of_line game) <* end_of_line <* end_of_input
(which worked with ~consume:All as well as ~consume:Prefix)
I solved using regex - but I really want to learn angstrom now. ๐
Thoughts on today? Iโm still working on it after a few hours lol, ended up iterating through the grid and building some struct with the number and the coordinates attached to it
I should have made a graph originally, instead did the finicky bits for both parts with separate string twiddling algorithms
my naive solution would be to ||collect every digit-coordinate pair into a seq, group sequential digits into number-coordinate pairs, turn that into a map from coordinates to numbers, collect all symbols' coordinates and then index the 8 squares around each symbol's coordinate from the map, and if it exists, then add it to an accumulator and remove it from the map (no number is touched by more than one symbol), and then sum||
i havent actually written it yet though
there are also no symbols on the outer edge, which was a case I bothered to handle but when I was debugging realized it couldn't be causing trouble with the input
https://github.com/acrucetta/advent-of-code-2023/blob/main/lib/year2023/day03.ml - this is my day 3 so far, for some reason I'm geting a type error where my is_by_symbol function expects char list list list
The struct I'm shooting for is: [("142",[(0,2);(1,2);...]] i.e. points with their coordinates
where is integer power in stdlib ocaml ๐ฆ
just spent literally twenty minutes debugging "I copied the carriage return character (โ) into the answer submission"
the math was good the whole time ๐
much less painful than yesterday. n.b.: I used zarith but my part 2 total was <10 million, so it is probably unnecessary
zarith does give you integer power though (weirdly it takes a Z.t base and an integer exponent)
Does anyone know whats' the best way to check if a string in a list is contained in another list using the Core library?
For some reason it doesn't like how I'm filtering the data. I get an error:
40 | let matches = List.filter ~f:(fun x -> Core.List.mem winners x) numbers in
^^^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type equal:('a -> 'a -> bool) -> bool
but an expression was expected of type bool
(*
[get_matches] will iterate over each number in [numbers]
and find a match in [winners] if there's a match we keep
the number if not, we filter it out. We will return an int
representing the score for the card.
*)
let get_score winners numbers =
let matches = List.filter ~f:(fun x -> List.mem winners x) numbers in
let length_matches = (List.length matches) in
if length_matches = 0 then 0
else Z.pow (Z.of_int 2) (length_matches - 1) |> Z.to_int
;;```
Core.List.mem takes a labeled argument equal to test for equality of elements
the stdlib List.mem implicitly uses polymorphic equality
also whenever you see fun x -> f a b c x you can just use f a b c
you probably want something like List.filter ~f:(List.mem ~equal:String.equal winners)
worked like a charm! thank you so much
it's quite confusing how o'caml has so many standard libraries compared with other languages
cpu > brain, brute force got there before I could figure out the right way to combine maps
I don't grasp the idea for day problem 2 ๐ฆ
2 ideas: 1. ||trace ranges to (multiple) ranges through maps|| 2. ||combine maps by case analysis||
unless you mean "what is to be calculated" <snip>
I understood what is to be calculated - it's the efficiency that is the problem. ||I saw usage of ranges but I don't understand it yet||
Consider a single ||range of seeds|| and the seed-to-soil map. Find a way to transform that ||range into multiple "output ranges"||
then repeat for each map
it helped me to draw a picture of what a map is using 2 number lines
I could only brute force it today... not sure I want to return to it for a better solution
I ended up just using the ranges and comparing each seed with > or < than start/ends
now I'm just burning the cpu in part 2
I'd be curious to see the range implementation, I'm sure there's a more elegant solution
Today's puzzle should have been day 1
Agreed, it was surprisingly simple
does anyone have a solution for part 2 of day 5 that works?
Mine has a off by 1 error that I fail to understand
I also had an off by 1 error. Quite painful because I was brute-forcing part 2 and had to run it again ๐
My off by 1 error was in the ranges and it didn't show up for the example in either parts and also not the real input in part 1
Thanks I just found it: <= was the sneaky error
Ha, same here!
Seems like you already got your answer but here is one possible approach
https://github.com/steffenhaug/aoc/blob/master/2023/5/main.ml
I'm not 100% happy with it, particularly the big true/false match in the middle, but I don't really know any better way to do the edge cases
Hm, I implemented the version with one traversal of the list of map ranges by map , but your version might be better in term of overall code complexity.
It runs pretty fast, but i also think the problem is just quite small in terms of number of intervals, so I'm not quite sure
Since i re-traverse the rules for every set its at least worse than linear, so a solution with one traversal of the map ranges could probably beat it
for day 3 pt 2, do we need to process negative numbers?
'-' is a part that is not a gear, not a negation
at least that's how I interpreted it, but then none of my gears touched a number with a leading -
Camel Cards! In OCaml of course
I'm spinning on part 2 despite using a set to minimize branching paths ๐ฆ
looks like there are always 6 elements so maybe i'm just wasting time
|| am I supposed to use MATH to find the solution instead of simulating the cycles? an outrage ||
||thankfully it was pretty simple math :-D||
Today is relatively easy, heres my solution:
||
let get_last l = List.rev l |> List.hd
let parse input = String.split_on_char ' ' input |> List.map int_of_string
let rec map_by_two f l =
match l with
| hd :: mid :: tl ->
let r = f hd mid in
r :: map_by_two f (mid :: tl)
| _ -> []
let madness_descent l =
let rec descent l =
match List.for_all (( = ) 0) l with
| true -> 0
| false ->
let res = map_by_two (fun l r -> r - l) l in
get_last res + descent res
in
descent l + get_last l
let solve_part_1 input =
List.map parse input
|> List.map madness_descent
|> List.fold_left ( + ) 0
let solve_part_2 input =
List.map parse input
|> List.map List.rev
|> List.map madness_descent
|> List.fold_left ( + ) 0
||
Anyone had luck on day 12?
It broke my brain thinking about the permutations with ocaml
Iโve enjoyed it until now, it feels like a breath of fresh air coming from python
part 1 works with naive backtracking, part 2 works with some memoization or dynamic programming
any pointers for day 21 part 2? Thought I was clever just tracking the 2 outer layers but it's still too many points.
- you're tracking an area. How does an area grow?
- look for specific properties in the input regarding the starting point
(it's OK to have a look at the solutions on the aoc reddit!)
I was hoping for exactly such a clue, my instinct is to handle the most general form of the question and don't check to see if the input is "nice". I think I see the intended solution now.
The problem with pulses has the same thing