Andrew Fontaine

Mostly code

Advent of Code 2019: Day 4

05 Dec 2019
Code Snippet for Advent of Code 2019: Day 4

This one didn’t seem too bad, but I can’t help but feel there’s a “clever” solution that alludes me. Some sort of funky math theorizing that I just don’t know about.

Puzzle 1

Put on your hacker gloves, it’s time to crack some passwords!

Hackerman

Oh, you only want a count of possible ones? That shouldn’t be too hard.

First, a function to check if the digits/2 of the password match the rules.

  def p1_possible(y) do
    length(y) == 6 and Enum.sort(y) == y and check_doubles(y)
  end

While I feel like there should be some fancy way to check if doubles exist, I decided chunk_every/4 would work great here.

  defp check_doubles(y) do
    y
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.any?(fn [a, b] -> a == b end)
  end

Similar to yesterday’s puzzle, I am taking two adjacent digits and making sure they are equal. All that’s left is to count up the good ones.

  def compute_possibles([s, e]) do
    Enum.reduce(s..e, 0, fn x, acc ->
      y = Integer.digits(x)

      if p1_possible(y) do
        acc + 1
      else
        acc
      end
    end)
  end

Boom, HACKED

I'm in

Puzzle 2

Puzzle 2 has a nice little spin on the original problem, declaring that there is definitely at least one set of just doubles in the password.

  • 111111 is no good, as there isn’t a set of doubles all on its own
  • 111122 is good, because the 22 is a double.

I can already re-use p1_possible/1, as all of those rules apply as well. To see if there is a unique set of doubles, I can use chunk_while/4 to only emit chunks when the current digit is different than the last one.

  defp check_unique_doubles(y) do
    y
    |> Enum.chunk_while(
      [],
      fn
        x, [] -> {:cont, [x]}
        x, [x | _t] = acc -> {:cont, [x | acc]}
        x, [_y | _t] = acc -> {:cont, acc, [x]}
      end,
      fn x -> {:cont, x, []} end
    )
    |> Enum.any?(fn x -> length(x) == 2 end)
  end

I also decided it would be easiest to use pattern matching in the anonymous function, to avoid any extra ifs or conds. I’m not really sure what the last parameter is for, titled after_fun. It says that it runs after iteration is done, but I don’t understand what the difference is between returning {:cont, element, acc} and {:cont, acc} is. Something to look into at a later time!

After that, I just need to check if any chunk has a length of 2.

The solution is the same as before, but with the new p2_possible/1 instead.

  def compute_possibles([s, e]) do
    Enum.reduce(s..e, 0, fn x, acc ->
      y = Integer.digits(x)

      if p2_possible(y) do
        acc + 1
      else
        acc
      end
    end)
  end

  def p2_possible(y) do
    p1_possible(y) and check_unique_doubles(y)
  end

Calculating… calculating… .20 seconds is pretty quick still! I’m pleasantly surprised. 🌟 received.

hacked

Conclusion

I, uh, actually didn’t write any tests for this one. It was pretty straight-forward and I manged to solve each puzzle on the first try, which is always nice. I can’t help but think that there’s a better way to do this than brute-forcing things, but, hey, it finished really quick.

That’s all today! On to day 5!

I ❤ feedback. Let me know what you think of this article on Twitter @afontaine_ca