Fork me on GitHub
#adventofcode
<
2017-12-08
>
fellshard01:12:11

I should've done part B more by hand, yeah. Automating it is a bit clumsy.

minikomi01:12:22

wow, so much to unpack

minikomi01:12:12

my strategy for finding the relevant node in part b was: * (ab)use tree-seq recursively to get a list of every nodes "tallied" weight - the weight it holds * group-by parent * filter for unbalanced groupings * get the one with the lowest tally (will be deepest in the tree)

minikomi02:12:20

part 1 was simply "the node which supports, but isn't supported" -- i.e. (first (filter set-of-supportees supporters))

minikomi02:12:32

hmm tokyo timezone is good for getting the new questions when they come out, but not so great for discussion 😕

bhauman02:12:23

darn I forgot about tree-seq

minikomi02:12:18

it's such a good swiss-army-knife of a function

minikomi03:12:33

I was inspired to write my first macro.. it's a let which you can drop *stop* into and it will throw an exception with the bindings up until that point as data https://gist.github.com/minikomi/b6e5a097970513934faf46f01f194725 when solving these problems I often have a long let within a function which somehow goes wrong..

fellshard04:12:02

knuckle-crack Places, everyone! REPLs at the ready!

fellshard05:12:45

Man. I love reductions

fellshard05:12:07

Also, seems I'll need to dredge up my interpreter macros from last year...

dyankowsky05:12:21

what kinds of stuff did you have?

fellshard05:12:35

Not sure if I ever finished them tbh xD

fellshard05:12:02

It was basically a way to write functions that could be partially applied at fixed points

fellshard05:12:43

So I can bind the parts of the operation I know at read-time, and then hand it the remaining context at execute-time

fellshard05:12:55

Since that's a recurring pattern for these assembly-read type problems

dyankowsky05:12:17

this is both the blessing and curse of Lisps

dyankowsky05:12:25

you can make your own syntax!

dyankowsky05:12:30

you can make your own syntax...

dyankowsky05:12:39

(well, not syntax per se)

fellshard05:12:05

In these cases, I found it easier than having to let-bind around an anonymous function

dyankowsky05:12:21

one problem that I have is that, since I don't use clojure that much, I'm not savvy to all the clever things that I could do with builtins, so I worry that I sometimes rebuild things that already exist

dyankowsky05:12:08

for example, I don't know if Clojure has any partial application support short of reader function literals

fellshard05:12:11

Def. poke around the solutions here, you'll learn a lot of new functions that way, and how they're commonly (and sometimes uncommonly) leveraged. 4clojure used to be great for that, but I think it's down nowadays...

fellshard05:12:21

It's not common, I think, from what I've seen...

fellshard05:12:09

Partly because for small arities, it's just quicker to use #(f bound-param %1 %2), or something like

dyankowsky05:12:40

I'll stick that one in my toolbox

dyankowsky05:12:45

but now I think it's bed time

minikomi05:12:56

goodnight 🙂

minikomi05:12:21

ah reductions is amazing haha

minikomi05:12:52

not so remarkable today

fellshard05:12:44

It may be repetitive, but I really do end up getting into these micro-interpreter type problems >_>

fellshard05:12:55

Maybe partially because Lisps are just so snappy at handling them

mikelis05:12:31

hah, I had heard in passing about reductions but didn’t know it’s called that. I searched for scan, didn’t find it so wrote my own

fellshard05:12:58

When a lot of these problems move from 'find the answer' to 'tell me something about the state along the way', reductions usually shows up

fellshard06:12:13

So it also usually pays to build your solution as a reduction when feasible

mikelis06:12:13

not a fan of the first element behavior though

(reductions str [1 2 3])
=> (1 "12" "123")

fellshard06:12:35

Same as reduce, really

fellshard06:12:01

No, yeah. Reduce always takes the first element as it stands if you don't provide a default

mikelis06:12:46

well, there’s always (drop 1)

fellshard06:12:15

