This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-12-07
Channels
- # adventofcode (269)
- # beginners (100)
- # boot (6)
- # cider (4)
- # cljsrn (4)
- # clojure (161)
- # clojure-android (31)
- # clojure-argentina (2)
- # clojure-brasil (8)
- # clojure-greece (45)
- # clojure-india (2)
- # clojure-madison (2)
- # clojure-russia (17)
- # clojure-spec (4)
- # clojure-sweden (1)
- # clojure-uk (32)
- # clojurescript (93)
- # core-logic (2)
- # cursive (21)
- # data-science (2)
- # datomic (46)
- # defnpodcast (1)
- # duct (5)
- # emacs (21)
- # events (1)
- # fulcro (17)
- # graphql (13)
- # job (1)
- # jobs (2)
- # leiningen (11)
- # lumo (3)
- # off-topic (119)
- # om (4)
- # onyx (2)
- # planck (6)
- # portkey (12)
- # re-frame (5)
- # reagent (3)
- # ring (5)
- # shadow-cljs (27)
- # spacemacs (19)
- # specter (6)
- # unrepl (9)
@minikomi Yes (cycle (range num-memory-banks))
is better than (mapcat identity (repeat (range num-memory-banks)))
. Now that I know about cycle
I won't have to coerce it from other functions. Thanks @mfikes š
cycle
can also be useful in the day 1 problem, where the problem definition indicates "The list is circular"
it's a good one
I managed to get the correct solution partially "by hand", now I'm going back to finish the code
to part2 that is
heh, mine's about 3x slower
but I'm OK with that
good point
Today's problem with DataScript + ex-info
: https://github.com/vvvvalvalval/advent-of-code-2017/blob/master/src/aoc2017/day07.clj
https://github.com/minikomi/advent-of-code/blob/master/src/advent/2017/day7.clj here's my loop-de-loop solution
FWIW, my code for day 7: https://github.com/borkdude/aoc2017/blob/master/src/day7.clj
To keep it sustainable, Iāll try not to think about this puzzle and to tweak things all day š
my goal with these is to arrive at the solution asap, without too much tinkering with performance and algorithmic elegance
Getting to try some libs that make it easy to solve the problems can be a good value add too. Working on last year's problems, I've been able to use Clara Rules for example
Looking through your datascript solution now, never applied datascript or datomic to any advent problems yet
Frankly, for this problem, DataScript turned out to be more overkill than I hoped.
still, quite comfortable for representing graphs.
I thought I would need a delay construction like with 2015's day 7, but this problem didnāt require it because of the regularity in the graph
nice viz of today: http://raevnos.pennmush.org/images/day07.svg
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.
All you needed to parse this one was clojure.edn/read-string
and destructuring (SPOILER in thread) š
I like what thegeez did with the underscore for the arrow, to emphasize this is just a separator
My day 7 solutions are up https://github.com/mfikes/advent-of-code/blob/master/src/advent_2017/day_07.cljc
I too had difficulty with part 2, trying perhaps 3 incorrect solutions until I figured out what was needed. And I did that part by hand, and backfilled the code š
This year my extra challenge is to finish before coffee, so I wasnāt too concerned of making it the most beautiful
has anyone tried an approach with inverting the whole tree?
I donāt know about inverting the whole tree, but I keep a map of name -> ancestors and Iāve inverted that one in the second part
in part 1 i iteratively pruned off all leaves and references to them until only the root is left
right, well thatās essentially inverting it i guess
thatās simpler
didnāt think of that
the puzzles publish at 7am local time š
for me part 2 is an ugly solution with println
which kinda works like throw in some of the other solutions
i rewrote it to use reduced
but itās uglier than the throw variant
sometimes thatās really nice for an early return
iām hoping to try a solution with walk
can you explain more?
did you get rid of the reduced and replaced it with some, or used both?
(let [[w tops] (get tower root)
top-res (group-by #(find-unbalanced tower %) tops)]
(or (some (comp #(when (reduced? %) %) first) top-res)
the problem that prevented me from an elegant solution is the fact that I need frequencies
or group-by
and only one of the values is reduced?
but I havenāt spent much time trying to make it prettier
since in the morning I wasted around 20 minutes because my parsing regex was using (....)
for the name part since in the test input all names were 4 chars long
and I thought I had an error in the solution itself which I couldnāt find⦠until i fixed the regex to be ([^ ]+)
I started by reduce
ing over the data, but I had a nil
(and ignored) accumulator, and had it stop with reduced
when it found the unbalanced bit. I then took this reduce
, and took the accumulator out of the reducing function, and essentially turned it into a predicate for some
.
Example:
(reduce (fn [_ x] (when (odd? x) (reduced x))) nil [2 4 2 7 2])
can be simplified to
(some (fn [x] (when (odd? x) x)) [2 4 2 7 2])
I'm curious about people using Spec to parse this one. Digging through the solutions now...
Hmm. A lot of silver stars right now. I wonder if that is normal for this time of day.
If you have kids and have to start early at work, I can imagine this is not something you do on the bus
I found this one more challenging, but when I found out about the squares at the bottom right, I felt really dumb
Never heard of it before. These are not typical problems you run into in day to day coding
@mfikes @borkdude From what I saw in AoC 2016, they do get more difficult š
(only did a subset of AoC 2016)
Interestingly, if you look at tentamen's part 2 solution (https://github.com/tentamen/adventofcode/blob/master/src/adventofcode2017/day7.clj), you can see that the input resulted in the unbalanced node being at the root of the tree, and if you are lucky with such a data set, you can skip some coding. (If I try that code on my input, it exhausts the stack somewhere in it)
Since we've been talking a lot about performance lately: I've been working on problem 23 of AoC 2016, in which performance ends up being rather critical. (The problem is to implement an interpreter about a very basic assembly that self-modifies code: https://adventofcode.com/2016/day/23) I first did a "Clojure-idiomatic" solution, with immutable state and multimethods (https://github.com/vvvvalvalval/advent-of-code-2017/blob/master/src/aoc2016/day23.clj#L44). Then I saw it tooks at least 30min to run on part 2, and did a second version that uses more primitive Java stuff (arrays, primitive, enums): See https://github.com/vvvvalvalval/advent-of-code-2017/blob/master/src/aoc2016/day23.clj#L165 and https://github.com/vvvvalvalval/advent-of-code-2017/blob/master/java/aoc2016/Day23.java The second version turned out to be about 100 times faster (16 seconds instead of 20 minutes).
Thought people might find this interesting
Yeah, @val_waeselynck it seems like this assertion might be true: "If you solve any problem with Clojure, you can come up with a succinct, maintainable, readable solution. If you need perf, there is often an order of magnitude or more available by switching to lower level constructs, and you can often get away with going down that path for the perf-critical portion of your code."
One a large project I was on recently (where overall perf was critical), it felt like 80ā90% of the code could be Clojure, and the rest could still be "Clojure", but really Java in disguise.
I think this assertion is true for the ClojureScript compiler, in a sense. Most of it is normal code, but the hot spots have been optimized in ways that deviate from "normal"
However, I saw Martin Odersky say in a talk that a compiler typically has no hot spots - but that may be more true of compilers that have to do a fair amount of static inference
Hmm. Very odd. The ClojureScript compiler seems like any other program to me. It has bits that are very hot compared to others. Martin is a genius so perhaps I'll dig up that talk.
I think it was this one https://www.youtube.com/watch?v=WxyyJyB_Ssc
@mfikes Totally agree with that. It should also be noted that even if I had started with the Java thing, it would have taken me more time overall to debug it and run it, given the time it took me to debug it š (granted, that may be because my Java is a little rusty)
Also, lots of the highly-performant and desirable algorithms that were invented in the 60s and 70s heavily rely on mutation. Perhaps that caused a lot of what I saw when working on the large project.
@mfikes I would argue SQL query engines are examples of highly-performant functional algorithms - i.e they yields highly performant programs that manipulate only immutable values
Good point. I wonder if they are functional on the outside but inside it is like "transient city"
I believe they totally are transient inside - but it's a good testimony that being declarative makes a lot of room for global optimizations
The virtual DOM is another such example
This entire subject is, of course, important. If it weren't for the availability of these tradeoffs, you really couldn't use Clojure for a large class of problems.
In other words, if you buy into using Clojure, I think you have to accept that for parts of your program, you can't insist on the ideal Clojure approach. (Unless your problem has no perf aspects, of course.)
@mfikes about the input, I checked if I wasnāt just lucky with my input so I took yours: https://github.com/borkdude/aoc2017/blob/master/src/day7.clj#L111
Yeah, for tentamen you can see that part of the code has a comment showing that the root is where the unbalanced node is. Then the remainder of the code leverages that fact. Fair.
@mfikes I even find that Clojure can be a more elegant solution to some problems in the high-performance space, what with macros and the runtime compiler as zero-cost abstractions
Definitely: Here is another effect I've seen. Since it is so damned easy to change Clojure code to do something in a different way, it is often feasible to explore different approaches and end up with a faster solution than if you were working in Java.
Time is money. If you can't explore alternatives you often have to accept the perf you have.
@mfikes Funnily, you could say that Clojure programmers are to Java Programmers what JITs interpreters are to AOT compilers: the first start very quickly and spend a little time optimizing the critical parts based on experience, the other ones start slowly and spend a lot of time speculating on what is going to be critical (and being pessimistic about it)
Think about the extreme of programming in machine code. Once you've solved a problem, you are pretty challenged to try a different approach, no matter how fasts your machine code runs.
This has led to a philosophy: Develop in the highest-level programming paradigm you can find that also affords you the opportunity to delve lower when needed.
Also, when correctness isnāt an issue (you want to research solutions first, handling all edge cases is not a problem) then types can get in the way a bit, where you have to be explicit when you donāt want to be
@val_waeselynck I like your JIT to AOT analogy
Exactly. The fact that ClojureScript can be thought of as a compiler is the wrong way of thinking of it, IMHO. The best way to think of ClojureScript is just as a Lisp, but with a mode that can be AOT'd to produce a small artifact when you really need it.
@borkdude totally. Being able to use Criterium and Tufte on the fly helps a lot when optimizing stuff
@mikelis.vindavs I use walk for today's problem: https://github.com/thegeez/clj-advent-of-code-2017/blob/master/src/advent/day7.clj#L81
That was like slogging through mud. I posted it, only because I want to be able to look back in the future and say "Oh, my! I have improved!"
I don't even want to look at the other solutions yet. I feel like I need to keep pounding on it.
And speaking of datomic and databases, Vik Fearing has been solving them using postgres: https://github.com/xocolatl/advent-of-code-2017
Iām surprised.. for few (3) elements, maps seem faster than vectors
that is, mapping to
{:id name
:weight (parse-int weight)
:children (some-> top (str/split #", "))}))
and then indexing on :id
is some 5-10% faster than doing the same with a 3-tuple and first
Iām using destructuring for both, didnāt try get
or nth
I'm trying to focus on habit at a time. And in this case it's been transducers and cljc. It's too easy for me to yak shave.
Is this in reference to my "mud" comment? Or perhaps you mean @mikelis.vindavs? The performance of mine was pretty good: 78ms iirc.
Ah sorry. I meant @mikelis.vindavs š about the 3-tuples
just (time (dotimes [_ 1000] (solve)))
And when I did a bit of refactoring, the mud cleaned up a bit. I don't like throwing to get a result, but that's what I ended up doing.
@grzm Me too. I donāt see that as a problem. Could easily refactor it, but I find it a creative way of solving the problem š
https://github.com/axelarge/advent-of-code/blob/master/src/advent_of_code/2017/day7.clj
an application of creative destruction š shows my bias with respect to throwing: I'm inclined to view it as something negative, rather than the tool it actually is.
Iāll try your suggestions , thanks
Detecting an error is fine with throw/catch and that was what the puzzle was about š
Also think I applied destructuring as deeply as I ever have: https://github.com/grzm/advent-of-cljc/blob/master/src/main/com/grzm/advent_of_code/advent_2017/day_07/part_2.cljc#L17
I'm grouping by the tree weight, so the keys are the weights. Given the constraints of the problem, I know there's only going to be one that's an odd tree weight, so everything else is going to have the same tree weight. Then the sort-by transforms the map into a sequence of two vectors, odd-tree-weight first.
It's kind of obscured by the destructuring, but that let
only has a single binding in it.
ah, missed that, I thought it was the argument to the function⦠getting tired š
I've been staying up and starting AoC at 11PM my time. Not the best to start working on a puzzle, especially ones like this one. Lots of little mistakes getting in the way of getting to the solution.
Three levels deep. Once you're there it's revealing a pretty specific solution, but it's cool that destructuring works so robustly.
@mikelis.vindavs I was a little surprised as well when I changed my solution for yesterday to represent banks of memory as integer-indexed maps instead of vectors, and it ran slightly faster. I did not make this change for perf reasons, but so that the code would be correct when using (merge-with + ,,,)
Here is the revision I'm talking about if curious : https://github.com/mfikes/advent-of-code/commit/9a178036eeb2c39b168712fed3388007217933ea
benchmarked with criterium, same thing ā the difference is small, but still there
but whatās even more interesting that maps vs vectors is the fact that for the 1st part of the problem
a set difference approach is slower than an iterative pruning one
that is, https://github.com/axelarge/advent-of-code/blob/master/src/advent_of_code/2017/day7.clj#L25-L48 is faster than https://github.com/axelarge/advent-of-code/blob/master/src/advent_of_code/2017/day7.clj#L77-L82
I canāt wrap my head around why that would be true. Is set/difference
slow?
We may actually be seeing completely different effects, since mine is in ClojureScript. (I don't know JavaScript that well, but it seems like arrays are integer-indexed maps in a sense anyway, at least until the optimizer does something about them.)
I've never heard anyone suggesting that set/difference
should be avoided for perf reasons.
the difference is pretty small, but in the other approach, Iām iteratively
- removing nodes that have no children (`filter + into {}`)
- removing references to those nodes (`map + filter + into {}`)
until thereās only 1 entry left
which seems like a ton more work than 2 map
+ set difference
is there a way to load a leiningen project in cljs? i have planck installed but am not very familiar with it
also I suppose io/resource
isnāt implemented in cljs
@mikelis.vindavs You can run lein classpath
to spit out a classpath, and Planck -c
to take a classpath
Planck has a
namespace that defines resource
. I've been using it: https://github.com/mfikes/advent-of-code/blob/master/src/advent_2017/day_05.cljc#L7
To be honest lein classpath
dumps out a lot of stuff you may not need, so I often manually specify the classpath, and there is -D
sugar that Planck and Lumo have to expand to existing deps in your local Maven repo: http://planck-repl.org/dependencies.html
thanks, Iāll try that
Yeah, just take a peek at my repo https://github.com/mfikes/advent-of-code to see how I have it set up to run both Planck and Clojure with one source tree.
PureScript solution: https://github.com/krisajenkins/AdventOfCode/blob/master/src/Year2017/Day7.purs
_total = _Newtype <<< prop (SProxy :: SProxy "total")
let childrensTotals = split.right ^.. traversed <<< _total
lensesā¦I Like how Kris came to the same spot where the last bit of code wasn't necessary (you could do the last bit by hand faster than writing the code for it)
Yeah, but I want my part-2
function to always give the answer.. itās part of my implicit contract
Whatās a better way for this:
(defn find-by-val
[m v]
(ffirst
(filter (fn [[k vā]]
(= v vā))
m)))
@borkdude If you want early termination and nil
is not a concern, this seems to read nicely
(defn find-by-val
[m v]
(some (fn [[k v']]
(when (= v v')
k))
m))
Iām using
(defn find-where [pred coll]
(some #(when (pred %) %) coll))
Hmm. Maybe they both produce the same result, even with nil
in the map as key or value
ahh but thatās sequences not maps
itās unfortunate this isnāt in core
@mfikes I was there, too (with you and Kris). I had a prn stuck in my recursive search function which printed out the unbalanced nodes. Did that by hand, then went back and finished the code.
Makes me think there's something getting in the way of me using the machine to explore the problem space. Either practice, or tooling.
busy day finally got my pushed: https://github.com/bhauman/advent-of-clojure-2016/blob/master/src/advent_of_clojure_2017/day07.clj
@bhauman Creative, rand-nth, just start at some random node in the tree: https://github.com/bhauman/advent-of-clojure-2016/blob/master/src/advent_of_clojure_2017/day07.clj#L30
ok, enough yak shaving for today: https://github.com/ChrisBlom/advent-of-code/blob/master/src/adventofcode/2017/day07.clj#L54-L84
@chrisblom Ahh, you got away with calling set/difference
with a non-set smaller 2nd argument. For example this works (set/difference #{:a :b :c} [:a :b])
while this doesn't (set/difference #{:a :b :c} [:a :b :c :d :e])
thatās a beautiful solution!
but does it assume that the weight will always need to be adjusted up?
fixed it: https://github.com/ChrisBlom/advent-of-code/commit/88bfa7a17e674ddeb3eeeeec84889c3a0c8b30b0
still pretty nice & readable
I found that getting the āodd one outā was surprisingly complicated
Yeah. This is mine, but Iām sure there are other solutions:
(defn unbalanced-balanced
[vals]
(->> (frequencies vals)
(group-by val)
sort
(map #(second %))
(map #(ffirst %))))
@grzm Yes, but attempting to formally address that with this proposed page https://github.com/mfikes/clojurescript-site/blob/c552daa145ce7005446fa315f042d82ddc4d51f8/content/reference/javascript-api.adoc
Actually this works too:
(defn unbalanced-balanced
[[a b c]]
(if (= a b)
[c a]
[a b]))
I think we do know there are at least 3 items, because if there were 2 it would violate the constraint that "exactly one program is the wrong weight."
This might be correct
(let [[a b :as all] (sort [2 2 17 2])]
(if (= a b)
(last all)
a))
Somewhat ugly but fast:
(defn unbalanced-balanced
[[a b c & nums]]
(let [[balanced maybe-unbalanced]
(cond (= a b) [a c]
(= a c) [a b]
(= b c) [b a])]
[(some #(when (not= balanced %) %)
(cons maybe-unbalanced nums))
balanced]))
In fact, in the third condition, we know for sure that a is the unbalanced one, but I didnāt want to write a special case for that
The computational complexity of this game https://www.youtube.com/watch?v=Sm-zWDaoCtI
Another version, more optimized:
(defn unbalanced-balanced
[[a b c & nums]]
(let [find-other (fn [v nums]
(some #(when (not= v %) %)
nums))]
(cond (= a b) [a (find-other a (cons c nums))]
(= a c) [a (find-other a (cons b nums))]
(= b c) [b a])))