AoC 2020, Day 3

    Day three challenge is a bit tricky. I can see the next one being more difficult, which is part of the fun.

    I looked at the challenge and immediately found myself reaching for Python shell. I'm not very familiar with the Clojure std lib for the repl to be useful as a means to write PoC.

    Since I'm trying to learn Clojure, the obvious choice would have been to try to solve this on the Clojure repl. But I couldn't help it.

    This also makes me wonder; is there a good quality "Clojure for Python Programmers" crash course type guide? Something to help me map the mappable concepts from Python to Clojure std lib.

    Python solution

    Here's what I came up with on the ipython shell.

    In [1]: forest = """
       ...: ..##.........##.........##.........##.........##.........##.......
       ...: #...#...#..#...#...#..#...#...#..#...#...#..#...#...#..#...#...#..
       ...: .#....#..#..#....#..#..#....#..#..#....#..#..#....#..#..#....#..#.
       ...: ..#.#...#.#..#.#...#.#..#.#...#.#..#.#...#.#..#.#...#.#..#.#...#.#
       ...: .#...##..#..#...##..#..#...##..#..#...##..#..#...##..#..#...##..#.
       ...: ..#.##.......#.##.......#.##.......#.##.......#.##.......#.##.....
       ...: .#.#.#....#.#.#.#....#.#.#.#....#.#.#.#....#.#.#.#....#.#.#.#....#
       ...: .#........#.#........#.#........#.#........#.#........#.#........#
       ...: #.##...#...#.##...#...#.##...#...#.##...#...#.##...#...#.##...#...
       ...: #...##....##...##....##...##....##...##....##...##....##...##....#
       ...: .#..#...#.#.#..#...#.#.#..#...#.#.#..#...#.#.#..#...#.#.#..#...#.#
       ...: """.strip().split("\n")
    
    In [2]: def count_trees(jungle, down, right):
       ...:     x, y = 0, 0
       ...:     tree_count = 0
       ...:     total_levels = len(jungle)
       ...:     x_len = len(jungle[0])
       ...:     while y < total_levels:
       ...:         targetY = jungle[y]
       ...:         char = targetY[x]
       ...:
       ...:         if char == "#":
       ...:             tree_count += 1
       ...:
       ...:         y += down
       ...:         x += right
       ...:         if x >= x_len:
       ...:             x = divmod(x, x_len)[1]
       ...:     return tree_count
       ...:
    
    In [3]: count_trees(forest, 1, 3)
    Out[3]: 7
    

    It took several attempts to reach here. I also restarted ipython shell to make In [1] appear so as to give the impression that I solved it on my first try.

    The same function can be used to solve the second part of the challenge.

    In [4]: with open("resources/day3-input.txt") as fi:
       ...:     jungle = list(filter(bool, map(str.strip, fi.readlines())))
       ...:
    
    In [5]: import math
    
    In [6]: math.prod([count_trees(jungle, down, right) for right, down in
                       [(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)]])
    Out[6]: 1744787392
    

    Unlike my previous solutions, this one didn't need significant refactoring but it was a coincidence.

    The problem with this solution is that a "literal translation" to Clojure is going to look like a pile of vomit; if possible that is. This function not written in a functional way.

    Clojure

    It took me until the weekend to get back to it again. I'm picking it up by implementing the day 3 solution in Clojure.

    Here's the final product.

    (ns advent-of-code.day3
      (:gen-class)
      (:use [clojure.string :only [split-lines]]))
    
    (defn read-input [] (split-lines (slurp  "./resources/day3-input.txt")))
    
    (defn traverse-x
      [line x]
      (if (< x 0)
        nil
        (get line (mod x (count line)))))
    
    (defn is-a-tree?
      [lines x y]
      (let [line (get lines y)
            point-chr (if (nil? line) nil (traverse-x line x))]
        (= point-chr \#)))
    
    (defn count-trees [[right down]]
      (let [lines (read-input)
            total-lines (count lines)]
        (loop [x 0
               y 0
               treecount 0]
    
          (if (> y total-lines)
            ;; we've exhausted the tree line; return
            treecount
    
            ;; traverse and see there's a tree exists
            (let [is-tree (is-a-tree? lines x y)
                  treecount (if is-tree (+ 1 treecount) treecount)
                  x (+ x right)
                  y (+ y down)]
              (recur x y treecount))))))
    
    
    (defn part1 []
      (count-trees [3 1]))
    
    (defn part2 []
      (let [slopes [[1 1] [3 1] [5 1] [7 1] [1 2]]]
        (reduce * (map count-trees slopes))))
    

    I'm sure this can be improved a lot. I've never been comfortable with my understanding of loops/iterations in lisps. I hope to fix that with some practice in the coming days.

    I also learned that I could group test assertions together in one deftest. It makes perfect sense on hindsight but I'm a slow learner.

    (deftest test-solutions
      (testing "test solutions"
        (is (= (part1) 257))
        (is (= (part2) 1744787392))))
    

    I'm off to solving day 4. This time I'll try to skip solving in Python and try to do it on Clojure altogether.