Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Year 2021, Day 08

livebook/2021/day08.livemd

Year 2021, Day 08

Section

Input

input = Day08.test_input()

Split to individual codes

input = String.split(input, [" ", " | ", "\n"], trim: true)

Split every code to characters and sort it so it is easier to work with

input = Enum.map(input, &(&1 |> String.graphemes() |> Enum.sort()))

Split it to the original lines (10 signals + 4 output = 14)

input = Enum.chunk_every(input, 14)

Split every line to signal and output

input = Enum.map(input, &Enum.chunk_every(&1, 10))

Sort signals by length and output as Tuple instead of list

input =
  Enum.map(input, fn [signal, output] ->
    {Enum.sort_by(signal, &length/1), output}
  end)

Part 1

Get just the outputs in one flat list

part1 = Enum.flat_map(input, &elem(&1, 1))

Count just outputs with specified lengths

Enum.count(part1, &(length(&1) in [2, 3, 4, 7]))

Part 2

Parse signal to determine mapping of digits. These are the rules:

  • 2 segments is one
  • 3 segments is seven
  • 4 segments is four
  • 5 segments is two, three or five
    • three contains all segments from one
    • two has exactly 2 segments from four
    • five has exactly 3 segments from four
  • 6 segments is zero, six or nine
    • nine has all segments from four
    • zero has all segments from one
    • six doesn’t match any of previous rules
  • 7 segments is eight

Produces map with shape:

%{
  1 => 'ab',
  2 => '...',
  ...
}
defmodule Display do
  def find_digit(data, acc) when length(data) == 2, do: Map.put(acc, 1, data)
  def find_digit(data, acc) when length(data) == 3, do: Map.put(acc, 7, data)
  def find_digit(data, acc) when length(data) == 4, do: Map.put(acc, 4, data)
  def find_digit(data, acc) when length(data) == 7, do: Map.put(acc, 8, data)

  def find_digit(data, %{1 => one, 4 => four} = acc) when length(data) == 5 do
    cond do
      # three contains all segments of one
      Enum.all?(one, &(&1 in data)) ->
        Map.put(acc, 3, data)

      # two contains 2 segments from four
      Enum.count(data, &(&1 in four)) == 2 ->
        Map.put(acc, 2, data)

      # five contains 3 segments from four
      :otherwise ->
        Map.put(acc, 5, data)
    end
  end

  def find_digit(data, %{1 => one, 4 => four} = acc) when length(data) == 6 do
    cond do
      # nine contains all segments from four
      Enum.all?(four, &(&1 in data)) ->
        Map.put(acc, 9, data)

      # zero contains all segments from one
      Enum.all?(one, &(&1 in data)) ->
        Map.put(acc, 0, data)

      # six is the only 6 segment number left
      :otherwise ->
        Map.put(acc, 6, data)
    end
  end
end

Split input to signals and outputs

[signals, outputs] =
  Enum.reduce(input, [[], []], fn {s, o}, [sig, out] ->
    [[s | sig], [o | out]]
  end)

Convert signal data to mapping data

mappings =
  Enum.map(signals, fn signal ->
    Enum.reduce(signal, %{}, &Display.find_digit/2)
  end)

Reverse the mapping key and value

mappings = Enum.map(mappings, &Enum.map(&1, fn {k, v} -> {v, k} end))

Covert the number to string so we can join it easily later

mappings = Enum.map(mappings, &Enum.map(&1, fn {k, v} -> {k, Integer.to_string(v)} end))

Convert mapping back to map

mappings = Enum.map(mappings, &Enum.into(&1, %{}))

Zip the mappings and outputs back together

part2 = Enum.zip(mappings, outputs)

Apply the mapping to the output

part2 =
  Enum.map(part2, fn {mapping, output} ->
    Enum.map(output, &Map.get(mapping, &1))
  end)

Join the numbers to single string

part2 = Enum.map(part2, &Enum.join/1)

Convert the output from string to integer

part2 = Enum.map(part2, &String.to_integer/1)

Sum the outputs to get the final answer

Enum.sum(part2)