Garfieldtech

Technical thoughts, tutorials, and musings

Blog

Advent of Functional PHP: Day 9

Posted:

Day 9 of this year's Advent of Code revolves around grid interpretation. Specifically, we are given a grid of numbers and want to find the low points, that is, the numbers that are smaller than any of their orthogonal neighbors. (We're told to ignore diagonals in part 1.)

After finding the low points, we need to do a bit of math on each one, and add them up. As usual, this last step is mostly just to produce a single verification number at the end. That part is easy as usual, but how do we find the low points?

Advent of Functional PHP: Day 8

Posted:

Advent of Code Day 8 was, to put it mildly, a pain in the ass. There's a couple of reasons for that. It's a naturally tricky problem, it's hard to genericize, and it's explained fairly badly. It took a while but with some help from others I was finally able to figure out (and refactor to) a good, functional solution to it. So let's dive in.

The problem boils down to one of encryption. Our input is several lines that all look like this:

acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf

Where each letter corresponds to one segment in an LED display for a number. Each number appears once on the left side, which is enough for you to figure out what letter corresponds to what segment. Then we need to use that knowledge to decode the numbers on the right and figure out what the number is.

Advent of Functional PHP: Day 7

Posted:

Advent of Code Day 7 this year is another problem that's more about the math than about the programming, so we won't see much in the way of new functional techniques. Still, there's some interesting bits in there.

Today we need to calculate the fuel costs of moving a bunch of crabs in submarines all into a line. (Don't ask. Really, don't ask.) Essentially we want to center-align a series of points using the least "cost" possible. Crab positions are represented by a single number, as crabs can only move horizontally. (Because crabs.)

The trick for today is realizing that the crabs don't matter; it's a distance-cost calculation. In part 1, the cost for a crab to move one space toward whatever alignment number we want to pick is 1.

Advent of Functional PHP: Day 5

Posted:

After the last two days, Day 5 of Advent of Code is almost mundane in comparison. Today we're asked to read in the definition for a series of lines and compute how many times they intersect.

The process is much the same as the previous days: Parse the incoming data into some sort of data model, then run some computations on it. And both parts will consist primarily of pipe() operations, since we're really just shuffling data from one form to another.

Our input data looks like this (albeit with a much larger range of coordinates):

Advent of Functional PHP: Day 6

Posted:

Day 6's challenge is a little fishy. Given what we've already done so far, it's pretty simple. The only catch is making sure you do it in an efficient way.

Specifically, we're asked to model the growth patterns of fictional lantern fish. In this silly model, we start with a list of fish at various ages. Each fish spawns a new fish every 7 days, and a newborn fish takes an extra 2 days before it starts spawning new fish. Fish also never die. (Someone warn the AI people that we've found the paperclip optimizer.)

Part 1 asks us how many fish there are after 80 days, all around the world, given the start data. Let's find out, but let's do so efficiently.

Advent of Functional PHP: Day 3

Posted:

