Throughout the spring and summer of 2021 a few of the times that I mentioned on the Advent of Code subreddit that I was doing the 2015 problem set in all 3 languages, some folks said they’d be interested in a writeup on the experience. Now that I’ve finally finished 2015 (my first set of 50 stars!) it’s time for that writeup. Before I continue, I’d like to thank everyone on the subreddit who has helped me. I have a README.md for each day’s problem and you’ll find my thanks to those who helped me within those READMEs here in my repo.
Why these languages?
- Python – it’s my main language and the one in which I’m most proficient. I have written some programs used by many others (like my Extra Life Donation Tracker) and utilities that solve some problem I have (like my btrfs snapshot and backup program). By solving each problem in Python first, I allow myself to focus on the problem first instead of a syntax I’m unfamiliar with.
- Ruby – I’ve had a lot of interest in Ruby because it is often compared and contrasted to Python. In 2020 I’d read through a book teaching kids how to program in Ruby and I wanted to push further.
- Perl – my first exposure to Perl was in the mid-90s when I was trying to figure out how people were making these cool websites with something called CGI scripts. I didn’t get too far with it at the time. Then, about 15 years ago I went through the O’Reilly books because I had the book Flickr Hacks and all (or most?) of the scripts were in Perl. I hadn’t really touched it since then and wanted to see if I could get proficient in it. Perl may be on the way out (I don’t think the Perl5 -> Perl6/Raku transition helped) but it’s still ranked #21 on Github. And the language is still being developed. But, anyway, there isn’t a real need for justification, I’ve just had a lot of folks ask me “why?” so I wanted to address that.
What the experience was like
First of all, Python’s list comprehensions are AWESOME. The funny thing is that until 1-2 years ago, I couldn’t figure out how to make sense of them. Then I just so happened to come across the right tutorials in some books and online classes last year that finally brought it all home. I now love it so much that it annoys me that Ruby doesn’t have it. (of course Perl wouldn’t).
I tried to be as idiomatic as I could given what I knew about either of the languages. In general, if you go through my repo in order – the later days will be more idiomatic as I learned more about the languages and gained more experience. That said, I could usually port my Python solution over to Ruby without too much trouble. Sometimes I would see where I could make idiomatic changes as I went along. Other times I would make the changes after getting the solution working or after getting some help from the AoC subreddit or the Ruby subreddit. Perl, being the older language, often was the most different. Sometimes it was for the better, but often it was worse.
Although I’ve never been an awesome programmer, I’ve done it at work and in school so I’ve come across C and Java in addition to Python, Ruby, and Perl. There are lots of arguments about curly braces (brackets in British english), indentation, etc. But having used 3 languages each with their own different ways of doing things has really shown the pros and cons. Python is the one I’m the most used to, so I have no problem using indentation to see when functions, classes, conditionals, etc end. (Especially in an IDE – copying code from a book is another story) I also find that curly braces tend to be pretty easy as long as you indent them so you can match it with the one above. But, for some reason, “end” with Ruby just causes me all kinds of headaches in seeing what goes with what (Day 23 was a real pain for that). Especially since the semantics are different in conditionals compared with Python and Perl. What I mean is that in Python when you have an if statement, you out-indent for your else or elif. For Perl you close your if statement curly braces and start a new one for your else. But in Ruby your else goes in the same “end” as your if statement. There’s definitely a logic to it, but it’s the odd man out in this trio.
Anyone who’s programmed for a while knows how important for the DRY principle it is to be able to create functions/methods/subroutines. But it is incredibly apparent to me that subroutines in Perl were created in a VERY DIFFERENT time period or mindset because they are the biggest pain in the world. Every time I had a subroutine-heavy algorithm designed in Python I began to dread creating the same thing in Perl. Passing arguments is THE WORST. Unlike a modern language where you’re defining what the variables are going to be called in the signature you’re pulling them off of an array that holds all the variables as if every single Perl subroutine is the equivalent of making your Python functions all have *args in the signature. And getting the data out reliably usually meant getting it as a scalar; a big pain if it was really an array! And there was the passing of references instead of arrays….oy!
Finally, one of the things that programming language geeks get into (and I guess I’m one of those now) is talking about how much extra syntax there is just for your language to parse. I hate languages that require semicolons. The fact that Python and Ruby don’t need them; the fact that, as I’ve done my research into Golang, it doesn’t need them. It just serves to remind me how pointless they are in Perl. The sigils were also annoying. The funny thing is that I could actually see them being incredibly useful if someone designed a major IDE to have a Perl parser. It could provide suggestions, etc based on knowing if your variable was an array or hash before you’ve even added data to it. But mostly it just served as a source of warnings when I’d first run any given Perl script.
What I learned
First off, a generic thing – different languages have different strengths. For the vast majority of the AoC problems, any programming language was as good as the next. But there were some where the built-ins or the parsing of a particular language made a solution easier or run a little faster.
- Python
- Got a lot more practice with regular expressions including learning when to use search vs match
- After struggling with it last December, I finally understand memoization
- using ord() to get the ASCII value for a letter and then using that to advance to the next letter
- How to do permutations where you need all the elements to sum to a specific number.
- How to use itertools Product to make combinations from different lists
- Ruby
- Creating files to import
- Using iterable#each to do idiomatic for loops
- map(&:to_i) to change an entire array from string to int without having to explicitly loop through the array.
- if $PROGRAM_NAME == __FILE__ to do Python’s equivalent of if __name__ == “__main__”
- How to do unit testing
- memoization/caching
- blocks in Ruby don’t need something like pass, so I was just able to do if backspace_or_unicode == nil and keep going.
- string.delete_prefix and string.delete_suffix are the equivalent of lstrip and rstrip in Python
- Chunk and chunk_while – which made Day 10 so easy!
- Ruby classes and how to initialize them
- using sort to sort by custom variables in a class
- It appears you cannot just access variables in a Ruby class, you have to have a method to return the value.
- How to do permutations where you need all the elements to sum to a specific number.
- array#flatten to reduce an array of arrays into an array
- by default, the Hash just returns nil instead of erroring out if I try to access a key that doesn’t exist. Easier having to use get in Python.
- Perl
- Push instead of append to add to arrays
- sorting numerically is a lot more complicated in Perl than the other languages
- using CPAN.
- Perl does not have True/False primitives
- Apparently there’s no way to tell if something is a string. If it’s not a number, hash, or array – it’s a string
- The existence of the $-[0] and $+[0] after you do a regex match. They return the start and end of a match, respectively.
What’s Next
Next up for me is starting work on the 2016 problem set. For that problem set I plan to add Haskell and Go to the list of languages. I will also keep Python, Ruby, and Perl so that I can stay sharp on those languages. I’ll also be intermittently going back to 2020 to finish the problems I didn’t finish last December and, eventually, also doing those in the other languages.