~sgiath/aoc-2018

9c6648a29f7f3b4a93876449528597be0fe5d79f — Sgiath 2 years ago c3384bd
Add comments and structure code
3 files changed, 114 insertions(+), 32 deletions(-)

M lib/day01/solution.ex
M lib/day02/solution.ex
M lib/mix/tasks/advent.ex
M lib/day01/solution.ex => lib/day01/solution.ex +2 -13
@@ 33,12 33,6 @@ defmodule AdventOfCode.Day01 do

  Starting with a frequency of zero, what is the resulting frequency after all of 
  the changes in frequency have been applied?

  ## Example

      iex> AdvenOfCode.Day1.part1()
      497

  """
  @spec part1(input :: File.Stream.t()) :: integer()
  def part1(input) do


@@ 74,12 68,6 @@ defmodule AdventOfCode.Day01 do
    - +7, +7, -2, -7, -4 first reaches 14 twice.

  What is the first frequency your device reaches twice?

  ## Example

      iex> AdventOfCode.Day1.part2()
      558

  """
  @spec part2(input :: File.Stream.t()) :: integer()
  def part2(input) do


@@ 97,8 85,9 @@ defmodule AdventOfCode.Day01 do
    end)
  end

  @doc "Convert lines of file to list of integers"
  @spec convert_file(file_stream :: File.Stream.t()) :: Enumerable.t()
  defp convert_file(file_stream) do
  def convert_file(file_stream) do
    file_stream
    |> Stream.map(&Integer.parse/1)
    |> Stream.map(fn {num, _trailing} -> num end)

M lib/day02/solution.ex => lib/day02/solution.ex +109 -18
@@ 1,34 1,125 @@
defmodule AdventOfCode.Day02 do
  @moduledoc """
  Advent of Code - Box IDs - 2th December

  https://adventofcode.com/2018/day/2

  Author: Filip Vavera <FilipVavera@sgiath.net>
  """

  @doc """
  Late at night, you sneak to the warehouse - who knows what kinds of paradoxes you
  could cause if you were discovered - and use your fancy wrist device to quickly scan
  every box and produce a list of the likely candidates (your puzzle input).

  To make sure you didn't miss any, you scan the likely candidate boxes again, counting
  the number that have an ID containing exactly two of any letter and then separately
  counting those with exactly three of any letter. You can multiply those two counts
  together to get a rudimentary checksum and compare it to what your device predicts.

  For example, if you see the following box IDs:

    - abcdef contains no letters that appear exactly two or three times.
    - bababc contains two a and three b, so it counts for both.
    - abbcde contains two b, but no letter appears exactly three times.
    - abcccd contains three c, but no letter appears exactly two times.
    - aabcdd contains two a and two d, but it only counts once.
    - abcdee contains two e.
    - ababab contains three a and three b, but it only counts once.

  Of these box IDs, four of them contain a letter which appears exactly twice, and
  three of them contain a letter which appears exactly three times. Multiplying these
  together produces a checksum of 4 * 3 = 12.

  What is the checksum for your list of box IDs?
  """
  @spec part1(Stream.t()) :: integer()
  def part1(input) do
    {pairs, tripples} =
      Enum.reduce(input, {0, 0}, fn id, {pairs, tripples} ->
        {pair, tripple} =
          id
          |> String.to_charlist()
          |> Enum.reduce(%{}, fn char, acc -> Map.update(acc, char, 1, &(&1 + 1)) end)
          |> Enum.reduce({0, 0}, fn
            {_char, 2}, {_pairs, x} -> {1, x}
            {_char, 3}, {x, _tripples} -> {x, 1}
            _, acc -> acc
          end)

        {pair + pairs, tripple + tripples}
    # Compute number of words containing pairs and triples
    {pairs, triples} =
      Enum.reduce(input, {0, 0}, fn id, {pairs, triples} ->
        # Get pair and triple for one word
        {pair, triple} = get_pair_and_triple(id)

        # Sum it with accumulator
        {pair + pairs, triple + triples}
      end)

    pairs * tripples
    # Compute final checksum
    pairs * triples
  end

  @doc "Compute if string contains pair or triple"
  @spec get_pair_and_triple(string :: String.t()) :: {0 | 1, 0 | 1}
  def get_pair_and_triple(string) when is_binary(string) do
    string
    |> String.to_charlist()
    # Count occurrence of different characters in ID
    |> Enum.reduce(%{}, fn char, acc -> Map.update(acc, char, 1, &(&1 + 1)) end)
    # Count pairs and triples
    |> Enum.reduce({0, 0}, &pt_counts/2)
  end

  @doc "Reducer which counts if pair or triple occurred"
  @spec pt_count(counts :: {char(), integer()}, accumulator :: {0 | 1, 0 | 1}) :: {0 | 1, 0 | 1}
  def pt_counts(counts, accumulator \\ {0, 0})
  def pt_counts({_char, 2}, {_pairs, tripples}), do: {1, tripples}
  def pt_counts({_char, 3}, {pairs, _tripples}), do: {pairs, 1}
  def pt_counts(_counts, accumulator), do: accumulator

  @doc """
  Confident that your list of box IDs is complete, you're ready to find the boxes full
  of prototype fabric.

  The boxes will have IDs which differ by exactly one character at the same position
  in both strings. For example, given the following box IDs:

    - abcde
    - fghij
    - klmno
    - pqrst
    - fguij
    - axcye
    - wvxyz

  The IDs abcde and axcye are close, but they differ by two characters (the second and
  fourth). However, the IDs fghij and fguij differ by exactly one character, the third
  (h and u). Those must be the correct boxes.

  What letters are common between the two correct box IDs? (In the example above, this
  is found by removing the differing character from either ID, producing fgij.)
  """
  @spec part2(input :: Stream.t()) :: String.t()
  def part2(input) do
    input
    |> Enum.to_list()
    # Convert to actual list and also every string ID to charlist
    |> Enum.map(&String.to_charlist/1)
    |> boxes()
  end

  @doc "Find similar ID for the first ID in the list. If not found call recursively"
  @spec boxes(list :: list(charlist())) :: String.t()
  def boxes([head | tail]) do
    if correct_box = Enum.find
    Enum.find_value(tail, &similar(&1, head)) || boxes(tail)
  end

  defp difference(x, y) do
    true
  @doc "Decide if two words are similar - different in just one char"
  @spec similar(word1 :: charlist(), word2 :: charlist()) :: String.t() | nil
  def similar(word1, word2) do
    word1
    |> Enum.zip(word2)
    |> Enum.split_with(fn {bit1, bit2} -> bit1 == bit2 end)
    |> case do
      # Different chars is list with one element
      {same, [_char] = _different} ->
        # Convert zipped charlists to string
        same
        |> Enum.map(fn {x, _} -> x end)
        |> List.to_string()

      # Everything else is invalid
      _ ->
        nil
    end
  end
end

M lib/mix/tasks/advent.ex => lib/mix/tasks/advent.ex +3 -1
@@ 34,7 34,9 @@ defmodule Mix.Tasks.Advent do

  defp execute(day, part) do
    :"Elixir.AdventOfCode.Day#{day}"
    |> apply(:"part#{part}", [File.stream!("lib/day#{day}/input.txt")])
    |> apply(:"part#{part}", [
      "lib/day#{day}/input.txt" |> File.stream!() |> Stream.map(&String.trim_trailing/1)
    ])
    |> IO.inspect(label: "Day#{day}.part#{part}/0 result")
  end
end