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
2017-12-06T05:38:44.000105Z

TIL.. max-key Also, TIL: max-key gives the last instance for which (k x) is greatest

borkdude 2017-12-06T08:32:17.000259Z

Solved it, but no real insights for today

borkdude 2017-12-06T08:33:40.000006Z

And it’s fast enough, so no optimizing today 😉

val_waeselynck 2017-12-06T08:34:51.000194Z

@borkdude same thing, a bit disappointed 🙂

borkdude 2017-12-06T08:38:55.000419Z

I almost never use loop in Clojure, but for Advent of Code I used it a couple of times now

orestis 2017-12-06T08:43:22.000218Z

I got to use transient for the first time today. I like it how you can do that inside a tight loop so you don’t have to do the whole functional way for something that you know is never leaving that function.

orestis 2017-12-06T08:46:27.000076Z

I just love this:

(nth (iterate next-cycle [0 2 7 0]) 5)
;; => [2 4 1 2]

2017-12-06T08:49:50.000383Z

ah, that's nice

val_waeselynck 2017-12-06T08:51:52.000112Z

I did use quot and rem for better performance, wondering if that's wasted

2017-12-06T08:52:20.000016Z

Also, I learned you can use :as when destructuring vectors

orestis 2017-12-06T08:52:23.000368Z

It’s only ~13k steps for me…

borkdude 2017-12-06T08:52:31.000201Z

yeah, that’s nice, but could you apply iterate to find the solution, as you had to compare the previous ones

orestis 2017-12-06T08:53:38.000232Z

Nope. But it’s ok, because detect-loop is only 7 lines of code and uses next-cycle.

borkdude 2017-12-06T08:54:07.000223Z

it’s what I thought too, iterate is nice, but I can’t use it. I also have a next-state fn

orestis 2017-12-06T08:54:46.000025Z

I solved part 2 directly in the REPL; I love this.

orestis 2017-12-06T08:56:00.000180Z

Of course, this only is possible because we have a small problem today — couldn’t do it with yesterday’s 20 million operations.

2017-12-06T08:56:20.000309Z

trying to think of a clever way to use take-while & iterate..

borkdude 2017-12-06T08:57:47.000318Z

If you would only have to compare the last two solutions (as I initially thought) you could maybe do it like lazy fibonacci: https://en.wikibooks.org/wiki/Clojure_Programming/Examples/Lazy_Fibonacci

borkdude 2017-12-06T08:58:10.000367Z

but now you have to be able to look back at all solutions and it breaks down

orestis 2017-12-06T08:59:53.000283Z

I don’t think you can do it without a reduction over sequence…

2017-12-06T09:00:17.000362Z

(count
 (let [seen (volatile! #{})]
   (take-while #(and (not (@seen %))
                     (vswap! seen conj %))
               (iterate step [0 2 7 0]))))

2017-12-06T09:00:18.000550Z

lol

2017-12-06T09:01:16.000167Z

:shipit:

2017-12-06T09:04:17.000155Z

wait, need to clip the head until the first repeat too

orestis 2017-12-06T09:08:52.000016Z

Why volatile and not a transient?

orestis 2017-12-06T09:10:10.000167Z

Hah! Now I got the and bit :)

2017-12-06T09:11:57.000134Z

transient also works.. not sure when to use which to be honest!

orestis 2017-12-06T09:14:37.000272Z

A nice discussion there ^

2017-12-06T09:17:35.000052Z

yeah i did find that, but seems a bit unclear in the end. My takeaway was: if the value has IEditableCollection, go for transient unless you want to co-ordinate between multiple threads, in which case use volatile! for speed and atom if you need what atom brings.. sound accurate?

2017-12-06T09:20:53.000204Z

i'd be careful with volatile! in a multithreaded context, as vswap! is not atomic, concurrent vswaps will give unreliable results

2017-12-06T09:21:44.000111Z

Ah, I misread: > Volatiles are faster than atoms but give up atomicity guarantees so should only be used with thread isolation.

2017-12-06T09:24:45.000103Z

