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

— Day 4: Secure Container (Part 1) —

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 Day3Part2 (as in the end of Day3Part2). You also might like to check out Getting Started – or my solution for Day3Part1 to get up to speed. I hope somebody finds this useful!

So this puzzle looks like it might not be too complex. I’m thinking to write a few test cases for valid passwords. Then loop through all the possible numbers and record those which pass all the tests.

So lets start by setting up a test file – from the instructions it looks like there’s four good tests to start with:

  • It is a six-digit number.
  • The value is within the range given in your puzzle input.
  • Two adjacent digits are the same (like 22 in 1<em>22</em>345).
  • Going from left to right, the digits never decrease; they only ever increase or stay the same (like 111123 or 135679)

/tests/Day4Part1Test.php

<?php

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

final class Day4Part1Test extends TestCase
{
    public function testHasSixDigits()
    {
    }

    public function testIsWithingRange()
    {
    }

    public function testHasTwoAdjacentSameDigits()
    {
    }

    public function testDigitsNeverDecrease()
    {
    }
}

And let’s fill those out:

<?php

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

final class Day4Part1Test extends TestCase
{
    public function testEndToEnd(): void
    {
        $outputs = [1, 0, 0];
        foreach ($outputs as $inputFileNumber => $output) {
            $inputFile = 'day4/part1/' . $inputFileNumber;
            \PuzzleDebugger::print('TESTING INPUT FILE ' . $inputFile);
            $puzzleSolver = new CrossedWiresMeasurer($inputFile);
            $puzzleSolver->run();
            $this->assertSame($output, $puzzleSolver->getOutput());
        }
    }

    public function testHasSixDigits()
    {
        $passwordCracker = new PasswordCracker('day4/part1/sample_range');

        $passwordCracker->password = '123';
        $this->assertFalse($passwordCracker->hasSixDigits());

        $passwordCracker->password = '123456';
        $this->assertTrue($passwordCracker->hasSixDigits());
    }

    public function testIsWithingRange()
    {
        $passwordCracker = new PasswordCracker('day4/part1/sample_range');

        $passwordCracker->password = '1';
        $this->assertFalse($passwordCracker->isWithinRange());

        $passwordCracker->password = '123456';
        $this->assertTrue($passwordCracker->isWithinRange());
    }

    public function testHasTwoAdjacentSameDigits()
    {
        $passwordCracker = new PasswordCracker('day4/part1/sample_range');

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

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

    public function testDigitsNeverDecrease()
    {
        $passwordCracker = new PasswordCracker('day4/part1/sample_range');

        $passwordCracker->password = '654321';
        $this->assertFalse($passwordCracker->hasDigitsNeverDecrease());

        $passwordCracker->password = '123456';
        $this->assertTrue($passwordCracker->hasDigitsNeverDecrease());
    }
}

Now lets create the PasswordCracker class

/src/day4/PasswordCracker.php

<?php

namespace PuzzleSolvers\Day4;

use PuzzleSolvers\PuzzleSolver;

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

    public function run()
    {
    }

    public function hasSixDigits()
    {
    }

    public function isWithinRange()
    {
    }

    public function hasTwoAdjacentSameDigits()
    {
    }

    public function hasDigitsNeverDecrease()
    {
    }
}

Next I created a test input file sample_range

/inputs/day4/part1/sample_range

100000-200000

Now let’s run make tests and get the tests passing one-by-one. Let’s start with hasSixDigits()

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

That test is now passing. Let’s move on to isWithinRange()

public function __construct($filename)
{
    parent::__construct($filename);
    $inputPieces = explode('-', $this->inputs[0]);
    $this->lowerRange = $inputPieces[0];
    $this->upperRange = $inputPieces[1];
}

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

That test is now passing. Let’s move on to hasTwoAdjacentSameDigits()

public function hasTwoAdjacentSameDigits()
{
    return preg_match('/([0-9])\\1/', $this->password) === 1;
}

The regex here basically just checks for a digit [0-9] and then the result of that match again directly after \\1

That test is now passing. Let’s move on to hasDigitsNeverDecrease()

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

    return true;
}

Ok – so make tests are all passing now. Next we want to loop through all of the inputs and test them for validity. Let’s create our end-to-end tests now, with 3 input files restricting the input range to the provided examples:

/inputs/day4/part1/0

111111-111111

/inputs/day4/part1/1

223450-223450

/inputs/day4/part1/2

123789-123789
public function testEndToEnd(): void
{
    $outputs = [1, 0, 0];
    foreach ($outputs as $inputFileNumber => $output) {
        $inputFile = 'day4/part1/' . $inputFileNumber;
        \PuzzleDebugger::print('TESTING INPUT FILE ' . $inputFile);
        $puzzleSolver = new PasswordCracker($inputFile);
        $puzzleSolver->run();
        $this->assertSame($output, $puzzleSolver->getOutput());
    }
}

Now let’s implement the logic in run()

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

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

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

Alright make tests everything is passing. Note that I am treating password as a string to need to cast it back after adding 1. I think we’re ready to write our solve.php script

/src/day4/solve.php

<?php

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

use PuzzleSolvers\Day4\PasswordCracker;

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

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

And don’t forget to grab our input for the puzzle

/inputs/day4/part1/input

109165-576723

Here is the final PasswordCracker class:

<?php

namespace PuzzleSolvers\Day4;

use PuzzleSolvers\PuzzleSolver;

class PasswordCracker 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()
    {
        return preg_match('/([0-9])\\1/', $this->password) === 1;
    }

    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 get that gold star!

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

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 4 Part 2

Leave a comment

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