Fork me on GitHub

A demo of using Clojure to solve Advent of Code problems in your editor, using VS Code + #joyride.


And the starter template project is a bit updated as well:


Day 4 - solutions


;; 202204
(let [d (->> (slurp "src/y2022/input202204")
             (re-seq #"\d+")
             (map parse-long)
             (partition 4))
      f (fn [[a1 a2 b1 b2]]
          (or (<= a1 b1 b2 a2)
              (<= b1 a1 a2 b2)))
      g (fn [[a1 a2 b1 b2]]
          (or (<= a1 b2 a2)
              (<= b1 a2 b2)))]
  (map #(count (filter % d)) [f g]))
;; (464 770)

👏 14

(def input
  (->> (util/load-lines "resources/")
       (map #(re-seq #"\d+" %))
       (map util/parse-ints)))

;; Part 1
(defn fully-contains? [[a b c d]]
  (or (<= a c d b)
      (<= c a b d)))

(count (filter fully-contains? input)) ;; 487

;; Part 2
(defn overlap? [[a b c d]] (and (<= a d) (<= c b)))
(count (filter overlap? input)) ;; 849


After the initial impl, I tried to write a fast non-allocating one, but got a measly 8.1x speedup for a trememdous readability-cost:

;; 988.65µs (+- 54.56µs)
(defn day4 [fs]
  (let [xs (->> (re-seq #"(?:(\d+)-(\d+),(\d+)-(\d+)\n)" fs)
             (mapv #(mapv parse-long (rest %))))
        count-by #(transduce
                    (comp (filter %) (map (constantly 1)))
                    + xs)]
    [(count-by (fn [[a b x y]] (or (<= x a b y) (<= a x y b))))
     (count-by (fn [[a b x y]] (and (>= y a) (<= x b))))]))

;; 121.68µs (+- 7.01µs)
(defn day4b [^String fs]
  (let [add (fn [^long x ^Character c]
              (+ (- (int c) 48) (* 10 x)))
        n (count fs)]
    (loop [i 0, state 0, p1 0, p2 0
           a 0, b 0, x 0, y 0]
      (if (= i n) [p1 p2]
        (let [c (.charAt fs i)]
          (case c
            (recur (inc i) 0
              (if (or (<= x a b y) (<= a x y b))
                (inc p1) p1)
              (if (and (>= y a) (<= x b))
                (inc p2) p2)
              0 0 0 0)
            (\- \,)
            (recur (inc i) (inc state) p1 p2 a b x y)
            (case state
              0 (recur (inc i) state p1 p2 (long (add a c))      b                x             y)
              1 (recur (inc i) state p1 p2       a         (long (add b c))       x             y)
              2 (recur (inc i) state p1 p2       a               b         (long (add x c))     y)
                (recur (inc i) state p1 p2       a               b                x      (long (add y c))))))))))

Piotr Kaznowski07:12:33

(defn parse [line]
  (->> line (re-seq #"\d+") (map parse-long)))

(defn fully? [[A B X Y]]
  (or (<= A X Y B) (<= X A B Y)))

(defn partially? [[A B X Y]]
  (or (<= X A Y) (<= A X B)))

(defn -main [day]
  (let [input (map parse (file->lines day))
        overlaps (fn [f] (count (filter true? (map f input))))]
    {:part1 (overlaps fully?)
     :part2 (overlaps partially?)}))

👍 2
Piotr Kaznowski08:12:29

Oh, filter true? is totally redundant here!


I guess there are two types of solutions. 1. comparing numbers of start and end ranges 2. using sets I will always use sets for these types of problems. Feels more flexible and better describable.


I'm not an algo expert, but the solution with sets is much less optimal, because it... generates sets (time proportional to the sizes of the ranges and space proportional to the set size). This is contrary to the golden rule of algos: Don't compute what is not needed.


Of cause I’m not talking about performance. I was talking about what kind of approach comes more naturally.

👍 1

hmm, another 'comparing ranges' answer: obvious in hindsight that parsing things into [[a1 b1] [a2 b2]] pairs made things less neat!


(ooh, weird how the preview renders <= and >= as = 🙃)


same solution as above. Only thing interesting was my helper from previous years: (def input (f/read-ranges "2022/d04.txt"))

👏 1
Luis Santos11:12:32

(defn parse-assignment [a]
  (->> (string/split a #"-|,")
       (map #(Integer/parseInt %))
       (partition 2)
       (map (fn [[a b]] (range a (inc b))))
       (map set)))

(defn subset? [[a b]]
  (or (clojure.set/subset? a b)
      (clojure.set/subset? b a)))

(defn intersection? [[a b]]
  (not (empty? (clojure.set/intersection a b))))

(defn day4 []
  (let [in (slurp "input/day-4-input-1.txt")
        assignments (map parse-assignment (string/split-lines in))
        count-assignments #(count (filter true? %))]
    {:part1 (count-assignments (map subset? assignments))
     :part2 (count-assignments (map intersection? assignments))}))

👍 1

Nice, very short and clean

👍 1

seq instead of not empty

Luis Santos12:12:00

@thierry572 not-empty would return nil for empty sets. I would have to change my (filter true?) to something else. What's the shortest way of achieving your suggestion?


Not at computer atm, what about (filter some? %)

Luis Santos12:12:45

I would have to change my subset? function to return nil. It would be change for change. I don't see an immediate benefit.


Good point. Doesnt seq yield the same result? Maybe (some? (not-empty


More then one way to reach Rome with Clojure (or any other language for that matter) :rolling_on_the_floor_laughing:

👍 1

This is one of those days that I'm going to now look at how other people did the overlap and contains and realise my solution is dumb...


I too considered sets, that does feel more natural to me, but then thought it was wasteful to generate all thsoe numbers

Luis Santos14:12:07

This was one of the best solutions i've seen. I always forget that these functions can take more than 2 arities.

(defn fully? [[A B X Y]]
  (or (<= A X Y B) (<= X A B Y)))

(defn partially? [[A B X Y]]
  (or (<= X A Y) (<= A X B)))

👍 2

@UK00KCK6D Using not=

(defn intersection? [[a b]]
  (not= #{} (set/intersection a b)))


@U013YN3T4DA I started out with sets aswell but ended up using a for loop on the sets with data/diff


A lot of good stuff here.

Andrew Byala17:12:59

I was rereading the solution of a coworker of mine, and I can see now that our overlaps? or partially-overlaps? function can actually be simplified to only compare two terms.

(defn overlaps? [[a b c d]]
  (and (<= a d) (<= c b)))


seems like that would also return true for partial overlaps, e.g. (overlaps? [1 3 2 4]) ?

Casey19:12:51 Not very memory efficient, but using range and sets made this day's quite fun


the advent of transducers continues:

(defn count-filtered [data pred]
    (comp (map #(str/split % #","))
          (map #(str/split % #"-"))
          (map parse-long)
          (partition-all 4)
          (filter pred))

(def input (-> "adventofcode2022/day4.txt"

  ;; part 1
  (count-filtered input (fn [[a b c d]]
                          (or (<= a c d b)
                              (<= c a b d))))

  ;; part 2
  (count-filtered input (fn [[a b c d]]
                          (or (<= a c b)
                              (<= a d b)
                              (<= c a d)
                              (<= c b d)))))

👍 1

Here is mine: I made an explorative chart at the end to see if there are any interesting patterns in the intersection data (not really 😕).


(ns day4)

(defn parse [line]
  (map #(map parse-long (split % "-")) (split line ",")))

(def data (->> "input/day4.txt" slurp lines (map parse)))

(defn contain? [[a b] [c d]]
  (or (and (>= c a) (<= d b)) (and (>= a c) (<= b d))))

(defn in? [a b c] (and (>= b a) (<= b c)))

(defn overlap? [[a b] [c d]]
  (or (in? a c b) (in? a d b) (in? c a d) (in? c b d)))

(defn solve [f]
  (->> data (map (partial apply f)) (filter identity) count))

(println "1:" (solve contain?))
(println "2:" (solve overlap?))


(my >= and =< only take 2 args 😫)


(ns day-04
  (:require [clojure.set :refer [subset?]]))

(defn input []
  (for [line (re-seq #".+" (slurp "../"))
        :let [[a b c d] (map parse-long (re-seq #"\d+" line))]]
    [(set (range a (inc b))) (set (range c (inc d)))]))

(defn count-matching [f]
  (count (filter #(apply f %) (input))))

(defn solution-1 []
  (count-matching #(or (subset? %1 %2) (subset? %2 %1))))

(defn solution-2 []
  (count-matching #(or (some %1 %2) (some %2 %1))))


(ns aoc2022.day4
  (:require [ :refer [resource]]
            [clojure.string :as str]
            [clojure.set :refer [intersection]]))

(defn data []
  (->> (resource "inputs/day4.txt")

(defn complete-overlap? [[s1 e1 s2 e2]]
  (or (and (<= s1 s2) (>= e1 e2))
      (and (<= s2 s1) (>= e2 e1))))

(defn partial-overlap? [[s1 e1 s2 e2]]
  (not (or (< e1 s2) (> s1 e2))))

(defn solution [overlap?]
  (->> (data)
       (map #(str/split % #","))
       (map (partial map #(str/split % #"-")))
       (map (fn [[[s1 e1] [s2 e2]]]
              (map #(Integer/parseInt %) [s1 e1 s2 e2])))
       (filter overlap?)

(def part1 (partial solution complete-overlap?))
(def part2 (partial solution partial-overlap?))


These are amazing. It takes me longer to understand the question.

First hundred users to get the first star on Day 4:

  1) Dec 04  00:00:16  max-sixty (AoC++)
  2) Dec 04  00:00:32  Luke M
  3) Dec 04  00:00:44  dtinth (AoC++)
  4) Dec 04  00:00:55  nim-ka

😲 3

Mine were 5m6s and 6m21s for the two stars resp. It took me ~2m just to read the question xDD.


I wonder how it is possible to submit the solution in 16s...


Seems almost impossible to do this in 16 seconds, it takes longer to read the questions


Too bad the answer time is counted from release of the questions and not from when you actually start with them. I am still asleep at 6am.

Luis Santos11:12:54

That's not what I'm seeing.


@UK00KCK6D thats for both stars. OP is about part 1

🤯 2
😅 1
👀 1

But still, 16 seconds?


Assuming that getting input-data and submitting is automated to a keybinding, a question-comprehesion time of 10s and typing the solution in 6s seems possible to me. Possible, but extremely impressive of course.


With that little time though, there's no way one can read the whole text. But, here are the example-data and the bold (and glowing) question:


In how many assignment pairs does one range fully contain the other?
These are enough to understand the question, and it doesn't seem too outrageous this way.

👍 1

Ruby has a cover? method on Ranges which makes it trivial, but I think for these speeds it’s AI:

👍 1
Luis Santos13:12:39

I checked previous years and there were similar speeds for the 2 rounds.


> it doesn't seem too outrageous this way It seems too outrageous to me still. 😃

😄 1

There are some nice writeups from past years from folks who placed highly in the leaderboard

👍 1

@coyotesqrl I really like the Clerk-generated pages. What happens if you don't put the visibility metadata before the function definitions? Do you get a list of the vars that are created when you define the function?

R.A. Porter07:12:24

Yep. It's not terrible, but seeing the metadata is less noisy to me than seeing the var definitions. I could apply the metadata to the whole namespace and then change on a per-form basis as needed as well. I might do that on some days.

R.A. Porter07:12:54

It'll be more useful/interesting if there are any puzzles that lend themselves to interesting accompanying visuals, assuming I can solve them and want to put in the extra effort to create visualizations.


That's cool. It's great having the puzzle text right in line, too. It's certainly making me tempted to explore it as an option.


I am using it this year but until now there was no benefit from it compared to just using the repl. It just took up more screen real estate. I hope some more visual problems will come soon.

👍 1

day 2

🎉 5

I wanted to make the visual more pronounced per part.

🎉 1

Did you manage to upload this to github pages yet?


no, I lost the motivation after a few hours of custom domains BS. But I'll look at again tonight. Want to get days 3 and 4 out of the way first.

🎉 1

ok, on to day 3 :)


thanks to @tylerw for the basis of the visuals. I did quite some mods but it was good not to have to worry about the correctness of the results 🙏:skin-tone-3:

👍 1
Luis Santos18:12:48

Asked GPT to create a clojure program. Quite impressive the fact that it can generate something that "looks" correct.

;; define a function that checks if one range fully contains the other
(defn fully-contains [a b]
  ;; split the two ranges into their start and end points
  (let [[a-start a-end] (map #(Integer/parseInt %) (a/split "-"))
        [b-start b-end] (map #(Integer/parseInt %) (b/split "-"))]
    ;; check if the range a fully contains range b
    (if (and (<= a-start b-start) (>= a-end b-end))

;; define a list of section assignment pairs
(def pairs ["2-4,6-8" "2-3,4-5" "5-7,7-9" "2-8,3-7" "6-6,4-6" "2-6,4-8"])

;; initialize a counter variable
(def counter 0)

;; iterate over all pairs of section assignments
(doseq [[i j] (for [i (range (count pairs)) j (range (inc i) (count pairs))] [i j])]
  ;; check if one range fully contains the other
  (if (or (fully-contains (nth pairs i) (nth pairs j))
          (fully-contains (nth pairs j) (nth pairs i)))
    ;; if it does, increment the counter variable
    (inc counter)))

;; print the number of pairs where one range fully contains the other
(println counter)


Hey I just want to point out that using read-string is a really good way to parse most of the input text for these problems.

(def data (-> (slurp "src/adv2022/day4/input.txt")
              (string/replace "-" ",")
              (->> (partition 2) (partition 2))))

👍 4
💯 1

in JS I had a horrible hack for day 1

(->> @data
     (map (fn [s]
       (let [n (js/parseInt s)]
        (when-not (js/isNaN n) n))))
     (partition-by nil?)
     (keep #(reduce + %)))


but this works perfectly

(->> @data
     (map edn/read-string)
     (partition-by nil?)
     (keep #(reduce + %)))


Thanks for the tip!!

simple_smile 1

First I place delimiters in the file around the contents



This reminds me of one of my favorite clojure tricks - write data into edn from another program, then just edn/read-string it into clojure's happy place

👍 1

Yeah I love in general how easy it is to get data in


I split the lines and map read-string over them.

metal 1

You don’t need to parseInt or any of these thing just use read-string for just about everything


I guess I should just be safe and mention the disclaimer that you shouldn’t use clojure.core/read-string in production apps …


I used a macro for the first 3 days

(defmacro adventreader
  `(let [data# (with-open
                [rdr# (io/reader ~resource)]
                 (doall (line-seq rdr#)))]


and this for today

                        (io/input-stream data))


@bhauman why not use clojure.edn/read-string, did you need to parse programs? (I haven't looked at the input)


@borkdude I’m just using read-string because it’s fast and easy and part of clojure.core. Perfect for hacking data. clojure.edn/read-string is for real world use cases for certain.


@thierry572 read-string parses the input returning integers, lists, vectors etc thus you can skip the parsing step


I know, I use it a lot daily, but didnt want to manipulatr the source data first :rolling_on_the_floor_laughing: just lazy, thanks for the tip tho.

👍 1

I revised my babashka tasks, maybe somebody else will find them useful. Still not library ready, maybe by end of month. Right now hardcoded for my directory structure. Any feedback before make it general? Changes: • modernized to Babashka CLI • tasks take optional -y YYYY -d DD args, which default to this year and today • bb download : saves file locally • bb badges : only 1 total call to AOC instead of 1 per year, and now includes Total stars • bb template creates src/test stubs from Selmer templates for the day • bb go combo: downloads input, creates templates, and opens browser to the day’s pages (on Mac).

👍 6

Awesome, feel free to send a PR to advent-of-babashka under my github name and add the link to "external resources"

Piotr Kaznowski22:12:42

I'm new to pods -- tried to run bb badges and am getting

Message:  Cannot run program "/home/piotr/.local/share/.babashka/pods/repository/retrogradeorbit/bootleg/0.1.9/linux/x86_64/bootleg": error=2, No such file or directory
Threre's no bootleg executable at the location, only manifest.edn file. Before it crashes, it prints:
Downloading pod retrogradeorbit/bootleg (0.1.9)
Successfully installed pod retrogradeorbit/bootleg (0.1.9)
What am I missing here?


@UUAKPEMJ9 Which OS is this?


and what architecture?


ldd /home/piotr/.local/share/.babashka/pods/repository/retrogradeorbit/bootleg/0.1.9/linux/x86_64/bootleg


cc @U1QQJJK89 - might be good to provide a fully static musl compiled binary for bootleg

Piotr Kaznowski22:12:32

$ ldd /home/piotr/.local/share/.babashka/pods/repository/retrogradeorbit/bootleg/0.1.9/linux/x86_64/bootleg
ldd: /home/piotr/.local/share/.babashka/pods/repository/retrogradeorbit/bootleg/0.1.9/linux/x86_64/bootleg: No such file or directory


aha, so the file doesn't exist at all?

Piotr Kaznowski22:12:48

Yes, as I said above: > Threre's no bootleg executable at the location, only manifest.edn file. Before it crashes, it prints:


then it's not the issue I suspected it to be


This is what I'm seeing:

$ bb -e "(require '[babashka.pods :as pods]) (pods/load-pod 'retrogradeorbit/bootleg \"0.1.9\")"
Downloading pod retrogradeorbit/bootleg (0.1.9)
Successfully installed pod retrogradeorbit/bootleg (0.1.9)
#:pod{:id "pod.retrogradeorbit.bootleg.glob"}


I'll also try on ubuntu...


can you maybe try the above with:

BABASHKA_PODS_DIR=/tmp bb -e "(require '[babashka.pods :as pods]) (pods/load-pod 'retrogradeorbit/bootleg \"0.1.9\")"

Piotr Kaznowski22:12:24

Hmm, that worked with /tmp!


On ubuntu it also worked fine for me. Perhaps you could try to wipe the pods directory and try again

Piotr Kaznowski22:12:30

It reproduces the erratic behavior.


could it be a file system permission thing?


you could try to debug locally with - the load call should also work on the JVM

Piotr Kaznowski22:12:15

OK, will try, thanks.

Piotr Kaznowski22:12:57

(Meanwhile I can hack it with BABASHKA_PODS_DIR : I have my bb script for AoC, wanted to play with the badges -- thanks @borkdude & @U1Z392WMQ)

👍 1

@UUAKPEMJ9 I found the problem that caused this. You should be able to remove the workaround with the current master build:

bash <(curl ) --dev-build --dir /tmp


The issue was that the directory wasn't created before untar-ring

Piotr Kaznowski10:01:01

@borkdude Cool! Will have a look.