i think there was a check on volatiles to detect concurrent swaps, but they removed it to use it in transducers

2017-12-06T09:26:37.000128Z

ah, seems like: atoms - mutable, can be updated from multiple threads, guaranteed atomic volatiles - mutable, can be updated from multiple threads, but not atomic so be careful transients - mutable, require thread isolation

2017-12-06T09:27:22.000417Z

yeah that sounds right, maybe the check was on transients, i don't remember

val_waeselynck 2017-12-06T09:30:04.000415Z

I would rephrase this as: - atoms: slowest, no caution required - volatiles: faster, but avoid concurrent updates - transients (or any non-synchronized data structure such as java.util.HashMap): fastest, but don't update after sharing to other threads

2017-12-06T09:31:35.000161Z

if each thread is only, for example, updating a specific index on an array I think it's safe.. but you'd best know what you're doing

2017-12-06T09:54:32.000476Z

https://gist.github.com/minikomi/0bf7bedb64767a832be172c5b0767d1e Trying some things out, only the atom gives [1000 1000 1000 1000 1000] every time.

2017-12-06T09:57:48.000217Z

wow, atoms are cool.

2017-12-06T10:00:11.000462Z

thats a good way to test it, but the transient example is incorrect

2017-12-06T10:01:11.000377Z

you need to pass the result of transient operation around (for assoc!, dissoc! etc), like how you would use normal assoc

👍 1
theeternalpulse 2017-12-06T10:14:12.000275Z

I just sat down with pen and paper for day 3 and I think I have a breakthrough lol. Love the aha moments of an interesting problem.

2017-12-06T10:21:43.000123Z

So it's not really equivalent to atom/volatile, in that you have to reassign it or use it in, say, a reduction where it's explicitly the updated version which is being passed around?

(let [t (transient {})]
  (assoc! t :test 3)
  (assoc! t :banana 4)
  (persistent! t))
seems to work fine, but in some cases it might fail?

2017-12-06T10:33:44.000011Z

https://clojuredocs.org/clojure.core/assoc! ooh, the docs explain well. thanks for the tip!

2017-12-06T10:38:55.000504Z

yes, try this with like 1000 assoc! with different keys and see what happens

2017-12-06T10:40:21.000038Z

@minikomi this shows how it fails:

(let [t (transient {})]
  (dotimes [i 1000]
    (assoc! t i :test))
  (persistent! t))

2017-12-06T10:42:08.000409Z

nice! that's what i was looking for, yeah

2017-12-06T10:44:18.000122Z

its because for small maps a (transient) array-map is used

2017-12-06T10:44:59.000278Z

and for larger maps (> 10) it switches to a hash-map, so then assoc! returns a different object

2017-12-06T10:45:08.000089Z

right

2017-12-06T10:45:44.000472Z

thanks, learned something tonight 🙂 and now i go home

borkdude 2017-12-06T12:24:36.000056Z

I thought I had a nicer solution with mapv for next-state but that doesn’t pan out

borkdude 2017-12-06T12:29:41.000218Z

e.g. (next-state [0 1 2 3 6 4 5 0]) => [1 2 3 3 0 5 6 1] is hard to do with mapv, where you don’t have some state in the function

2017-12-06T12:54:07.000100Z

yeah

2017-12-06T12:55:02.000121Z

mapv doesn’t cut the cases where your number is smaller than count-1

2017-12-06T12:55:29.000062Z

looks like the redistribution has to be sequential

2017-12-06T12:55:44.000002Z

I just found out 😄

borkdude 2017-12-06T12:57:55.000023Z

Part 1 finishes in 63 ms on my machine

borkdude 2017-12-06T12:58:08.000156Z

(without using transients or mutable arrays)

2017-12-06T13:23:29.000240Z

completely instant on my machine in planck, so cljs

mfikes 2017-12-06T13:24:54.000266Z

I'm at 260 ms (Planck, optimized code for elegance, readability over speed)

borkdude 2017-12-06T13:25:41.000074Z

@mfikes I would if I could find a more elegant solution, but mapv didn’t work… curious about other solutions.

borkdude 2017-12-06T13:25:56.000010Z

