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

Day 2: I Was Told There Would Be No Math

2015/2.livemd

Day 2: I Was Told There Would Be No Math

Mix.install([
  {:kino, "~> 0.12.3"}
])

Modules

defmodule Present do
  @moduledoc """
  Functions for calculating the various material requirements of
  decorating presents.
  """

  @typedoc "A box"
  @type box :: {pos_integer(), pos_integer(), pos_integer()}

  @doc """
  Calculates the total wrapping paper needed for a list of boxes.

  ## Example
  ```elixir
  iex> Present.total_wrapping_paper([{2, 3, 4}, {1, 1, 5}])
  81 
  ```
  """
  @spec total_wrapping_paper(list(box())) :: pos_integer()
  def total_wrapping_paper(boxes) do
    boxes
    |> Stream.map(&wrapping_paper/1)
    |> Enum.sum()
  end

  @doc """
  Calculates the total ribbon needed for a list of boxes.

  ## Example
  ```elixir
  iex> Present.total_ribbon([{2, 3, 4}, {1, 1, 5}])
  43
  ```
  """
  @spec total_ribbon(list(box())) :: pos_integer()
  def total_ribbon(boxes) do
    boxes
    |> Stream.map(&ribbon/1)
    |> Enum.sum()
  end

  # the wrapping paper needed for a box
  defp wrapping_paper(box) do
    surface_area(box) + smallest_side_area(box)
  end

  # the ribbon needed for a box
  defp ribbon(box) do
    volume(box) + smallest_perimeter(box)
  end

  # the surface area of a box
  defp surface_area(box) do
    {l, w, h} = box
    2 * l * w + 2 * l * h + 2 * w * h
  end

  # the area of the smallest side of a box
  defp smallest_side_area(box) do
    {l, w, h} = box
    Enum.min([l * w, l * h, w * h])
  end

  # the volume of a box
  defp volume(box) do
    {l, w, h} = box
    l * w * h
  end

  # the minimal perimeter of a box
  defp smallest_perimeter(box) do
    {l, w, h} = box
    Enum.min([2 * (l + w), 2 * (l + h), 2 * (w + h)])
  end
end
defmodule Parser do
  @moduledoc """
  For parsing a string into a list of box types.
  """

  @doc ~S"""
  Takes a string of newline separated dimensions and 
  returns a list of boxes.

  ## Example
  ```elixir
  iex> Parser.parse("4x2x8\n5x6x7")
  [{4, 2, 8}, {5, 6, 7}]
  ```
  """
  @spec parse(String.t()) :: list(Present.box())
  def parse(str) do
    str
    |> String.split("\n")
    |> Enum.map(&parse_line/1)
  end

  # parse a line eg. "5x2x3" maps to {5, 2, 3}
  defp parse_line(str) do
    str
    |> String.split("x")
    |> Enum.map(&String.to_integer/1)
    |> List.to_tuple()
  end
end

Input

input = Kino.Input.textarea("Please paste your puzzle input:")

Part 1

input
|> Kino.Input.read()
|> Parser.parse()
|> Present.total_wrapping_paper()

Part 2

input
|> Kino.Input.read()
|> Parser.parse()
|> Present.total_ribbon()