PHP: Be careful of the order of str_replace

Advent of code

I’m currently playing around with Advent of Code 2023 – if you don’t know it yet and you like to code. Play around with a new language, new paradigm or just want to code – this advent calendar is four you!

On the first day, you need to find the first and last number in a string.

Spoiler Alert! – here comes the second part of day 1

The second part is to not only match on 1, 2, 3, etc. but on one, two, three up to nine as well.

How to find first and last digit

There are a hundred ways to find the first and last digit in a string, ideas I came up with:

  • trim a-z
  • loop through the string and save the first digit and the “next”… until next is the last
  • preg_match

I implement preg_match, because it felt fun and regex is always a challenge for me.

The second part was laughable, I implemented a str_replace for the strings, and passed the result to my first implementation – but the result was wrong – WTF!?

Why doesn’t it work!?

I looked ad the results but couldn’t see anything wrong, but thankfully you always get a couple of test input and the correct intermediate data and the end result, I took them, built a PHPUnit test and voila, two of them failed.

  • Expected input: eightwothree -> 8wo3
  • Expected output: 83

But:


Failed asserting that 23 is identical to 83.
adventofcode2023/tests/Day1/IntegrationTest.php:32

What happened!?

My replacement looks like this:

public function fix(string $coordinate): string
{
    return str_replace(
        ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'],
        ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
        $coordinate
    );
}

And what I learned today is, that it looks like, that PHP is looping through my input array, is looking for replacements and replaces a substring. The problem is, the order is wrong, because eight comes in my string before two and if I replace eight with 8, there is no two left.

The solution, but not THE solution

My first idea was to use strtr, I used it in the past, it somehow replaces strings and maybe it works – it did work the way I expected it to work. It replaced the strings the way I wanted it:

public function fix(string $coordinate): string
{
    return strtr(
        $coordinate,
        array_combine(
            ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'],
            ['1', '2', '3', '4', '5', '6', '7', '8', '9']
        )
    );
}

So if you are looking for a string replacement method, which takes care of the order IN the string and not in the replacement array, this is your solution!

But adventofcode wasn’t happy – now it becomes a little brainfuck

Turns out, the expectation is eightwothree -> 823, my strtr implementation works for this example, but e.g. not for this: 1eightwo -> 182, where my implementation provides 18wo and therefore 18 which is not the expected result 12.

The solution

I thought about how to avoid it and the problem is, if I replace a string with the number I lose the string, obviously. But I need the remaining string to make sure to replace stuff behind it – and just in case in front of it as well, so my idea was to conserve most of the string, but not the first or last character.

eightwothree ->
ight8eightwothree ->
ight8eighwo2twthree ->
ight8eighwo2twhree3thre ->
823

Because I ignore all the characters anyway later I can add as many as I want. And how do we implement this now?

public function fix(string $coordinate): string
{
    $coordinate = trim($coordinate);
    $words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
    $digits = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
    for ($i = 0; $i < 9; $i++) {
        $begin = substr($words[$i], 0, -1);
        $end = substr($words[$i], 1);
        $digits[$i] = $begin . $digits[$i] . $end;
    }
    return str_replace(
        $words,
        $digits,
        $coordinate,
    );
}

First we get the begin and end of the digits and replace the values in our replacement array, then we use str_replace again – because we don’t need the order and it replaces more – not sure strtr works here as well though!

And running the code works <3

Leave a Reply

Discover more from Winkelwagen

Subscribe now to keep reading and get access to the full archive.

Continue reading