Advent of Code 2017

Advent of Code 2017 has begun!

There’s a dedicated #adventofcode channel in the Clojurians Slack. Join us for general chat about the puzzles and solutions sharing.

My personal goal is to get enough experienced Clojurists in the channel so they can teach me idiomatic Clojure :slight_smile:

6 Likes

Cool stuff! I think I’d heard of it before but never really checked it out before today.

I wrote my solution for the first day as a Lumo script

Expand to see code (spoiler)
(def +input+ (last js/process.argv))

(print
 (->> +input+
      (into [])
      (#(conj % (first +input+)))
      (map js/parseInt)
      (partition 2 1)
      (filter (partial apply =))
      (map first)
      (apply +)))
2 Likes

And here’s day 2. This time with regular Clojure (e.g. run with clj)

I tried to use my Ergodox for this so it took a long time and I did the debugging with my regular keyboard cause there’s a limit to how much frustration I can take :slight_smile:

Too bad that as a European it’s basically impossible to take place in the ranking. Only the first 100 people get points, I did the exercise in the morning and I’m 3200th…

Expand to see code
(ns day02
  (:require [clojure.string :as str]))

(def input (slurp "input.txt"))

(defn split-rows [input]
  (str/split input #"\n"))

(defn split-cells [input]
  (str/split input #"\s+"))

(defn row-max-diff [row]
  (->> row
       (map #(Long/parseLong %))
       ((juxt (partial apply max) (partial apply min)))
       sort
       reverse
       (apply -)))

(print (->> input
            split-rows
            (map split-cells)
            (map row-max-diff)
            (apply +)))

If you’re doing Advent of Code in Clojure, join the fun!

1 Like

Day three already felt a lot more challenging! Some proper discrete math stuff :slight_smile:

I have a feeling there must be a more elegant solution, but I did solve it in the end.

Show code

https://github.com/plexus/AdventOfCode2017/blob/master/src/advent/day03.clj

Day 4 part 1. How’s this for a beginner?

Show code
(ns main
  (:require [clojure.string :as str]))

(defn read-input-file [filename]
  (->> 
   (slurp filename)
   str/split-lines
   (map read-string)))

(def grid (read-input-file "input.txt"))

(defn checkrow [xs]
  (= (count xs) (count (distinct xs))))

(count (filter true? (map checkrow grid)))

I thought day 4 was a lot more straightforward again. Maybe I was making things too complicated for day 3?

Also I just figured out there’s a second part every day :man_facepalming: :smiley:

Haha yep! It’s generally a harder twist on the initial problem.

For day 3, I think the difficulty was just in visualizing a solution (I’ve never worked with spiral arrays before).

The part two solution didn’t require code; it was on OEIS:

http://oeis.org/A141481

I came up with a more “mathematical” solution for day 3 part 1, rather than actually creating the full matrix, but this didn’t help for doing part 2 at all :slight_smile:

1 Like

Had a go at day 4.

Code

Part I

(->> passphrases 
  (map #(clojure.string/split % #" ")) 
  (map (fn [e] [(count e) (count (distinct e)) e]))
  (filter (fn [[t d _]] (= t d))
  (count))

Part II

(->> passphrases 
  (map #(clojure.string/split % #" ")) 
  (map (fn [e] [(count e) (count (distinct e)) e]))
  (filter (fn [[t d _]] (= t d)))
  (map (fn [[_ _ e]] (->> e
                                (map frequencies)
                                (distinct)
                                (count)
                                ((fn [s] [s (count e) e])))))
   (filter (fn [[s e ee]] (= s e)))
   (count))

Exactly what happened to me! I was happy that I figured out a “rule”-based approach for part 1. Then in part 2 I had to actually create the spiral array :slight_smile:

In any case here are my solutions for day 3 https://github.com/greenonion/advent-of-code/blob/master/src/advent_of_code/2017/day_3.clj

I didn’t avoid using loop-recur which I know isn’t considered idiomatic Clojure. I’m not sure how I would avoid it in this case though. I’m also studying SICP at the moment so it has definitely influenced me!

I don’t think there’s anything wrong with loop/recur. Sometimes it’s hard to do something any other way.

Here is my day 4

Show code
(defn valid-passphrase?
  [pass-str]
  (->> (clojure.string/split pass-str #"\s+")
       (apply distinct? ))) 

(defn anagram?
  "sorting the string makes comparison super easy"
  [pass-str]
  (->> (clojure.string/split pass-str #"\s+")
       (map sort)
       (apply distinct? )))

Day 4 code

Day04 tests

Hi @javazquez, I’ve hidden your code so people can avoid seeing spoilers. You can do the same by clicking on the little gear icon in the editor and choosing “Hide details”.

1 Like

I just did day 5 (see the code).

I liked this one, I think the Clojure solution is pretty elegant. It’s certainly a good example of using recur, I wouldn’t know how to do this one without it.

When solving the second part I was worried for a bit I had ended up with infinite recursion cause it took quite some time to finish, about 21.5 seconds on my machine. It is performing 28 million steps but still. My guess is that’s the price you pay for constantly updating vectors. That’s a lot of HMAC nodes that are being created and consequently garbage collected.

ok, seems I was very wrong there. I tried to make a version that uses Java arrays, and it’s an order of magnitude slower. Clojure data structures for the win!

Final update for today :slight_smile: The Java array version was about 38x times slower than the Clojure vector version, which I found surprising, so I tried if I could speed it up.

I split a few things up into helper functions so I could type hint them, and used Java interop instead of aget and aset, all to try and prevent it from having to box and unbox numbers, and from having to reflect on types.

This worked, now the Java array version is about 3x faster than the Clojure version, so about 100x faster than the Java version without type hints.

here’s the result

Conclusion: always measure before you try to speed things up. Just because you’re using more lower level operations doesn’t always mean it’s faster.

Consider:

(defn contained-square-root [input]
  (reduce (fn [last-n n]
            (if (> (* n n) input)
              (reduced last-n)
              n))
          (range)))

That’s very likely because you used reflection. (aget ...) is implemented depending on the array type. If CLJ doesn’t know which array you’re dealing with it’ll use reflection (Turn on reflection). You can avoid that by type hinting with ^longs:

(fn [^longs x] (aget x idx))

Should be pretty fast.

oh good call, I had forgotten you can also type hint primitive arrays.

My type hinted version with lots of ^long thrown in performed pretty well though.