The third challenge in this year's Advent of Code is all about bit manipulation. We're asked to read in a series of binary numbers and interpret them in various entirely illogical ways as a form of diagnostics. (Incidentally, if you ever write a system that requires this kind of logic to debug its output, you're fired.)

In any case, we're given a file with a list of 12 digit binary numbers and asked to compute various values. In the first part, we are asked to find the most common bit (0 or 1) in each position, and the result is known as "gamma." Then we have to find the least common bit in each position, and the result is known as "epsilon." (I don't know why you would want to do this; it's all Greek to me.)

In practice, the functional part of the solution isn't all that challenging. It was the bit-wise manipulation that was tricky, as I rarely do bitwise operations. But we do get to show off a few new functional tracks, so let's dig in.

Advent of Functional PHP: Day 4

Posted:

Day 4 of Advent of Code has us playing bingo against a giant squid. (Don't ask; I don't understand it either.) More specifically, we want to take an input file that consists of a series of numbers that will get called, followed by a series of boards. We then need to compute which board will be the first to win, following the standard rules of bingo (although with no free space in the middle, the cheating squid...).

This sort of problem is inherently very stateful, and thus, frankly, not a good fit for functional code. It absolutely can be done in a functional way, but it's not the best fit. We're not interested in the best fit in this series, though, just how it could be done functional-style. So let's do it functional style just to say we did. Along the way we will really exercise the function composition concept, and show a few other tricks along the way.

Onwards!

Advent of Functional PHP: Day 2

Posted:

In today's challenge, we're asked to interpret a series of basic command lines from a file and update the position of our submarine accordingly. It's basically a graph walk of sorts, with instructions of up, down, and forward. (Apparently you cannot go backward, as in life.)

As with yesterday's challenge, we could do it imperatively with a foreach() loop and a couple of state variables floating around, but that conflates a whole bunch of different behaviors into one blob of code. We don't want to do that, so let's step back and consider the problem more clearly.

The first thing to realize is that we're tracking some value over time, our position, which is getting modified by each instruction line in the file. Said the other way around, we're iterating over a list of commands and each one is updating the position, using the result of the previous instruction as a starting point. "Walking a list and updating a value each time" should trigger our functional brains to say "ah ha, a reduce operation!"

As noted yesterday (I didn't know what today's challenge would be, I swear), a reduce operation walks over a list and performs the same operation (function) on each item, using the output of the previous iteration as an input. It also goes by the name "fold" or "foldl" (fold left) in a lot of more academic posts; same thing. But it fits our use case exactly: Given a start position and a list of "change position" commands, apply each command to the position in turn, returning a new position each time.

Advent of Functional PHP: Day 1

Posted:

Today's challenge asks us to interpret a list of numbers. In the first part, our goal is to determine how many elements in the list are larger than their immediate predecessor.

The imperative way would be to toss it in a foreach() loop and track some state along the way. But we want to be functional and avoid "track some state," because the whole point of functional programming is to avoid tracking state, as tracking state is error prone.

When looking at a foreach-style operation that has some state, my first inclination is to look at a reduce operation. A reduce operation walks over a list and performs the same operation (function) on each item, using the output of the previous iteration as an input. That is, each step takes the output of the previous operation and the next element, and produces an output. It's quite elegant.

However, reduction operations only allow you to pass forward one intermediary result. In our case, the input to a given operation is the current element and its predecessor. That can be squeezed into a reduction operation if we try, but it feels awkward to me, so I'm going to allow some for-style loops. As long as they're controlled, that's OK.

Let's think about the steps involved in our problem:

  1. Read the input file in from disk.
  2. Break it up into a series of integers (or numeric strings, which for PHP is close enough).
  3. Determine which of those elements is larger than its predecessor.
  4. Count the number of elements that pass the test in step 3.
  5. Profit!

Advent of Code 2021: Functional PHP

Posted:

I am planning to participate in Advent of Code this year. For those not familiar with it, it's a daily coding challenge that runs through December, until Christmas. Mostly it's just for fun, but some people take it as an opportunity to either push themselves (by solving the puzzles in a language they're unfamiliar with) or to show off some feature of a language they like, which they then blog about.

In my case, I'll be solving puzzles in PHP, of course, but specifically using functional techniques. My goal is to demonstrate how functional programming in PHP is not just viable but creates really nice solutions. At least, I hope it works out that way; I haven't seen any of the challenges yet. :-)

I will be using the just-released PHP 8.1, because it's a really nice release that I am already in love with. I will also be either using or extending a small functional library I've built recently to support my own projects, Crell/fp. Mostly it's built to support pipe operations, by providing a user-space pipe() function (since a syntactic one sadly didn't get accepted to PHP itself) and a series of functions that return closures in order to play nicely with it.

A secondary goal of this endeavor is to help flesh out that utility library more. :-) (It doesn't even have any common monads yet, like Either, which I expect I'll end up making along the way somehow.)

Each day, I will blog my PHP solution to the problem to share with the rest of the community in the hopes that it will be educational. At least, each day until I decide I can't keep up with it. We'll see how long I last...

Let the coding commence!