Fork me on GitHub
Vincent Cantin05:12:56

Good morning !

😁 12

Hello! Today’s problem was pretty straightforward.. I am tied with the amount of problems I finished last year.. Not close to burning out.. I wonder if it is going easy on us this year?

Vincent Cantin05:12:26

I am awaiting the difficult problems like a surfer awaiting its wave.


You can pick one from the last year ^_^

👍 3
💯 3
Vincent Cantin06:12:36

I will keep them for January.


Agree! #6 usually starts ramping way up.


why give us a difficult problem on the weekend when you can save it for a weekday…

😆 6
Vincent Cantin06:12:17

During the first 1 min 30, I was staring at a blank page, waiting for the page to load.


AoC really starts the first time you add

(set! *unchecked-math* :warn-on-boxed)
(set! *warn-on-reflection* true)
to a namespace

😏 9
👟 3
Vincent Cantin06:12:33

I like @dos’s solution, which did not separate the input loading from the processing for the solution. It might be faster to type in that way.


This is the first year I'm (seriously) trying to do this. Does it usually get harder day by day?


(Today's puzzle was pretty easy, but if they're going to get difficult I might not have time to see it through.)


Problems have been surprisingly simple so far this year


Last year for me, was by far the most difficult of them all


That’s probably because we’re going to vacation 😃

😄 3

that or he’s lulling us into complacency just before dropping the hammer tomorrow


oh you got work Monday morning? Hope you remember A* search

😁 12

I skipped AoC last year because the year before I ended up spending sometimes multiple hours on a single solution. If it gets like that again I'm out, but a bit more challenging than today's would be nice too 🙂


I’ve done them since 2015, finished most but not all of them.. Except last year i bailed very early


Today's video:; I also talk a bit about the different ways that people solved yesterday's puzzle

👍 6

Many thanks to folks who dropped into the live chats or left comments!

Vincent Cantin07:12:42

Let me know if I leave too many comments during the streaming 😅 I have a hard time holding them while excited by the contest.


All good, it's nice to see some activity :)


they definitely get difficult and I was spending hours on problems routinely, but i’m not a fast solver


last year by like day 14 you had implemented a VM for a made up language, and then implemented pong using that language


I'm not a fast solver either, and also I'm doing them in Clojure, which I don't really know, so I'm extra slow.


i know the feeling… i’m trying to go back and do them in Go after I finish in Clojure, and it is … painful (i don’t know Go)


lots of typing


Day 6 Answers Thread - Post your answers here


(ns day6
  (:require [clojure.string :as str]
            [clojure.set :as set]))

(def input "...")

