I should've done part B more by hand, yeah. Automating it is a bit clumsy.
wow, so much to unpack
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)
part 1 was simply "the node which supports, but isn't supported" -- i.e. (first (filter set-of-supportees supporters))
Ah, was looking for a walk/postwalk based answer 😄
Getting under 2ms for part 2 with your input on my loop/recur
solution.. tree-seq solution turns out to be slower at ~14ms.. guess we can stop early once an unbalanced branch has a balanced set of children.
hmm tokyo timezone is good for getting the new questions when they come out, but not so great for discussion 😕
darn I forgot about tree-seq
it's such a good swiss-army-knife of a function
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..
knuckle-crack Places, everyone! REPLs at the ready!
Man. I love reductions
yep
Also, seems I'll need to dredge up my interpreter macros from last year...
what kinds of stuff did you have?
Not sure if I ever finished them tbh xD
It was basically a way to write functions that could be partially applied at fixed points
https://github.com/armstnp/advent-of-code-2016/blob/master/src/advent_of_code_2016/day8.clj#L45-L49
ah, I see
So I can bind the parts of the operation I know at read-time, and then hand it the remaining context at execute-time
Since that's a recurring pattern for these assembly-read type problems
this is both the blessing and curse of Lisps
you can make your own syntax!
😅
you can make your own syntax...
(well, not syntax per se)
Constructs
sure
In these cases, I found it easier than having to let-bind around an anonymous function
oh sure
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
for example, I don't know if Clojure has any partial application support short of reader function literals
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...
It's not common, I think, from what I've seen...
Partly because for small arities, it's just quicker to use #(f bound-param %1 %2)
, or something like
I'll stick that one in my toolbox
but now I think it's bed time
take care
goodnight 🙂
ah reductions
is amazing haha
not so remarkable today
It may be repetitive, but I really do end up getting into these micro-interpreter type problems >_>
Maybe partially because Lisps are just so snappy at handling them
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
😄
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
So it also usually pays to build your solution as a reduction when feasible
not a fan of the first element behavior though
(reductions str [1 2 3])
=> (1 "12" "123")
Same as reduce
, really
oh wait
No, yeah. Reduce always takes the first element as it stands if you don't provide a default
well, there’s always (drop 1)
d'oh
That was delayed, probably a lambda spinning up
lazy evaluation
@mikelis.vindavs that's maybe a little much only an hour after it dropped
good point
thanks 🙂
sorry
I kind of assumed this is a spoiler-alert zone
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.
it’s just general bikeshedding, I should probably get to work instead 😅
FWIW, I chose a third way 🙂
I was trying to save lines by trusting input https://github.com/axelarge/advent-of-code/blob/master/src/advent_of_code/2017/day8.clj#L15-L24
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.
oh, and how you reversed threading directions. That's one I'll have to keep in mind.
you mean with ->>
inside of ->
?
it’s neat, I only wish it worked the other way. the workaround there is to create a lambda
(->> {}
(#(assoc % :x :y)))
or use as->
I never know what to use for the name
love me some as->
I tend to go with $
that’s a good choice, stands out.
I’m loving this channel and the community so far!
could go with an emoji 😆
still pretty nice & readable
as->
is new to me! Much nicer to pick up than the magic wand / swiss arrows for most things, I bet
Today was ok. I found the answer for part 2 while trying to find part 1 it turned out 😉. 5 ms performance.
I have a security breach in there though 🙂
(fixed the security breach)
Posted my solutions to day 8. Highlights: cond->
, case
, reductions
. https://github.com/vvvvalvalval/advent-of-code-2017/blob/master/src/aoc2017/day08.clj
Here’s mine: https://github.com/borkdude/aoc2017/blob/master/src/day8.clj
https://github.com/minikomi/advent-of-code/blob/master/src/advent/2017/day8.clj
just using resolve 👐
@borkdude I find it intriguing that you used clojure.core/==
instead of clojure.core/=
, what's the rationale?
Not needed, just to confuse new people 😉
@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
yeah, i live on the edge
a dec -511 if fs/wipe-harddisk-if-odd >= -4
heh
@val_waeselynck mapcat vals
- good one
i need to use mapcat more, always miss it
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
).
ooh interesting edge case.
true: lucky by input 🙂
(update registers reg-loc (fnil identity 0))
interesting line to write 😆
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.
@karlis very similar to mine
Solution in PostgreSQL: https://twitter.com/pg_xocolatl/status/939099677026340864
@karlis Someone seems to have done this in Python: https://www.reddit.com/r/adventofcode/comments/7icnff/2017_day_8_solutions/dqxuwrz/
oh my 😄
well that was easy 🙂
https://github.com/bhauman/advent-of-clojure-2016/blob/master/src/advent_of_clojure_2017/day08.clj
darn why didn't I use max?
I guess its still early
@bhauman for fn-map I had: (defn op [sym] (get {’!= not= ’inc + ’dec -} sym (resolve sym)))
but I changed it because I considered it a security hole - your version is safe
anyway, this was an easy one for clojure at least
Yeah, I saw that you did that, good call
eval is temping here but ...
yeah resolve is a better call as well
I just started playing TIS-100, and I'm digging it, I'm super surpised by this.
My solutions are up. I used eval
FWIW: https://github.com/mfikes/advent-of-code/blob/master/src/advent_2017/day_08.cljc
@minikomi You can let ==
resolve to ==
FWIW
at first, i wanted to use syntax quote and eval
but then decided to go with an interpreter
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
I didn't generate an entire namespace from the source input, if that's what you mean. 😀
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
Nice @nooga Fairly compact result.
this is a pretty neat approach nevertheless!
I could definately golf it some more, avoid repeating solve1 code in solve2 and replace the loops with reduce 😄
thanks @mfikes
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?
@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)
fnil
is little known but really useful
Yep, I saw fnil
in @nooga’s code and thought the same — I’ve yet to finish my 3rd read-through of clojure.core
😉
@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 🙂
@mfikes Ah, update can take extra args, no need to close over them. Thanks!
@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 🙂
It’s a bummer that get
offers a default value but update
doesn’t. Breaks the symmetry.
I failed to point that out in an early interview for code that was like (if x x y)
undefined || 0
is idiomatic JS though.
(I think! Who knows if there really is a concept of idiomatic JS these days)
To be honest, I think the most profitable way I learned things is by reading other's code
Yep, that’s the value of AoC!
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
😄
I think Rich Hickey also looks at those docs
I'm proud to have what I suspect is the slowest solution for day 8, coming in at 5 seconds
Going for 3 orders of magnitude slower than the fastest
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
yeah, I got that in emacs as well
Cool!
but often you will be thinking “huh, does re
in clojure.string/split
have to be re or can it be a string”
or something
btw. my solution clocks at 5-6ms
I always look that up, every time
I would love if Dash could look up clojuredocs. I wonder if there’s a plugin for that.
Re speeds, part 1 5ms, part 2 15ms…
hm, as we figured out, it’s impossible to compare the performance unless we run each other’s code
but hey, if it’s 5ms it’s very decent
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.
I have to clarify; 5ms is what time
prints when running within CIDER. Not sure if the JVM is hot/cold/lukewarm/whatever.
Both parts run in 5 ms for me (with criterium), but that’s because the first run already contained the solution for part 2
same
I mean, solve1 and solve2 are basically the same
in my code
@mfikes I like the use of spec to parse the values. I should start using that instead of regexes.
Yeah, I wouldn't argue it is the right way. But the only way to find out is to try.
for now read-string + destructuring is my favorite way, although spec sounds more interesting 🙂
you can use edn/read-string for a safer version of read-string
but really no need here
edn/read-string is my default read-string 🙂
I haven’t encountered a nil pointer with max… I think the behavior of (max) is undefined, like 1 / 0
(max nil 0)
gives a NPE for me
@cjmurphy what about (maximum nil 10 11 nil)
?
fnil
is only good out to 3 args as well
its better to use keep
to filter out nils
Right 🙂
or filter some?
If you do that, then perhaps define a variant of max
that can cope with no args
but then you would maybe get (max)
which is undefined
it’s probably an x y problem
basically a nil punning version of max is what we are talking about
@cjmurphy Did you encounter this via (vals {})
returning nil
?
Maybe I'll make a max that works with a sequence and deals with nil that way...
Curious what context it arises in
`(defn maximum [& args] (when-let [args (not-empty (filter some? args))] (apply max args))) `
@mfikes (apply maximum (vals new-memory))
yes
In that case you should get an arity exception from max
not a nil
arg
Yes I got that exception too.
(apply max (vals {}))
-> arity issue
I used (mapcat vals <seq>)
and it took care of null values.
I wonder how you got nils in the first place, I didn’t have this problem
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.
Cool mapcat
solution. Perhaps (concat [1 2] nil [3 4])
’s nil
-punning should be in its docstring
ah ok — I got my answer in the first part already
@borkdude I got the NPE here: new-kept-highest (maximum kept-highest new-highest)
@cjmurphy you could initialize kept-highest to 0, because this was the initial value of all registers
@mfikes I discovered it by accident.
kept-highest
is the 'running maximum' for part two, so not actually a register value.
I ended up with this unsavory hack: (apply max (or (vals {}) [0]))
@cjmurphy yeah, that’s why you could seed the running max with 0, because that’s the initial max
(@cjmurphy assuming your doing something like reduce)
Yes sure, I'll do that and see if any more NPEs
I'm doing iterate
.
same idea
@orestis Right. I also discovered (merge-with + [1] {0 2})
accidentally works, but I suspect the concat
behavior is intended. Hrm.
I'm thinking to initialize kept-highest
to lowest possible value rather than 0.
@cjmurphy I did it this way: https://github.com/borkdude/aoc2017/blob/master/src/day8.clj#L54
so 0 is the initial value of cur-max (or kept-highest)
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.
There would be a problem with 0
if all the register values were negative, and were always being set to negative.
@cjmurphy The problem description states: > The registers all start at 0.
Sure so no problem using either 🙂 I just like using the min possible value as the default for finding the maximum.
(If you have to use a default - if there really were none then nil would be better of course).
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.
@cjmurphy the invariant is that held is the maximum of the previous state. 0 is exactly that.
I just thought that it was possible that held means read and write. Very woolly word 'held'.
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.)
https://clojurians.slack.com/archives/C0GLTDB2T/p1512723886000290
I'm not so sure about that
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
Maybe you are right. To define a register, you have to touch it.
Hmm. I guess you could read from a register but never write to it.
yeah thats the off case but
but it seems as if they are defining them dynamically
specifications, specifications
That register wound’t appear in your lazily-produced map, but has a theoretical value of 0
although you could populate it when you check it fairly easily
Yep
b dec 5 if a < 1
would have been a good nasty input
We have to generate inputs with spec and exercise the hell out of this
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}]])
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.
symbol? should be a set with the symbols you expect
For generating register names: https://github.com/borkdude/aoc2015_day7/blob/master/day7.clj#L11
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
In PureScript I’m expecting an ADT for these things, although you could also just do it with a set there
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:
(defn maximum [& args]
(when-let [args (not-empty (filter some? args))]
(apply max args)))
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.
eval is not evil: https://github.com/ChrisBlom/advent-of-code/blob/master/src/adventofcode/2017/day08.clj#L20-L32
@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.
thanks!
Wow…so elegant!
Nice!
I was thinking about exactly this