adventofcode

Happy Advent 2020! Please put answers in the pinned threads or create one if it does not exist yet. | https://github.com/adventofcode-clojurians/adventofcode-clojurians | Join the private leaderboard with code 217019-4a55b8eb
kenj 2020-12-01T05:01:15.013100Z

the AOC server is struggling tonight

rjray 2020-12-01T05:35:01.013500Z

Yeah, I was getting alternating 502/503 errors within the first minute or so.

rjray 2020-12-01T05:58:14.014200Z

Per reddit thread (https://www.reddit.com/r/adventofcode/comments/k4ejjz/2020_day_1_unlock_crash_postmortem/), the downtime was due to overloading their configured AWS instances. They'll be canceling leaderboard points for the first day.

😮 1
fingertoe 2020-12-01T07:44:21.016200Z

Got day 1 done.. My question 2 answer came up too easily.. I might have to mix up my data to make it work on more universal data..

erwinrooijakkers 2020-12-01T08:59:51.018200Z

Day 1 with math.combinatorics https://github.com/transducer/adventofcode/blob/master/src/adventofcode/2020/day1.clj

👍 5
rmprescott 2020-12-02T15:33:10.076500Z

Curious: why use set membership rather than `(= 2020 ...)

erwinrooijakkers 2020-12-03T10:04:23.145400Z

this case it would be (partial = 2020) I think

erwinrooijakkers 2020-12-03T10:04:48.145600Z

It’s this recommendation from the style guide https://github.com/bbatsov/clojure-style-guide#set-as-predicate

erwinrooijakkers 2020-12-03T10:04:56.145900Z

;; good
(remove #{1} [0 1 2 3 4 5])

;; bad
(remove #(= % 1) [0 1 2 3 4 5])

;; good
(count (filter #{\a \e \i \o \u} "mary had a little lamb"))

;; bad
(count (filter #(or (= % \a)
                    (= % \e)
                    (= % \i)
                    (= % \o)
                    (= % \u))
               "mary had a little lamb"))

nbardiuk 2020-12-01T09:28:02.019900Z

oh, cool TIL about math.combinatorics I had to roll my own inefficient combinations function https://github.com/nbardiuk/adventofcode/blob/0955796f728c9d73376ba480003994db902c6b21/2020/src/day01.clj

zackteo 2020-12-01T09:34:19.021300Z

but I guess @nbardiuk that might be a good exercise haha. I did

(require '[clojure.math.combinatorics :as combo])

(->> (combo/combinations data 2)
     (filter #(= (apply + %) 2020))
     (apply reduce *))

zackteo 2020-12-01T09:35:00.021600Z

Maybe I should create a repo for advent of code hmmmm

misha 2020-12-01T09:35:03.021700Z

no deps, little waste, part 2 "Elapsed time: 2.362157 msecs"

👍 1
💯 1
misha 2020-12-01T09:35:25.021800Z

(time
  (let [xs   (->> input (str/split-lines) (map read-string) (sort <))
        vecs (for [x xs
                   y xs
                   :while (< (+ x y) 2020)
                   z xs
                   :let   [s (+ y x z)]
                   :while (<= s 2020)
                   :when  (= s 2020)]
               [x y z])]
    (some->> vecs first (apply *))))

"Elapsed time: 2.362157 msecs"

misha 2020-12-01T09:37:20.022500Z

@zackteo your example gives me "Elapsed time: 1095.922251 msecs" for part2

zackteo 2020-12-01T09:45:57.023900Z

@misha right >< yeah was thinking it isn't optimal but at least is a start hahaha 🙂 I usually don't get these challenges done

👍 1
zackteo 2020-12-01T09:51:32.024Z

so the first while is a pre-filtering. And xs is the sequential list of numbers?

misha 2020-12-01T09:56:26.024300Z

(for [x [1 2 3]
      y [1 2 3]]
  {:x x :y y})
=&gt;
({:x 1, :y 1}
 {:x 1, :y 2}
 {:x 1, :y 3}
 {:x 2, :y 1}
 {:x 2, :y 2}
 ,,,

(for [x [1 2 3]
      :when (&lt; x 2)
      y [1 2 3]]
  {:x x :y y})
=&gt;
({:x 1, :y 1}
 {:x 1, :y 2}
 {:x 1, :y 3})

misha 2020-12-01T09:58:45.024500Z

xs is parsed puzzle input, seq of ints, yes

misha 2020-12-01T10:08:24.024700Z

(defn find-first [p xs]
  (-&gt;&gt; xs (filter p) first))
there is more efficient some instead
(some pred xs)

👍 1
nbardiuk 2020-12-01T10:22:20.025400Z

that is interesting

(defn find-first [p xs]
  (some #(when (p %) %) xs))
some returns first truthy value, so I had to wrap it into when, and it is slightly slower now

spfeiffer 2020-12-01T10:34:18.025600Z

Yes, the combinatorics namespace has been very valuable for many AoC Puzzles in the past.

misha 2020-12-01T10:36:38.025800Z

ah, yes, should have mentioned: you'd have to change predicate too.

oxalorg (Mitesh) 2020-12-01T10:38:23.026500Z

I used a different apporach to solve this problem here: https://nextjournal.com/oxalorg/advent-of-clojure-01 The runtime complexity here is O(n) for part1 and O(n^2) for part 2

Björn Ebbinghaus 2020-12-01T10:46:35.028500Z

I made use of some in combination with set lookup. I find it quite simple. Task 1: ~ 0.25 msecs Task 2: ~30 msecs When executed with babashka https://github.com/MrEbbinghaus/advent-of-code/blob/master/2020/day01.clj

misha 2020-12-01T11:09:24.029200Z

in the worst case filter walks extra 31 element after first is ready to go:

(-&gt;&gt; (range 100) 
  (filter #(do (println %) (even? %)))
  (first))

0
1
,,,
30
31
=&gt; 0

nbardiuk 2020-12-01T11:13:28.029400Z

oh, that is valid point, it is dangerous in case of side effects inside filter

nbardiuk 2020-12-01T11:16:31.029600Z

I am still curious why in my case some was not visibly faster, maybe it is related with chunking?

(chunked-seq? (range 100)) ; true
(chunked-seq? (combinations 2 [1 2 3])) ; false

Björn Ebbinghaus 2020-12-01T11:23:57.030300Z

When I use a sorted-set as input I go down to 0.08ms and 0.09ms. But I think my input list favors this heavily as just 7 numbers are smaller than 2020 / 2 ..

👍 1
erwinrooijakkers 2020-12-01T11:25:26.030600Z

Why the while?

juniusfree 2020-12-01T11:28:40.031800Z

👋Newbie coder. Trying my first AoC. Any helpful feedback is welcome! https://twitter.com/juniusfree/status/1333727767658053632 https://github.com/juniusfree/advent-code-2020/tree/master/day01

erwinrooijakkers 2020-12-02T12:40:26.074400Z

@mroerni my definition of functional programming is “pure functions (that always return same output with same input) and no use of mutable variables”. By that definition the solution was already functional. Perhaps you mean something different, like using higher order functions with well known names (see below)? @juniusfree indeed map and filter are implemented using recursion. I think it’s instructive to look at and they have similar patterns to your solution, the gist of it (clojure.core one also uses lazy-seq to delay evaluation of recursive call making them lazy, and chunked sequences to increase performance):

(defn map* [f coll]
  (when (seq coll)
    (cons (f (first coll)) (map* f (rest coll)))))

(map* inc [1 2 3])
;; =&gt; (2 3 4)
reduce can also be implemented recursively and you can implemented map and filter also in terms of reduce. Some examples are given in https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-15.html#%_sec_2.2 (`reduce` is called accumulate there and is used to implement other higher order functions). It states in SICP: > In effect, map helps establish an abstraction barrier that isolates the implementation of procedures that transform lists from the details of how the elements of the list are extracted and combined The recursive definitions you defined yourself probably already exist, or can be written in, terms of higher order functions with well known names, thus making us think differently and perhaps arguably more functionally.

juniusfree 2020-12-02T20:13:14.095500Z

@erwinrooijakkers Correct. There are a lot of built-in functions in Clojure that I still have to explore!

misha 2020-12-01T11:33:26.032Z

notice, input ints are sorted ascending. while makes sure for does not iterate over combinations which are already known to be useless. even though xs are sorted, combinations of [x y z] - are not, and as soon as y or z becomes large enough to disqualify [x y z] - while makes for abandon iteration over the rest of y or z , and takes next x (or y respectively) instead

misha 2020-12-01T11:36:09.032200Z

if all combinations would be sorted by sum , you'd be able to just (-&gt;&gt; combos (drop-while &lt;2020) first). but combinations are not sorted, so need to terminate "branch" early another way: while .

Björn Ebbinghaus 2020-12-01T11:36:42.032400Z

Have look at recur for tail-recursion. https://clojuredocs.org/clojure.core/recur https://clojure.org/about/functional_programming#_recursive_looping Check this out

(defn check-second
  [fst rst]
  (cond
    (nil? (first rst))            nil
    (= 2020 (+ fst (first rst)))  (* fst (first rst))
    :else                         (recur fst (rest rst))))
And: Are abbreviated parameters worth it? I have no idea what you mean with loe

🙏 1
misha 2020-12-01T11:39:01.032600Z

I think so, yes:

(defn- -unchunk [sek]
  (when sek
    (lazy-seq
      (cons (first sek)
        (-unchunk (next sek))))))

(defn unchunked-seq
  "converts coll into seq which will be realized 1 element at a time,
   and not in chunks, like e.g. 32 for (range 40)."
  [coll]
  (let [xs (seq coll)]
    (if (chunked-seq? xs)
      (-unchunk xs)
      xs)))

(-&gt;&gt; (range 100)
  (unchunked-seq)
  (filter #(do (println %) (even? %)))
  (first))

0
=&gt; 0

👍 1
juniusfree 2020-12-01T11:59:13.032800Z

@mroerni Thanks for checking. loe just means list of expenses. I'll definitely look into recur

Björn Ebbinghaus 2020-12-01T12:01:48.033100Z

And it looks like you are still in the “thinking in loops”-phase (every functional programming beginner starts there when they come from python, java, … whatever In functional programming you would not “iterate” over things.. You apply functions to collections. Like so:

(defn check-second [fst rst]
  (when-let [snd (some (fn [possible-snd] (when (= 2020 (+ fst possible-snd)) possible-snd)) rst)]
    (* fst snd)))
(`(some pred coll)` returns the first “truthy” value when pred is applied to every element in item.)

Björn Ebbinghaus 2020-12-01T12:09:06.033300Z

Or maybe easier to understand:

(when-let [snd (first (filter #(= 2020 (+ fst %)) rst))]
  (* fst snd))

juniusfree 2020-12-01T12:17:48.033500Z

@mroerni You're right. I haven't develop the intuition yet on the application of functions on collections. Any tips or resources for that? And thanks for providing the revised code. I'll definitely study this.

erwinrooijakkers 2020-12-01T12:22:11.034Z

(cond (seq x) (blabla)
      :else 0)

;; is the same as:

(if (seq x)
  (blabla)
  0)

erwinrooijakkers 2020-12-01T12:22:29.034200Z

and you can use some sequential destructuring (https://clojure.org/guides/destructuring#_sequential_destructuring) instead of doing first and rest amybe

erwinrooijakkers 2020-12-01T12:22:50.034400Z

(defn report-repair
  [loe]
  (cond
    (empty? loe) 0
    :else (or
            (check-second (first loe) (rest loe))
            (report-repair (rest loe)))))

erwinrooijakkers 2020-12-01T12:22:52.034600Z

=

erwinrooijakkers 2020-12-01T12:23:15.034800Z

(defn report-repair
  [[x &amp; xs :as expenses]]
  (cond
    (empty? expenses) 0
    :else (or
            (check-second x xs)
            (report-repair xs))))

👍 1
erwinrooijakkers 2020-12-01T12:23:35.035100Z

=

erwinrooijakkers 2020-12-01T12:24:23.035300Z

(defn report-repair [[x &amp; xs :as expenses]]
  (if (seq expenses)
    (or (check-second x xs)
        (report-repair xs))
    0))

Björn Ebbinghaus 2020-12-01T12:25:39.035600Z

@juniusfree Maybe have a look at Clojure for the Brave and True https://www.braveclojure.com/clojure-for-the-brave-and-true/ But what I told you makes the difference between “Learning the clojure syntax” and “Learning functional programming”

nbardiuk 2020-12-01T12:54:53.036400Z

right! I also have noticed that sorting input improves almost any solution I've tried

erwinrooijakkers 2020-12-01T14:27:48.037600Z

ah I see smart 🙂

fingertoe 2020-12-01T15:04:12.037900Z

https://github.com/jreighley/aoc2020/blob/master/src/prb1.clj

👍 1
fingertoe 2020-12-01T15:13:27.038200Z

(time (reduce * (find-pair data 2020))) “Elapsed time: 0.646178 msecs” => 786811 (time (reduce * (find-triple data 2020))) “Elapsed time: 1.553634 msecs”

fingertoe 2020-12-01T15:17:32.039500Z

That doesn’t count the slurp or sort though.. using sorted data made my first answer right.

erwinrooijakkers 2020-12-01T18:41:15.040400Z

@mroerni in my eyes it’s quite a functional solution already because of the use of recursion. But yes there are possibly functions that encapsulate these recursive patterns in names (like map, filter, reduce and for-comprehension). I 100% agree with not using abbreviations.

erwinrooijakkers 2020-12-01T19:07:27.040900Z

I updated to similar to you 🙂 https://github.com/transducer/adventofcode/blob/master/src/adventofcode/2020/day1.clj

erwinrooijakkers 2020-12-01T19:07:39.041200Z

25ms then

erwinrooijakkers 2020-12-01T19:11:56.041400Z

Did not look for edge cases (like adding same number) and was not necessary for my input

erwinrooijakkers 2020-12-01T19:12:56.041600Z

Ah your while is one step earlier

erwinrooijakkers 2020-12-01T19:13:23.042Z

Then 3ms

mchampine 2020-12-01T19:13:41.042200Z

(def inp (map #(Integer/parseInt %) (str/split-lines (slurp "day1.data"))))
(first (for [a inp b inp :when (= 2020 (+ a b))] (* a b))) ;; part 1
(first (for [a inp b inp c inp :when (= 2020 (+ a b c))] (* a b c))) ;; part 2

🔥 4
Björn Ebbinghaus 2020-12-01T19:22:49.042600Z

I think this use of recursion is symptom of this “loop” thinking. And not an “apply function to collection” mindset. Recursion doesn’t make it functional. You can think of it that way: Does the decision whether a value in the input has a partner to sum to 2020 or not depend on prior values in the input? Does the order matter? No. So why a recursion? In a recursion you do one thing after another.

juniusfree 2020-12-01T21:36:42.043400Z

@mroerni @erwinrooijakkers Thanks for your feedback. I'll take some time to study these things. I have a question though. Please correct me if I'm wrong. Doesn't map,`filter`, etc. uses recursion under the hood?

Björn Ebbinghaus 2020-12-01T21:44:25.043600Z

Maybe, maybe not. 🙂 But this shouldn’t be your concern, it is a layer of abstraction.

👍 1
juniusfree 2020-12-01T22:27:22.043900Z

@mroerni OK. 😅

2020-12-01T23:59:17.045Z