Advent of Code Solutions
nbviewer.jupyter.org
nbviewer.jupyter.org
Day 12 calculates the fibonacci sequence, day 23 involves a big multiplication, and day 25 outputs the a number in binary format. If we run all additions and multiplications with "inc" and "dec", of course it is going to take forever (on my computer at least). So for me, I was forced to read the code and recognize the multiplication and algorithm and put in a couple "shortcut". If my computer is any faster, I might miss the fun.
In fact, AFAIK, Norvig made that specific mistake previously.
In general, Python doesn't seem to include a function in its standard libraries if you can write it as an obvious combination of 2 or 3 existing functions or sequence comprehensions. Which is a good thing, you don't want to cognitively overload the users with too many similar building blocks.
On the other hand if you've spent some time in a different high level language doing stuff like this in python feels too pedestrian.
Let's say, to "save users time" you decide to include all the meaningful combinations of some basic functions into your standard library. Then selecting from all those functions is about as time consuming than combining the two basic functions, unless you have memorized all of them by heart.
And then, what can also happen, if you haven't got them all memorized, you actually look up for a certain combination, only to find that particular one is for whatever reason missing from the library and you still have to compose it with the other functions you have.
The point is, there is a trade-off between time saved by having functions in standard library and time spent looking those functions up. So you don't want to have functions that are obvious combinations of 2-3 function, unless it's something very very common (e.g. a map function as a combination of monadic bind and return).
Meh, difficulty of writing such code is irrelevant in comparison to how these higher level functions read and compose, consider :
next(x for x in seq if x > y, None)
vs. first(seq, lambda x: x > y, None)
seq.first(lambda x: x > y, None)
the intent is very clear in the second example, it just has useless implementation details in the first.I haven't done python in a while but every time I go back to it I find such edge cases where the code ends up being obfuscated or you write one-off functions that you need in random places and should be in std. Contrast with Clojure for eg. with it's rich collection manipulation standard library - data manipulation is high level and expressive
There's a few amazing computational ones interspersed, like the Numbermind one, but in general I didn't find that the further along I went the harder I worked out my elegant programming skills. It was mostly doing math on paper and pressing it into code.
Let me compare for example Lisp lists with Python lists. Lisp lists are in a way more low-level, lacking useful things as slices. Sure you can always write a function to do a slice, but the point is many useful functions are missing from the standard libraries. I tried to learn Common Lisp and Clojure (I like the language more than Python) but I was never more productive in it than in Python for this reason.
This year, I have actually done some AoC in Haskell, and it's a similar deal as with Lisp. There are plenty libraries, but they just don't work as well together. An example: I needed a queue. So I used Data.Seq, but, there is no pop() function. No big deal, for sure, but annoying. Another example: I wanted to put list or set into a hash table, but unfortunately, these are not defined as instances of Data.Hashable.
It's actually debatable whether list should be Hashable by default. Sure it would be practical, but someone could complain about the hashing implementation. And that's why Python is going to beat Haskell on these problems, where you simply don't want to worry about thousands of those little things.
I was actually thinking about building myself a "pythonic" Haskell prelude which would have similar design to Python standard libraries and the same attention to consistency.
Common Lisp provides the function SUBSEQ for primitive slices.
CL-USER 66 > (subseq '((a 1 x0) (b 2 x1) (c 3 x2)) 1 2)
((B 2 X1))
For some more slice functionality you could use the CL-SLICE lib.It's also not too difficult to write. I wrote a multi-dimensional slice like Take in Mathematica:
CL-USER 58 > (take '((a 1 x0) (b 2 x1) (c 3 x2))
'(1 3)
'(1 3))
((2 X1) (3 X2))
CL-USER 59 > (take '((a 1 x0) (b 2 x1) (c 3 x2))
-2)
((B 2 X1) (C 3 X2))
CL-USER 61 > (take '((a 1 x0) (b 2 x1) (c 3 x2))
:all
'(1 2))
((1) (2) (3))
CL-USER 62 > (take '((a 1 x0) (b 2 x1) (c 3 x2))
'(1 2)
:all)
((B 2 X1))
CL-USER 65 > (take '((a 1 x0) (b 2 x1) (c 3 x2))
-2
-2)
((2 X1) (3 X2))
with steps: CL-USER 71 > (take '((a 1 x0 y0) (b 2 x1 y1) (c 3 x2 y2))
:all
'(1 4 2))
((1 Y0) (2 Y1) (3 Y2))Actually, it does.
Your view of Common Lisp seems to be created more by assumptions and not so by actual real limitations.
Strings:
CL-USER 10 > (subseq "xxfoobarxx" 2 8)
"foobar"
Any vector: CL-USER 11 > (subseq #(10 30 20 100 50 30 20 20) 2 8)
#(20 100 50 30 20 20)
Bitvector: CL-USER 12 > (subseq #*10101010010101001010101 2 8)
#*101010Clojure is much much more usable than CL.
I wrote full-time Clojure for two years, and did all of last year's Advent of Code in Clojure. In my entire life, I have never once written a line of Java and I have no idea the details of its standard library. That's how well-encapsulated Clojure is.
Perhaps "viewl :: Seq a -> ViewL" a could be used as pop.
> I wanted to put list or set into a hash table, but unfortunately, these are not defined as instances of Data.Hashable.
Are you using the "hashable" package? There does seem to be an instance for lists (when the elements of the list are themselves hashable):
> Hashable a => Hashable [a]
OK, I stand corrected on Hashable, apparently what I said is not true for neither lists or sets.
I had a lot of fun playing Shenzhen I/O and TIS-100, so if you want to do some programming challenges, I'd recommend trying those.
I was amused when it performed even worse than expected - thanks to the lack of console buffering when I eschewed linking in the C stdlib (I forget if I instead invoked WriteConsole or what...)
Too many of the problems had the structure:
* here's Part A * submit solution for Part A * OK, now here's Part B * Part B is just different enough that you have to do a not-fun refactoring of your Part A code (that wouldn't be necessary if you'd just been asked to also design for Part B up front)
It was too much like working with an awful PM who can't decide what they want, and it mostly just made me angry.
The other (better, I think!) way of looking at it is that the part-2 problems are incentive for you to think carefully about your part-1 solution, so that it generalizes.
Norvig's keypad is a perfect example. Norvig's solution of a sentinel character generalizes, as would a solution based on a simple graph structure. But I plowed into it with a flat array of integers and the modulo operator, and had to rewrite for part-2; my solution was, in the context of the contest, inferior to Norvig's.
It was a breath of fresh air from the other problems where I have to do fancy things like "dynamic programming" and "math."
I've never finished an Advent of Code though, and I don't think I'd get further in another language than C, I just spend most of my Christmas/December time with family instead of coding; maybe if there was a February of Code I'd participate more actively.
I actually think C is an _ideal_ language for challenges like these: parse a simple input file, do one or two things and write out a number. In fact (with a small number of very big exceptions) my code is usually quite short and concise, even compared to solutions my friends write in higher level languages. I originally picked C, not because I thought I'd be finish blazing fast in it, but because I wanted to learn to use it better (also for work and fun), so with that said, what little code I've written is publicly displayed on GitHub[2] for (as Hungarians say) spitting at. It has some comments and sometimes longish explanations about "design decisions" in the commit messages. I really did end up learning a lot and I am much more fluent in C now than before (no more Google/StackOverflowing every single statement, hooray!), but perhaps the most surprising thing was:
You don't need a Makefile to build `thing` from `thing.c` if you don't need to pass any fancy arguments or anything, you can just write `make thing` without a Makefile and if there's a `thing.c` there it'll run gcc on it and produce your binary!
I'd like to finish these challenges someday. Let's see.