I find this a creative way of finding part 2: https://github.com/EsthervdS/AdventOfCode/blob/master/day6/Reallocation.java#L52

mfikes 2017-12-06T13:26:09.000055Z

I'm cleaning up my code before committing, and then I'll dig into the backlog here 🙂

2017-12-06T13:40:47.000296Z

how do I profile in planck?

mfikes 2017-12-06T13:42:05.000257Z

@nooga I haven't hooked in any profiling tools, but ClojureScript has a simple-benchmark core fn. See, for example http://blog.fikesfarm.com/posts/2017-11-18-clojurescript-performance-measurement.html

2017-12-06T13:43:18.000281Z

hm

2017-12-06T13:43:22.000162Z

not too shabby

borkdude 2017-12-06T13:51:12.000185Z

The a in advent stands for array

borkdude 2017-12-06T13:51:36.000079Z

@nooga Cool! Without transients/mutable?

2017-12-06T13:57:01.000065Z

@borkdude no tricks, plain and simple

borkdude 2017-12-06T13:58:03.000161Z

@nooga Can you try my input for comparison? https://github.com/borkdude/aoc2017/blob/master/resources/day6.txt

2017-12-06T13:58:06.000310Z

sure

2017-12-06T13:59:56.000284Z

definately in the ballpark

borkdude 2017-12-06T14:01:18.000537Z

nice

mfikes 2017-12-06T14:02:04.000467Z

My solutions committed https://github.com/mfikes/advent-of-code/blob/master/src/advent_2017/day_06.cljc

2017-12-06T14:02:06.000725Z

I’m running this in planck because I’m too lazy to lein new for this

2017-12-06T14:02:45.000051Z

@mfikes could you share your input? for performance comparison?

mfikes 2017-12-06T14:03:42.000299Z

My perf is around half a second

mfikes 2017-12-06T14:04:41.000543Z

@borkdude I think I ended up with the same approach as Esther's

2017-12-06T14:05:14.000235Z

now that’s peculiar: solve1: 157.606489msecs, solve2: 157.029896msecs

2017-12-06T14:05:35.000183Z

So you’ve got harder input than me and @borkdude

mfikes 2017-12-06T14:09:28.000311Z

@minikomi Yes, the behavior of max-key is crucial if you happen to use it in this problem. Documented in Clojure and pending for ClojureScript https://dev.clojure.org/jira/browse/CLJS-2403

mfikes 2017-12-06T14:11:09.000157Z

I haven't use loop / recur yet in this year's solutions. (Probably to the detriment of ouright perf.)

2017-12-06T14:11:23.000297Z

Right, which is why I ended up rolling my own indexed-max

mikelis 2017-12-06T14:14:05.000033Z

not sure if you’ve incorporated this into your optimized solutions, but

(defn max-ndx [banks] (.indexOf banks (apply max banks)))
seems to be quite a bit faster than
(defn max-ndx [v]
  (- (dec (count v))
     (apply max-key (vec (rseq v)) (range (count v)))))

2017-12-06T14:17:04.000137Z

nice. same concept, but neater than mine

mikelis 2017-12-06T14:22:09.000237Z

first-max-pos can be optimized using reduce-kv

mikelis 2017-12-06T14:22:15.000106Z

(defn first-max-pos'
  [nums]
  (reduce-kv (fn [[_ max-val :as m]
                  k cur-val]
               (if (> cur-val max-val)
                 [k cur-val] m))
             [0 0]
             nums))

mikelis 2017-12-06T14:22:26.000726Z

runs 4-5x faster for me

2017-12-06T14:22:56.000510Z

faster than mine too?

mikelis 2017-12-06T14:23:10.000697Z

sorry didn’t see yours, I’ll check

bhauman 2017-12-06T14:23:36.000512Z

now to look at all the great stuff you guys have wrought

mikelis 2017-12-06T14:23:38.000231Z

@minikomi yes

2017-12-06T14:23:50.000636Z

(.indexOf block max-val) is probably fastest! ahh

2017-12-06T14:24:06.000009Z