That was delayed, probably a lambda spinning up

mikelis06:12:18

lazy evaluation

grzm06:12:00

@mikelis.vindavs that's maybe a little much only an hour after it dropped

mikelis06:12:13

good point

grzm06:12:15

thanks 🙂

mikelis06:12:46

I kind of assumed this is a spoiler-alert zone

grzm06:12:35

Yeah, I know what you mean. I'd give it at least another couple of hours. Or perhaps just point people to your repo. Then they know what they're in for.

mikelis06:12:45

it’s just general bikeshedding, I should probably get to work instead 😅

grzm06:12:47

FWIW, I chose a third way 🙂

grzm06:12:29

Nice. I like how you documented the lines. I've been getting lazier as the days have been getting on. I'll clean it up in the morning. Time for sleep for me.

grzm06:12:15

oh, and how you reversed threading directions. That's one I'll have to keep in mind.

mikelis06:12:03

you mean with ->> inside of ->?

mikelis06:12:14

it’s neat, I only wish it worked the other way. the workaround there is to create a lambda

(->> {}
     (#(assoc % :x :y)))

minikomi06:12:34

or use as->

mikelis06:12:52

I never know what to use for the name

minikomi06:12:55

love me some as->

minikomi06:12:14

I tend to go with $

mikelis06:12:37

that’s a good choice, stands out.

mikelis06:12:15

I’m loving this channel and the community so far!

minikomi06:12:28

could go with an emoji 😆

fellshard07:12:26

as-> is new to me! Much nicer to pick up than the magic wand / swiss arrows for most things, I bet

borkdude07:12:19

Today was ok. I found the answer for part 2 while trying to find part 1 it turned out 😉. 5 ms performance.

borkdude08:12:17

I have a security breach in there though 🙂

borkdude08:12:01

(fixed the security breach)

val_waeselynck08:12:45

@borkdude I find it intriguing that you used clojure.core/== instead of clojure.core/=, what's the rationale?

borkdude08:12:19

Not needed, just to confuse new people 😉

minikomi08:12:18

just using resolve 👐

borkdude08:12:05

@minikomi I had that too, but figured that when I would run someone elses input, bad things may happen on my machine: https://github.com/borkdude/aoc2017/commit/113de2ba459997544a6376a9944db41472d75bd6

minikomi08:12:45

yeah, i live on the edge

borkdude08:12:08

a dec -511 if fs/wipe-harddisk-if-odd >= -4

minikomi08:12:04

@val_waeselynck mapcat vals - good one

minikomi08:12:55

i need to use mapcat more, always miss it

val_waeselynck09:12:46

I noticed most people don't initialize the registers to 0, but this could lead to subtle bugs if all the registers that were updated end up being negative, and some of them are untouched you may end up with the wrong result (the right result being 0).

minikomi09:12:52

ooh interesting edge case.

borkdude09:12:04

true: lucky by input 🙂

minikomi09:12:51

(update registers reg-loc (fnil identity 0)) interesting line to write 😆

karlis11:12:15

mine for today: https://github.com/skazhy/advent/blob/master/src/advent/2017/day8.clj Curious to see if anyone tried the creative approach of transforming the whole input to s-expressions & then evaling it.

mfikes13:12:27

Yes, I used eval @karlis. It even works in self-hosted ClojureScript 🙂 https://github.com/mfikes/advent-of-code/blob/master/src/advent_2017/day_08.cljc#L22

mfikes13:12:55

I didn't generate an entire namespace from the source input, if that's what you mean. 😀

karlis13:12:00

this is a pretty neat approach nevertheless!

borkdude11:12:55

@karlis very similar to mine

karlis12:12:36

oh my 😄

bhauman12:12:06

well that was easy 🙂

bhauman12:12:52

darn why didn't I use max?

bhauman12:12:21

I guess its still early

borkdude12:12:50

@bhauman for fn-map I had: (defn op [sym] (get {’!= not= ’inc + ’dec -} sym (resolve sym)))

borkdude12:12:05

but I changed it because I considered it a security hole - your version is safe

borkdude12:12:07

anyway, this was an easy one for clojure at least

bhauman12:12:43

Yeah, I saw that you did that, good call

bhauman12:12:20

eval is temping here but ...

bhauman12:12:08

yeah resolve is a better call as well

bhauman12:12:53

I just started playing TIS-100, and I'm digging it, I'm super surpised by this.

mfikes13:12:04

@minikomi You can let == resolve to == FWIW

nooga13:12:56

at first, i wanted to use syntax quote and eval

nooga13:12:51

but then decided to go with an interpreter

orestis13:12:56

Here’s mine - I had failed to cope with eval in the past due to the lack of access to the lexical context, so I just went with a plain reduce. https://github.com/orestis/adventofcode/blob/master/clojure/aoc/src/aoc/2017_day8.clj

mfikes13:12:58

Nice @nooga Fairly compact result.

nooga13:12:23

I could definately golf it some more, avoid repeating solve1 code in solve2 and replace the loops with reduce 😄

nooga13:12:29

thanks @mfikes

orestis13:12:45

It seems a lot of people are using read-string for these, then destructuring the resulting vector… You wouldn’t usually do that, would you? Given the docs’ warning that read-string can eval code?

mfikes13:12:20

@orestis If I'm reading your code correctly, you can write

(update context target #(op (or % 0) am))
as
(update context target (fnil op 0) am)

nooga13:12:53

fnil is little known but really useful

orestis13:12:18

Yep, I saw fnil in @nooga’s code and thought the same — I’ve yet to finish my 3rd read-through of clojure.core 😉

mfikes13:12:32

@orestis Yeah, in real code I would parse things and not use read or eval. But this is AoC, one of the few places you can 🙂

orestis13:12:15

@mfikes Ah, update can take extra args, no need to close over them. Thanks!

mfikes13:12:00

@orestis Hey, even using (or x y) to deal with falsey x is a non-intuitive idiom that you learn to use after starting the journey 🙂

orestis13:12:48

It’s a bummer that get offers a default value but update doesn’t. Breaks the symmetry.

mfikes13:12:59

I failed to point that out in an early interview for code that was like (if x x y)

orestis13:12:23

undefined || 0 is idiomatic JS though.

orestis13:12:50

(I think! Who knows if there really is a concept of idiomatic JS these days)

mfikes14:12:20

To be honest, I think the most profitable way I learned things is by reading other's code

orestis14:12:27

Yep, that’s the value of AoC!

nooga14:12:17

my dirty secret is that despite writing almost exclusively clj and cljs for the past 3 years, I still look at ClojureDocs every 5 minutes

mfikes14:12:15

I think Rich Hickey also looks at those docs

mfikes14:12:48

I'm proud to have what I suspect is the slowest solution for day 8, coming in at 5 seconds

mfikes14:12:25

Going for 3 orders of magnitude slower than the fastest

nooga14:12:39

I think it’s because I don’t pay close attention to simple things like argument lists when focusing at the problem I’m currently solving

nooga14:12:52

yeah, I got that in emacs as well

nooga14:12:31

but often you will be thinking “huh, does re in clojure.string/split have to be re or can it be a string”

nooga14:12:36

or something

nooga14:12:47

btw. my solution clocks at 5-6ms

mfikes14:12:49

I always look that up, every time

orestis14:12:50

I would love if Dash could look up clojuredocs. I wonder if there’s a plugin for that.

orestis14:12:18

Re speeds, part 1 5ms, part 2 15ms…

nooga14:12:15

hm, as we figured out, it’s impossible to compare the performance unless we run each other’s code

nooga14:12:03

but hey, if it’s 5ms it’s very decent

orestis14:12:32

Today should be roughly comparable for everyone, assuming everyone has 1000 ops to run, there’s no loops or any other funky stuff going on there.

orestis14:12:03

I have to clarify; 5ms is what time prints when running within CIDER. Not sure if the JVM is hot/cold/lukewarm/whatever.

borkdude14:12:59

Both parts run in 5 ms for me (with criterium), but that’s because the first run already contained the solution for part 2

nooga14:12:26

I mean, solve1 and solve2 are basically the same

nooga14:12:33

in my code

orestis14:12:26

@mfikes I like the use of spec to parse the values. I should start using that instead of regexes.

mfikes14:12:58

Yeah, I wouldn't argue it is the right way. But the only way to find out is to try.

borkdude14:12:22

for now read-string + destructuring is my favorite way, although spec sounds more interesting 🙂

bhauman14:12:33

you can use edn/read-string for a safer version of read-string

bhauman14:12:09

but really no need here

borkdude15:12:13

edn/read-string is my default read-string 🙂

borkdude15:12:04

I haven’t encountered a nil pointer with max… I think the behavior of (max) is undefined, like 1 / 0

cjmurphy15:12:59

(max nil 0) gives a NPE for me

borkdude15:12:57

@cjmurphy what about (maximum nil 10 11 nil)?

mfikes15:12:03

fnil is only good out to 3 args as well

bhauman16:12:18

its better to use keep to filter out nils

bhauman16:12:48

or filter some?

mfikes16:12:01

If you do that, then perhaps define a variant of max that can cope with no args

borkdude16:12:09

but then you would maybe get (max) which is undefined

borkdude16:12:26

it’s probably an x y problem

bhauman16:12:05

basically a nil punning version of max is what we are talking about

mfikes16:12:10

@cjmurphy Did you encounter this via (vals {}) returning nil?

cjmurphy16:12:33

Maybe I'll make a max that works with a sequence and deals with nil that way...

mfikes16:12:42

Curious what context it arises in

bhauman16:12:38

`(defn maximum [& args] (when-let [args (not-empty (filter some? args))] (apply max args))) `

cjmurphy16:12:51

@mfikes (apply maximum (vals new-memory)) yes

mfikes16:12:37

In that case you should get an arity exception from max not a nil arg

cjmurphy16:12:57

Yes I got that exception too.

mfikes16:12:58

(apply max (vals {})) -> arity issue

orestis16:12:01

I used (mapcat vals <seq>) and it took care of null values.

borkdude16:12:54

I wonder how you got nils in the first place, I didn’t have this problem

orestis16:12:52

If you use reductions in the second part, and you don’t use a pre-populated map, the first few steps will give you an empty map until the first successful operation.

mfikes16:12:23

Cool mapcat solution. Perhaps (concat [1 2] nil [3 4])’s nil-punning should be in its docstring

borkdude16:12:31

ah ok — I got my answer in the first part already

cjmurphy16:12:56

@borkdude I got the NPE here: new-kept-highest (maximum kept-highest new-highest)

borkdude16:12:19

@cjmurphy you could initialize kept-highest to 0, because this was the initial value of all registers

orestis16:12:47

@mfikes I discovered it by accident.

cjmurphy16:12:46

kept-highest is the 'running maximum' for part two, so not actually a register value.

mfikes16:12:54

I ended up with this unsavory hack: (apply max (or (vals {}) [0]))

borkdude16:12:39

@cjmurphy yeah, that’s why you could seed the running max with 0, because that’s the initial max

borkdude16:12:11

(@cjmurphy assuming your doing something like reduce)

cjmurphy16:12:15

Yes sure, I'll do that and see if any more NPEs

cjmurphy16:12:38

I'm doing iterate.

mfikes16:12:57

@orestis Right. I also discovered (merge-with + [1] {0 2}) accidentally works, but I suspect the concat behavior is intended. Hrm.

cjmurphy16:12:26

I'm thinking to initialize kept-highest to lowest possible value rather than 0.

borkdude16:12:39

so 0 is the initial value of cur-max (or kept-highest)

cjmurphy16:12:03

I intitialized kept-highest/cur-max to Long/MIN_VALUE and now no more nil problems with max. So good point that there should not be problems with nil with max in the first place.

cjmurphy16:12:31

There would be a problem with 0 if all the register values were negative, and were always being set to negative.

borkdude16:12:25

@cjmurphy The problem description states: > The registers all start at 0.

cjmurphy16:12:23

Sure so no problem using either 🙂 I just like using the min possible value as the default for finding the maximum.

cjmurphy16:12:37

(If you have to use a default - if there really were none then nil would be better of course).

cjmurphy18:12:09

If there really is no input you want the running maximum to end up at nil. So I settled with @bhauman's function that avoids both the arity exception and the NPE, with no default (i.e. nil default) for the running maximum in my code. Repeating his useful function here:

cjmurphy18:12:21

(defn maximum [& args]
  (when-let [args (not-empty (filter some? args))]
    (apply max args)))

cjmurphy18:12:36

If however you did use a default value, and you interpreted 'the highest value held in any register during this process' to exclude registers that are only ever read from, and also to exclude the from value of registers that are set, then it would be possible for the 'running maximum' to end up as negative, in which case Long/MIN_VALUE will result in a correct outcome as long as there are a positive number of registers set.

cjmurphy15:12:19

If you attach any likelihood to the definition of 'held' requiring write then zero is not a good default. If you attach any likelihood to there being zero length input then it is better not to have a default at all. Good to be liberal with what you accept.

cjmurphy15:12:58

@borkdude @mfikes ^ Sorry to still be banging on about yesterday's thing 🙂

borkdude15:12:02

@cjmurphy the invariant is that held is the maximum of the previous state. 0 is exactly that.

cjmurphy15:12:27

I just thought that it was possible that held means read and write. Very woolly word 'held'.

mfikes16:12:55

Actually, I don’t really see a good solution to this whole mess given what Val pointed out. It seems you really need to initialize all your registers to 0. (Unless there is a way to accommodate not doing that.)

bhauman16:12:34

I'm not so sure about that

bhauman16:12:38

as registers aren't really defined before hand, the only case is where you have a register for a predicate value that isn't ever used

mfikes16:12:53

Maybe you are right. To define a register, you have to touch it.

mfikes16:12:26

Hmm. I guess you could read from a register but never write to it.

bhauman16:12:37

yeah thats the off case but

bhauman16:12:04

but it seems as if they are defining them dynamically

bhauman16:12:12

specifications, specifications

mfikes16:12:12

That register wound’t appear in your lazily-produced map, but has a theoretical value of 0

bhauman16:12:44

although you could populate it when you check it fairly easily

mfikes16:12:44

b dec 5 if a < 1 would have been a good nasty input

borkdude16:12:10

We have to generate inputs with spec and exercise the hell out of this

mfikes16:12:43

Finally, a reason to have used Spec to parse!

(s/exercise ::instr 4)
([() []] [() []] [() []] [(?? MN -2 if *o -t/LR 1) [{:tgt ??, :upd MN, :val -2, :if if, :lhs *o, :cmp -t/LR, :rhs 1}]])

mfikes16:12:37

One immediate bug this would ferret out for me is I have no function named MN. I would need to revise my s/def or do something.

borkdude16:12:08

symbol? should be a set with the symbols you expect

mfikes17:12:10

Yes. This is perhaps another argument to use Spec over hand-rolled regex’s: You can more succinctly define valid input: https://github.com/mfikes/advent-of-code/commit/84774e390f5ea82568cf1c87308b125692611a0e#diff-ae7d8fc250d870104fa0c68af9f2e19a

borkdude17:12:14

In PureScript I’m expecting an ADT for these things, although you could also just do it with a set there

mfikes19:12:06

@chrisblom Bravo! That's an extremely consumable solution, IMHO. By the way, I used eval as well. I also checked your solution and it works in Planck.

spfeiffer20:12:35

Wow…so elegant!

nooga21:12:12

I was thinking about exactly this