Home
About
Personal
Tech

Advent of Code 2020: Day 3

Ramping up productivity

I originally planned to only do weekly posts, but I’ll write every day as long as I find something worth writing about. It’s nice to be able to measure the time I spend writing posts in minutes instead of hours.

Day 3’s problems were not particularly challenging to me. I don’t really have much to say about the code I wrote, but I did, however, figure out a much more productive development workflow.

I’m a big fan of REPL-driven development, and that’s a dream in Julia with julia-repl and Revise. Where I was previously running utop in a separate terminal, and only executing bits of code there manually to experiment, I’m now using utop from within Emacs, and sending code to it directly from my source buffer. I also discovered the incredibly useful Merlin shortcut for merlin-type-enclosing, which lets you display the type of any expression. Since type errors account for 95% of compiler errors that I run into, this is a huge time saver.

There’s still one gap in my workflow, though, and it’s the one that Revise fills for Julia. It doesn’t seem to be possible to recompile my code without restarting utop. Instead, I just send my entire buffer over, which (re)defines all my functions at the top level. It’s a good enough workaround.

To run tests, I set compile-command to dune runtest, and bind compile to C-c C-c, so that running the tests is easy and quick. To actually write unit tests, I’ve been using ppx_inline_test to put tests right underneath my source code.

Without further ado, here’s the code, including the tests:

open Core
open Util

let build_grid lines =
  lines |> Array.of_list
  |> Array.map ~f:(fun line ->
         line |> String.to_array |> Array.map ~f:(Char.equal '#'))

let rec traverse_rec grid ~x ~y ~xd ~yd ~trees =
  if y >= Array.length grid then trees
  else
    let row = grid.(y) in
    let loc = row.(x mod Array.length row) in
    let trees = if loc then trees + 1 else trees in
    traverse_rec grid ~x:(x + xd) ~y:(y + yd) ~xd ~yd ~trees

let traverse grid ~x ~y = traverse_rec grid ~x:0 ~y:0 ~trees:0 ~xd:x ~yd:y

let part1 lines = lines |> build_grid |> traverse ~x:3 ~y:1

let part2 lines =
  let grid = build_grid lines in
  [ (1, 1); (3, 1); (5, 1); (7, 1); (1, 2) ]
  |> List.map ~f:(fun (x, y) -> traverse grid ~x ~y)
  |> List.reduce_exn ~f:(fun x y -> x * y)

let%test "examples" =
  let lines =
    "..##.......\n\
     #...#...#..\n\
     .#....#..#.\n\
     ..#.#...#.#\n\
     .#...##..#.\n\
     ..#.##.....\n\
     .#.#.#....#\n\
     .#........#\n\
     #.##...#...\n\
     #...##....#\n\
     .#..#...#.#" |> String.strip |> String.split ~on:'\n'
  in
  part1 lines = 7 && part2 lines = 336

let%test "input" =
  let lines = read_lines (input_file 3) in
  part1 lines = 276 && part2 lines = 7812180000

And here’s my Emacs configuration:

(use-package dune)
(use-package ocamlformat)
(use-package tuareg)
(use-package merlin
  :hook (tuareg-mode . merlin-mode))
(use-package utop
  :custom  utop-command "opam exec -- dune utop . -- -emacs"
  :bind (:map utop-minor-mode-map
              ("C-c C-e" . utop-eval-phrase))
  :hook (tuareg-mode . utop-minor-mode))
(add-hook 'tuareg-mode-hook (lambda ()
                              (setq-local compile-command "dune runtest"
                                          compilation-read-command nil)
                              (add-hook 'before-save-hook 'ocamlformat-before-save)))
(dolist (var (car (read-from-string (shell-command-to-string "opam env --sexp"))))
  (setenv (car var) (cadr var)))
(setq exec-path (split-string (getenv "PATH") path-separator))

See all of my code here.