good one

mikelis 2017-12-06T14:25:54.000699Z

cljs.user=> (time (dotimes [_ 10000] (first-max-pos [1 2 3 4 6 1])))
"Elapsed time: 146.985052 msecs"

; with reduce-kv
cljs.user=> (time (dotimes [_ 10000] (first-max-pos' [1 2 3 4 6 1])))
"Elapsed time: 33.089442 msecs"

cljs.user=> (time (dotimes [_ 10000] (get-indexed-max [1 2 3 4 6 1])))
"Elapsed time: 68.978079 msecs"

; using .indexOf
cljs.user=> (time (dotimes [_ 10000] (max-i-and-val [1 2 3 4 6 1])))
"Elapsed time: 42.277618 msecs"

mikelis 2017-12-06T14:26:41.000178Z

so reduce-kv is fastest

2017-12-06T14:26:50.000209Z

interesting

mikelis 2017-12-06T14:26:58.000719Z

the indexOf solution has to go through the list twice

2017-12-06T14:27:44.000594Z

(defn get-indexed-max [memory-state]
  (loop [idx 0
         current-max [nil -1]] ;; no values below 0
    (if-let [v (get memory-state idx)]
      (recur (inc idx)
             (if (< (second current-max) v)
               [idx v]
               current-max))
      current-max)))
here was my first attempt

mikelis 2017-12-06T14:30:05.000284Z

maybe if you get rid of the tuple allocations

mfikes 2017-12-06T14:30:24.000250Z

@mikelis.vindavs TIL. I suppose it is meant to be a stable / public part of the API: https://github.com/clojure/clojurescript/blob/3b3db232711266f9f41c94c777084664d6d0b71b/src/test/cljs/cljs/seqs_test.cljs#L131-L147

2017-12-06T14:30:25.000746Z

max-idx, max-val?

bhauman 2017-12-06T14:31:26.000518Z

my straightforward solutions run in the 100 - 200ms range without perf optimizations

bhauman 2017-12-06T14:31:39.000302Z

without

bhauman 2017-12-06T14:33:27.000547Z

@nooga which repo is yours?? or have you posted your answer?

2017-12-06T14:34:03.000292Z

Nah, I type them into planck repl as I go so I have nothing to post on gh

2017-12-06T14:34:04.000022Z

😄

2017-12-06T14:34:26.000264Z

hm, one sec

bhauman 2017-12-06T14:34:35.000116Z

thats cool, you could gist them out!

2017-12-06T14:34:54.000085Z

console.time()
var max_idx, max_val=-1;
for (i = 0; i < 10000; i++) {
arr.forEach(function(i,v) {
  if(v > max_val) {
    max_idx = i; max_val = v;
  }
})
}
console.timeEnd()
VM750:10 default: 22.593017578125ms

2017-12-06T14:35:03.000366Z

make of that what you will 😜

2017-12-06T14:35:25.000698Z

https://repl.it/repls/AwareOutgoingWolverine but really, it’s nothing special imo

bhauman 2017-12-06T14:36:13.000672Z

yeah but its still good to see 🙂 thanks for posting it

bhauman 2017-12-06T14:36:49.000490Z

I think we could make an entire clojure workshop based around this type of stuff

2017-12-06T14:36:54.000430Z

I don’t put much energy into this 😄

2017-12-06T14:37:27.000388Z

Yeah, a friend of mine is learning clojure at the moment and I got him to solve AoC

bhauman 2017-12-06T14:37:28.000053Z

I like it because I get to do something that is simply for enjoyment

bhauman 2017-12-06T14:37:33.000426Z

perfect!

2017-12-06T14:37:37.000498Z

much better than hackerrank problems IMO

mikelis 2017-12-06T14:38:15.000251Z

(defn loop-nth [xs]
  (let [size (count xs)]
    (loop [i 0
           maxi 0
           m 0]
      (if (< i size)
        (let [v (nth xs i)]
          (if (> v m)
            (recur (inc i) i v)
            (recur (inc i) maxi m)))
        [maxi m]))))
is some 10% faster than reduce-kv

🤘 1
mfikes 2017-12-06T14:39:29.000042Z

Nice solutions @bhauman. Thanks for sharing!

mfikes 2017-12-06T14:43:06.000384Z

AFAICT .indexOf and .lastIndexOf are so useful, that they were made to work in ClojureScript

bhauman 2017-12-06T14:44:36.000608Z

oh cool! i didn't know this

mfikes 2017-12-06T14:45:46.000235Z

Even crazy construct like (.indexOf (sequence (map inc) '(0 1 2 3 4)) 5) work in Clojure and ClojureScript

bhauman 2017-12-06T14:46:30.000740Z

very very cool and good to know

mfikes 2017-12-06T14:46:52.000754Z

I wonder if these test imply that they are stable / public https://github.com/clojure/clojurescript/blob/3b3db232711266f9f41c94c777084664d6d0b71b/src/test/cljs/cljs/seqs_test.cljs#L131-L168

mfikes 2017-12-06T14:48:46.000047Z

If that's true, index-of and last-index-of could be things. :thinking_face:

bhauman 2017-12-06T14:52:27.000417Z

well I can't wait for the next episode of AOC

2017-12-06T14:52:28.000251Z

could be

2017-12-06T14:53:22.000752Z

I was suprized when .indexOf worked both in clj and cljs

mfikes 2017-12-06T14:54:47.000455Z

Thanks for the tip @mikelis.vindavs, I've incorporated it https://github.com/mfikes/advent-of-code/blob/master/src/advent_2017/day_06.cljc

mfikes 2017-12-06T14:57:04.000178Z

I was surprised that (merge-with + [0 1] {0 7 1 2}) produced a vector. I used it without thinking but then checked afterwards that this is true.

mfikes 2017-12-06T14:57:46.000515Z

Perhaps I'm violating the expectations of merge-with, TBH, looking at its docstring.

borkdude 2017-12-06T15:04:53.000108Z

@mikelis.vindavs I looked at reduce-kv but this won’t work: (first-max-pos' [-1 -2 -5]), so I stayed with the current

mikelis 2017-12-06T15:05:10.000536Z

the inputs are always positive

borkdude 2017-12-06T15:05:46.000068Z

@mikelis.vindavs oh. well, then it works, but only for this puzzle 😉

mikelis 2017-12-06T15:05:49.000147Z

but it could be made to work with negative ones by providing a different initial value

borkdude 2017-12-06T15:12:50.000122Z

@bhauman I didn’t think of perf optimizations when I wrote mine, but ended up at 63 ms (on JVM that is). Now let’s see if I understand yours.

borkdude 2017-12-06T15:14:31.000298Z

ah, so it’s incrementing one by one

mfikes 2017-12-06T15:18:06.000068Z

Hah, I could see another variation on this, where all solutions are submitted to a server which benchmarks and ranks them.

mfikes 2017-12-06T15:20:23.000332Z

It seems you can optimize roughly for 3 things: 1. Get it done ASAP so you can get points 2. Write it elegantly so it is mantainable 3. Optimize the crap out of it

borkdude 2017-12-06T15:21:32.000630Z

Added some printlns to @bhauman’s solution because I had trouble seeing what was going on, but now I get it:

start: [0 2 7 0]

acc i [0 2 0 0] 3
acc i [0 2 0 1] 0
acc i [1 2 0 1] 1
acc i [1 3 0 1] 2
acc i [1 3 1 1] 3
acc i [1 3 1 2] 0
acc i [2 3 1 2] 1
Clever, so it will loop 7 times updating each index by one, starting from the index after the max value

borkdude 2017-12-06T15:22:20.000683Z

In my solution I maintained the invariant that the sum of elements should always remain constant, because I had a bug in it that increased the sum

bhauman 2017-12-06T15:23:38.000638Z

that merge-with behavior is unexpected

bhauman 2017-12-06T15:25:33.000798Z

@borkdude my redistribute version just duplicates the process specified

bhauman 2017-12-06T15:26:19.000363Z

not much different than a loop, oh and I got it down to 93ms with transients but I don't see that as worth it

borkdude 2017-12-06T15:28:48.000130Z

@bhauman Just for fun, I moved the (count …) to a let bound name, but it becomes slower, not faster … 🙂

bhauman 2017-12-06T15:29:39.000884Z

yeah didn't expect much from that as count is a constant time look up

borkdude 2017-12-06T15:29:41.000573Z

Probably it’s a neglectable thing and just co-incidence

bhauman 2017-12-06T15:30:20.000512Z

yeah thats likely

bhauman 2017-12-06T15:31:43.000055Z

although, moving the count to a let probably makes the alg more clear

borkdude 2017-12-06T15:31:49.000087Z

cool, with find-max-pos the bhauman solution is even faster than mine: 56 ms

mfikes 2017-12-06T15:32:02.000425Z

@bhauman Yeah, I've concluded it only accidentally works:

(merge-with (fn [_ x] x) [0 3] {0 7 1 2} {0 3 2 32})
and
(merge [0 3] {0 7 1 2} {0 3 2 32})
don't produce the same result.

mikelis 2017-12-06T15:33:23.000352Z

can someone explain this to me? I don’t really get it

bhauman 2017-12-06T15:34:04.000486Z

@mfikes is this because merge-with is exploiting a more general interface like get and assoc on the first collection?

bhauman 2017-12-06T15:34:59.000365Z

yeah thats probably why

mfikes 2017-12-06T15:35:18.000525Z

Yeah, but it is an abuse to rely on it.

mfikes 2017-12-06T15:35:26.000503Z

I'm going to see if I can fix my solution.

mikelis 2017-12-06T15:35:50.000849Z

ah it’s just treating it as an associative collection, just like (assoc [:a :b :c] 1 :x) works

mikelis 2017-12-06T15:37:10.000003Z

interesting that (merge-with + [10 11 12 13] [100 101 102 103]) doesn’t work but (merge-with + [10 11 12 13] {0 100 1 101 2 102 3 103}) does

mikelis 2017-12-06T15:38:25.000406Z

when dynamic typing and implementation details meet

mfikes 2017-12-06T15:50:24.000244Z

Well, since I want to treat the memory banks as associative and have it work with merge-with, I'm going to use maps instead of vectors.

bhauman 2017-12-06T16:01:36.000793Z

that'll do it

borkdude 2017-12-06T21:00:53.000725Z

Found a slight variation on bhauman’s next-state:

(defn redistribute [block]
  (let [[position max-val] (first-max-pos block)
        length (count block)]
    (reduce (fn [block [p v]]
              (update block p
                      + v))
            (assoc block position 0)
            (->> (range (inc position)
                        (+ (inc position) max-val))
                 (map #(mod % length))
                 frequencies))))

borkdude 2017-12-06T21:06:12.000618Z

The original bhauman version is twice as fast though! Provided that you use first-max-pos.. 40 ms now on my machine

borkdude 2017-12-06T21:17:07.000599Z

Ah, I see mfikes’ solution now, also used frequencies.

mikelis 2017-12-06T21:25:14.000217Z

It’s an interesting solution with frequencies, but if focusing on perf, isn’t generating a whole sequence just to squash it down kinda slow vs just adding (quot n-to-distribute num-banks) and then figuring out where to put the rest ?

mikelis 2017-12-06T21:26:48.000532Z

it’s rather elegant though

grzm 2017-12-06T21:39:19.000469Z

wow. @bhauman’s is really nice and terse, and a good example of using reduce to avoid a loop.

borkdude 2017-12-06T22:12:23.000586Z

@grzm I agree

mfikes 2017-12-06T22:43:58.000045Z

Hah, I recognize this approach 🙂

mfikes 2017-12-06T22:47:19.000177Z

This was where the desire to use (merge-with + on a vector came from. (To avoid an explicit reduce.)

mfikes 2017-12-06T22:52:33.000203Z

Yeah, his solution is close to what I would consider "art" 🙂

bhauman 2017-12-06T22:54:16.000161Z

geez guys

bhauman 2017-12-06T22:54:27.000121Z

thanks