Advent of Code 2019 Day 4 (Part 2) PHP Hints

— Day 4: Secure Container (Part 2) —

Hello – if you’re just visiting this blog for the first time all code can be found on GitHub here. The state of the code at the beginning of this post is release Day4Part1 (as in the end of Day4Part1). You also might like to check out Getting Started – or my solution for Day4Part1 to get up to speed. I hope somebody finds this useful!

Following on from Part 1 – it looks like this is only a minor change to our testHasTwoAdjacentSameDigits()

An Elf just remembered one more important detail: the two adjacent matching digits are not part of a larger group of matching digits.

From the examples it sounds like we just need to reject passwords that only have 3 or more repeating digits.

I’m going to go through the motions of setting up the tests as I’ve done in previous days so far and duplicating the PasswordCracker to PasswordCracker2 to maintain backwards compatibility:

/tests/Day4Part2Test.php

<?php

use PHPUnit\Framework\TestCase;
use PuzzleSolvers\Day4\PasswordCracker2;

final class Day4Part2Test extends TestCase
{
    public function testHasTwoAdjacentSameDigits()
    {
        $passwordCracker = new PasswordCracker2('day4/part1/sample_range');

        $passwordCracker->password = '112233';
        $this->assertTrue($passwordCracker->hasTwoAdjacentSameDigits());

        $passwordCracker->password = '111122';
        $this->assertTrue($passwordCracker->hasTwoAdjacentSameDigits());

        $passwordCracker->password = '123444';
        $this->assertFalse($passwordCracker->hasTwoAdjacentSameDigits());

        $passwordCracker->password = '223333';
        $this->assertTrue($passwordCracker->hasTwoAdjacentSameDigits());
    }
}

make tests are failing so let’s modify our hasTwoAdjacentSameDigits() function. I’m sure this can be done with a simple regex change… But I resorted to looping through the characters:

public function hasTwoAdjacentSameDigits()
{
    $i = 1;
    $repeatingCount = 1;
    while ($i < strlen($this->password)) {
        if ($this->password[$i] != $this->password[$i - 1] && $repeatingCount == 2) {
            return true;
        }

        if ($this->password[$i] == $this->password[$i - 1]) {
            $repeatingCount++;
        } else {
            $repeatingCount = 1;
        }

        if ($i == strlen($this->password) - 1 && $repeatingCount == 2) {
            return true;
        }

        $i++;
    }

    return false;
}

Essentially I’m counting the consecutive occurance of a digit. If consecutive – increase the count. Otherwise set it back to 1.

If the next digit doesn’t match – but we just had 2 in a row then the password is valid.

If we are at the end of the string and we just had 2 in a row then the password is valid.

Otherwise the password is not valid.

OK make tests is now passing. Here is my final class:

<?php

namespace PuzzleSolvers\Day4;

use PuzzleSolvers\PuzzleSolver;

class PasswordCracker2 extends PuzzleSolver
{
    public $lowerRange;
    public $upperRange;
    public $password;

    public function __construct($filename)
    {
        parent::__construct($filename);

        $inputPieces = explode('-', $this->inputs[0]);
        $this->lowerRange = $inputPieces[0];
        $this->upperRange = $inputPieces[1];
    }

    public function run()
    {
        $validPasswords = [];
        $invalidPasswords = [];
        $this->password = $this->lowerRange;
        while ($this->password <= $this->upperRange) {
            if (
                $this->hasSixDigits()
                && $this->isWithinRange()
                && $this->hasTwoAdjacentSameDigits()
                && $this->hasDigitsNeverDecrease()
            ) {
                $validPasswords[] = $this->password;
            } else {
                $invalidPasswords[] = $this->password;
            }

            $this->password = (string) ($this->password + 1);
        }

        $this->output = count($validPasswords);
    }

    public function hasSixDigits()
    {
        return (strlen($this->password) === 6);
    }

    public function isWithinRange()
    {
        return ($this->password >= $this->lowerRange && $this->password <= $this->upperRange);
    }

    public function hasTwoAdjacentSameDigits()
    {
        $i = 1;
        $repeatingCount = 1;
        while ($i < strlen($this->password)) {
            if ($this->password[$i] != $this->password[$i - 1] && $repeatingCount == 2) {
                return true;
            }

            if ($this->password[$i] == $this->password[$i - 1]) {
                $repeatingCount++;
            } else {
                $repeatingCount = 1;
            }

            if ($i == strlen($this->password) - 1 && $repeatingCount == 2) {
                return true;
            }

            $i++;
        }

        return false;
    }

    public function hasDigitsNeverDecrease()
    {
        $i = 1;
        while ($i < strlen($this->password)) {
            if ($this->password[$i] < $this->password[$i - 1]) {
                return false;
            }
            $i++;
        }

        return true;
    }
}

Let’s update solve.php

/src/day4/solve.php

<?php

require_once __DIR__ . '/../../vendor/autoload.php';

use PuzzleSolvers\Day4\PasswordCracker;
use PuzzleSolvers\Day4\PasswordCracker2;

$passwordCracker = new PasswordCracker('day4/part1/input');
$passwordCracker->run();
$output = $passwordCracker->getOutput();

echo "Day 4 Part 1 answer: " . $output . "\n";

$passwordCracker = new PasswordCracker2('day4/part1/input');
$passwordCracker->run();
$output = $passwordCracker->getOutput();

echo "Day 4 Part 2 answer: " . $output . "\n";

Let’s get that gold star!

$ php src/day4/solve.php 
Day 4 Part 1 answer: 2814
Day 4 Part 2 answer: 1991

The final code for this part can be found on GitHub here. Remember my answer may not be the same as yours for your real input. Make sure to replace your /inputs/day4/part1/input with your own file. Did it work? Let me know!

Next up: Day 5 Part 1

Leave a comment

Your email address will not be published. Required fields are marked *