Reducing associative arrays in PHP

PHP doesn't natively support passing associative arrays into array_map(). But there are ways to work around this limitation.

One of the most powerful functions is modern programming is being able to map a function over the elements of an array and then reduce that array to a single value. This map-reduce functionality is at the core of distributed computing.

It’s also a very useful operation in PHP.

Assume we have an array of integers. We want the sum of the squares of this list of numbers. To get there, we first map a squaring function over our array, then reduce the resulting array to a single value.

$numbers = [3, 7, 2, 4, 9];

$sumOfSquares = array_reduce(
    array_map(
        function($n) {
            return $n ** 2;
        },
        $numbers
    ),
    function($c, $i) {
        return $c + $i;
    },
    0
);

echo $sumOfSquares;

More complex operations, though, might need to use associative arrays. Unfortunately, PHP lacks the ability to reduce an associate array natively, so we have to code a workaround.

The coding challenge

A recent coding challenge a friend gave me required exactly this kind of functionality.

Given a string columnTitle that represents the column title as appear in an Excel sheet, return its corresponding column number. For example: A -> 1, B -> 2, …, Z -> 26, AA -> 27, …

This is a relatively straight-forward problem to solve algorithmically as it’s, effectively, converting between base 26 (there are 16 characters) to base 10.

A = 1 * 26^0 = 1 * 1 = 1
AA = 1 * 26^1 + 1 * 26^0 = 1 * 26 + 1 * 1 = 26 + 1 = 27
ZY = 26 * 26^1 + 25 * 26^0 = 26 * 26 + 25 * 1 = 676 + 25 = 701

Being a straight-forward algorithm problem means we can leverage simple loops to iteratively arrive at a solution.

When simple loops are involved, it’s a great opportunity to leverage maps and reduction!

Associative array_reduce()

In this case, the problem is actually simple enough we don’t need a map. But reducing things will still come in handy.

First, we create an array that maps our character values (A->Z) to their integer equivalents (1-26):

$indexes = array_combine(range('A', 'Z'), range(1, 26));

Then we create an array from our Excel column title, but we want to reverse the order of the elements so we’re effectively reading from right to left:

$letters = array_reverse(str_split($columnTitle));

Finally, we want to reduce our array of column headers to a single value and … this is where things break down. We need both the letter and its index in the array to know what power of 26 we need to use in multiplication. Unfortunately, PHP doesn’t reduce associative arrays … so we need to work around this limitation.

Instead, we’ll reduce the keys of our array – the integer value indexes of each letter in the array. Within our reducer function, we’ll then reference back to our original array and use the index to extract the character itself:

array_reduce(array_keys($letters), function($c, $i) use ($letters, $indexes) {
    $c += 26 ** $i * ($indexes[$letters[$i]]);
    return $c;
}, 0);

Fancier PHP

Newer versions of PHP support fancier lambda function notations with arrow syntax. This effectively simplifies our entire reduction into a single line of code, making for a really smooth function overall:

function titleToNumber($columnTitle) {
  $idx = array_combine(range('A', 'Z'), range(1, 26));
  $col = array_reverse(str_split($columnTitle));

  return array_reduce(array_keys($col), fn($c, $i) => $c + 26**$i * ($idx[$col[$i]]), 0);
}

How would you work around this minor limitation of PHP? Would it make sense for us to introduce a new version of array_reduce() to the language that works on associative arrays?

#