(def groups (str/split input #"\n\n"))

;; Part I
(apply + (for [group groups] (count (distinct (str/replace group #"\n" "")))))

;; Part II
(apply + (for [group groups
               :let [people (str/split-lines group)
                     answer-sets (map set people)]]
           (count (apply set/intersection answer-sets))))


(ns aoc.2020.d06
  (:require [aoc.file-util :as file-util]
            [clojure.set :refer [intersection]]
            [clojure.string :as str]))

(def input (map str/split-lines (file-util/read-chunks "2020/d06.txt")))

(defn part-1 [input]
  (reduce + (map (comp count distinct (partial apply str)) input)))

(defn part-2 [input]
  (reduce + (map (comp count (partial apply intersection) (partial map set)) input)))


first pass, feels inelegant


i feel the same way, but 🤷


im guessing there’s a frequencies-based solution


I misread part1 and actually solve part2 first 🙂


@U1G869VNV nice ... (str/split input #"*\n\n*") that did not occur to me, I do a (partition-by empty?) 🙂

metal 3
💯 3
Narve Saetre20:12:19

Noob code coming up - but I am actually quite happy with this code, it reads nicely I think:

(defn count-group [group]
  (->> group
       (map set)
       (apply clojure.set/intersection)

(->> (string/split (slurp "adv6.input.txt") #"\n\n")
     (map count-group)
     (reduce +))

Narve Saetre20:12:55

I love the -> and ->> macros, but sometimes they can't be used because the place to put the previous form is different ... any tips for that particular problem? Is there a -?> which "does the right thing depending on the form 😄

Narve Saetre20:12:46

Answering my self: I just searched and found as->, a very nice function. This allows me to write e.g.

(as-> "adv6.input.txt" v
      (slurp v)
      (string/split v #"\n\n")
      (map count-group v)
      (reduce + v))


@USAGL3YUS YMMV, but I try not to use as->, I find it has maintenance problems for me. I like to just pull out the different one as a let, so there’s only 1 threading type. In this case, could do:

(let [groups (-> path slurp (str/split #"\n\n")]
  (->> groups...))

Narve Saetre06:12:08

yeah, I c your point. I can also use minor (-> or (->> lines within the larger thread-block as well. Just gotta pick the right tool for the job 🙂


Day 7 Answers Thread - Post your answers here

Vincent Cantin06:12:32

I need to improve my regex capture skills. Let me know if you have a fast way to do the parsing/capturing, I am interested to learn.


not ideal, but I came up with this for parsing

(defn parse-entry [entry]
  (let [p  (re-find #"\w+ \w+" entry)
        cs (->> (re-seq #"(\d+) (\w+ \w+)" entry)
                (reduce (fn [m [_ v k]] (assoc m k (Long/parseLong v))) {}))]
    [p cs]))

(def bags (into {} (map parse-entry (str/split-lines input))))

Vincent Cantin07:12:34

I just realized that the algorithm for part2 could be used for part1

oxalorg (Mitesh)07:12:35

My naive solution: Using some kind of memo could speed up first pass, but this works! ^_^


Mine: I don’t know why exactly I had to call set on the tree-seq entries in part 1 nor why I had to do apply + 1 in part 2, but it works 🙂

❤️ 6
Vincent Cantin08:12:25

I forgot about tree-seq … that’s a good idea.


🙂 i now see set is because you have to find amount of different colors and + 1 is because you also have to count the containing bag


(into (set found-bags) (mapcat #(find-outer-bags bags %) found-bags)) nice @U067R559Q

😁 3

Wow nice, I tried to use core.logic last year for a somewhat similar problem ( but I did not manage

👍 3

@U2PGHFU5U thanks to your solution for part 2, I simplified mine. Now it’s pretty straightforward

(defn count-bags [bags [bag n]]
  (* n (apply + 1 (map #(count-bags bags %) (get bags bag)))))

😊 6

(loop [bags #{"shiny gold"}
       old-bags #{}]
  (if (= bags old-bags)
    (dec (count bags))
    (let [new-bags (into bags (map first
                                   (filter #(seq (clojure.set/intersection bags (second %))) data)))]
      (recur new-bags bags))))


(defn get-bags-inside [times [color amount]]
  [(mapv (partial get-bags-inside (* times amount)) (data color)) (* times amount)])

(dec (apply + (flatten (get-bags-inside 1 ["shiny gold" 1]))))


Nothing very exciting in my solution but I will post anyway 🙂 It took me way too long to figure out I was off by one for part 2 because I was counting the top shiny gold bag :)


What am I missing with this regex? Works fine for groups of 1 or two but fails with 3 or more (the second group gets improperly matched)

(\w+ \w+) bags? contain ((\d+) (\w+ \w+).*? bags?)(, (\d+) (\w+ \w+) bags?)*\.


the * doesn’t repeat the capture group like you think it does


i’d consider re-seq


> What am I missing with this regex? @U0ESCTA1E if you repeat a group, this group will be overwritten by the next match. For such cases you need to split a line, or just write 2 regex. Check my solution above for example.

Ben List16:12:20

heres mine, O'm definitely interested to see what others came up with today as I'm fairly new to clojure still


Here's mine: Writing the parsing took longer than I'd like, but I really got stuck in part 1 due to a small logic-error. Part 2 only took about 10 more minutes.

Joe20:12:46 So impressed at how concise some of the answers here are. That was a fun problem though!


Again, lots of effort spent on processing the raw input into a nice shape. I notice my part 2 recursive function has the same pattern as several others. Took me some grinding to get it working. Still not pretty, oh well.

;; with input processed as:
;;     ["dull aqua" ([4 "dark fuchsia"] [1 "shiny purple"])]
(defn count-nestedbags [inp topbag]
  (letfn [(rcount [bag]
            (let [[_ bt] bag
                  contained (second (first (filter #(= bt (first %)) inp)))]
              (apply + 1
                     (for [[bc bn :as thebag] contained]
                       (if (= bn "no other") bc
                         (* bc (rcount thebag)))))))]
    (dec (rcount [1 topbag]))))

(count-nestedbags input-part2 "shiny gold")


This was so hard for me. It took me a lot of time to realize my first regex approach would not work. Then I had struggles wrapping my head around the recursion I needed. Then I made all sorts of silly mistakes even though the structure was right… And the result aint pretty!

(comment ; step 1
  (def input (util/fetch-input 7))
  (->> (clojure.string/split-lines input)
       (map #(clojure.string/split % #"[ ,]"))
       (map #(->> %
                  (partition 2 5)
                  (map vec)
                  (map (partial apply str))))
       ((fn dig [look-for found rules]
          (let [directs (reduce (fn [acc [bag & bags]]
                                  (if (seq (clojure.set/intersection (set bags) look-for))
                                    (conj acc bag)
            (if (seq directs)
              (dig directs (conj found directs) rules)
        #{"shinygold"} #{})
       (apply clojure.set/union)

(comment ; step 2
  (def input (util/fetch-input 7))
  (->> (clojure.string/split-lines input)
       (map #(clojure.string/split (str "1 " %) #"[ ,]"))
       (remove #((set %) "no"))
       (map #(->> %
                  (partition 3 5)
                  (map (fn [[n c1 c2]]
                         [(Integer/parseInt n) (str c1 c2)]))))
       ((fn dig [look-for found rules]
          (let [directs (reduce (fn [acc [[_n bag-color] & bags]]
                                  (->> (for [color look-for
                                             :when (= color bag-color)]
                                         (map #(repeat (first %) %) bags))
                                       (apply concat)
                                       (apply concat)
                                       (into acc)))
            (if (seq directs)
              (dig (map second directs) (into found directs) rules)
        ["shinygold"] [])


when I have troubles naming things, I know I don’t know what I am doing, but I pressed on on sheer intuition. ¯\(ツ)


@U067R559Q is my hero. his solution in this thread blows me away.


I didn’t finish Part II in this style yet, but I decided to try and do day7 in a rules engine (#clara). There’s a better way to write this, but this gets the right answer:

(defrecord ContainerBag [type contained-bags])
(defrecord IntermediateBag [parent-type type])
(defrecord GoldBag [parent-type])

(defrule produce-intermediate-bags
   (= ?type type)
   (= ?contained-bags contained-bags)]
  (when (seq ?contained-bags)
     (mapv (fn [[kind num]]
             (if (= kind "shiny gold")
               (map->GoldBag {:parent-type ?type})
               (map->IntermediateBag {:parent-type ?type
                                      :type kind})))

(defrule indirect-gold-bags
   (= ?type type)]
   (= ?type parent-type)
   (= ?intermediate-type type)]
   (= ?intermediate-type parent-type)]
  (insert! (map->GoldBag {:parent-type ?type})))

(defquery gold-bags []
  [?gold-bags <- (acc/distinct :parent-type) :from [GoldBag]])

(defn run-rules []
  (-> (mk-session :cache false)
      (insert-all (doall (for [[k vs] (parse input)]
                           (map->ContainerBag {:type k
                                               :contained-bags vs}))))
;; Part I
(-> (run-rules)
    (query gold-bags)
;; => 179


It would be cool to dynamically generate defrules for each line


Day 8 Answers Thread - Post your answers here


(defn run-code [code]
  (let [history (atom #{})]
    (loop [acc 0
           ip  0]
      (let [[op arg] (nth code ip [:hlt 0])]
        (if (contains? @history ip)
          {:inf-loop acc}
          (do (swap! history conj ip)
              (case op
                :hlt {:halt acc}
                :nop (recur acc (inc ip))
                :acc (recur (+ acc arg) (inc ip))
                :jmp (recur acc (+ ip arg)))))))))


And my final solution: in the Part 2 firstly I used case , but re-implement it with a hash-map as @U07FP7QJ0 showed on the live stream.


I always feel filthy when I use atoms…. This one is pretty dirty, but I got it done:


definitely a bit engineered, but I’m not getting caught off-guard by a part 2: intcode boogaloo

Lars Nilsson15:12:24

Day 8 makes me appreciate immutable data structures and defmulti. I am fairly happy with my code for a change.

Joe18:12:00 - this one did give me some flashbacks to intcode 😨

👍 3
Ben List19:12:41

I really liked the intcode ones last year so I hope there's some future days that build this one up Anyways my solution for day 08


I tried for so long do step 2 in a tree-ish way. Gave up. The brute force was with me though. Copying the program and modifying each one differently worked. I want my gold stars! Glad I learned about cond-> the other day.

(defn parse [input]
  (->> (clojure.string/split-lines input)
       (map #(clojure.string/split % #" "))
       (mapv (fn [[op arg]] [op (Integer/parseInt arg)]))))

(defn run [program]
  (let [length (count program)]
    (reduce (fn [cpu _]
              (let [{:keys [pc pcs]} cpu]
                (if (< pc length)
                  (let [[op arg]         (nth program pc)]
                    (if (pcs pc)
                      (reduced cpu)
                      (cond-> cpu
                        (= op "acc")        (update :acc #(+ % arg))
                        (#{"acc" "nop"} op) (update :pc inc)
                        (= op "jmp")        (update :pc #(+ % arg))
                        :always             (update :pcs conj pc))))
                  (reduced cpu))))
            {:acc    0
             :pc     0
             :length (count program)
             :pcs    #{}}

  (def input (util/fetch-input 8))
  ; step 1
  (->> input

  ; step 2
  (->> input
       (#(repeat (count %) %))
       (keep-indexed (fn [i program]
                       (let [[op val] (program i)]
                         (assoc program i [(cond
                                             (= "nop" op) "jmp"
                                             (= "jmp" op) "nop"
                                             :else        op)
       (map run)
       (filter #(= (:pc %) (:length %)))

🎉 3

I’m officially a day behind now and late to the party


feels like part 2 could be way nice even brute forcing it… but I dunno how


A reason part 2 took me so long is that I was looking at it as a tree problem. So I was able to build a structure something like so (simplyfied):

[[nop jmp]
 [jmp nop]
But then I failed building the programs from this. Which for the example above would be:
[[nop acc acc jmp acc]
 [jmp acc acc jmp acc]
 [nop acc acc nop acc]
 [jmp acc acc nop acc]]
Does anyone have any pointers on how to go about it?

Vincent Cantin01:12:50

@pez Here is what I would do to optimize this problem: • Collect all the switch-indexes , indexes of the nop and jmp where the original program go before an infinite loop is detected, because the solution is to flip one of those indexes. • Build the reverse-jmp-graph, where edges are from jmp-targets to jmp-sources. • In this graph, collect all land-indexes which are reachable from the end of the program. • Find which change on instructions at switch-indexes let the program go into instructions at land-indexes .


I've learnt a few neat functions from looking at you guys solution so far this year 🙂

💯 3

In case you haven’t seen it, I highly recommend being well versed in all these base functions, really helps to know what’s possible.

👍 3

I am doing 2015 in parallel 😕

Vincent Cantin06:12:24

split keyboard? 😉

Vincent Cantin01:12:50

@pez Here is what I would do to optimize this problem: • Collect all the switch-indexes , indexes of the nop and jmp where the original program go before an infinite loop is detected, because the solution is to flip one of those indexes. • Build the reverse-jmp-graph, where edges are from jmp-targets to jmp-sources. • In this graph, collect all land-indexes which are reachable from the end of the program. • Find which change on instructions at switch-indexes let the program go into instructions at land-indexes .