DEV Community

Discussion on: AoC Day 9: Marble Mania

Collapse
 
d4be4st profile image
štef

I managed to do this in elixir! As elixir is immutable it cant have real circular data structures so i had to use zippers

The second challenge took 3 s :)

defmodule Day09 do
  @doc """
  Calcualte challenge

      iex> Day09.run({9, 25, 23})
      32
  """
  def run(config) do
    start_values = {0, 1, {[], 0, []}, %{}}
    {_, players_score} = marble(start_values, config)
    {_, score} = Enum.max_by(players_score, fn {_, v} -> v end)
    score
  end

  @doc """
  Adds a marble

      iex> start_values = {0, 1, {[], 0, []}, %{}}
      iex> Day09.marble(start_values, {1, 1, 23})
      {{[0], 1, []}, %{}}
      iex> Day09.marble(start_values, {2, 2, 23})
      {{[0], 2, [1]}, %{}}
      iex> Day09.marble(start_values, {3, 3, 23})
      {{[1, 2, 0], 3, []}, %{}}
      iex> Day09.marble(start_values, {9, 22, 23})
      {{[5, 21, 10, 20, 2, 19, 9, 18, 4, 17, 8, 16, 0], 22, [11, 1, 12, 6, 13, 3, 14, 7, 15]}, %{}}
      iex> Day09.marble(start_values, {9, 25, 23})
      {{[20, 24, 2, 19, 18, 4, 17, 8, 16, 0], 25, [10, 21, 5, 22, 11, 1, 12, 6, 13, 3, 14, 7, 15]}, %{4 => 32}}

  """
  def marble(
        {player, marble, marbles, players_score},
        {players_count, highest_marble, divisible_marble} = config
      ) do
    cond do
      marble > highest_marble ->
        {marbles, players_score}

      rem(marble, divisible_marble) == 0 ->
        {removed_marble, marbles} = remove_marble(marbles)

        players_score =
          Map.update(
            players_score,
            player,
            marble + removed_marble,
            &(&1 + marble + removed_marble)
          )

        marble(
          {rem(player + 1, players_count), marble + 1, marbles, players_score},
          config
        )

      true ->
        marbles = add_marble(marble, marbles)

        marble(
          {rem(player + 1, players_count), marble + 1, marbles, players_score},
          config
        )
    end
  end

  @doc """
  Add a marble

      iex> Day09.add_marble(1, {[], 0, []})
      {[0], 1, []}
      iex> Day09.add_marble(2, {[0], 1, []})
      {[0], 2, [1]}
      iex> Day09.add_marble(3, {[0], 2, [1]})
      {[1, 2, 0], 3, []}
      iex> Day09.add_marble(4, {[1, 2, 0], 3, []})
      {[0], 4, [2, 1, 3]}
  """
  def add_marble(marble, marbles) do
    {previous, current, next} = next(marbles)
    {[current | previous], marble, next}
  end

  @doc """
  Add a marble

      iex> Day09.remove_marble({[5, 21, 10, 20, 2, 19, 9, 18, 4, 17, 8, 16, 0], 22, [11, 1, 12, 6, 13, 3, 14, 7, 15]})
      {9, {[18, 4, 17, 8, 16, 0], 19, [2, 20, 10, 21, 5, 22, 11, 1, 12, 6, 13, 3, 14, 7, 15]}}
  """
  def remove_marble(marbles) do
    {previous, current, next} = Enum.reduce(1..7, marbles, fn _, marbles -> previous(marbles) end)
    [new_current | next] = next
    {current, {previous, new_current, next}}
  end

  @doc """
  Moves to the right

      iex> Day09.next({[], 0, []})
      {[], 0, []}
      iex> Day09.next({[0], 1, []})
      {[], 0, [1]}
      iex> Day09.next({[0], 2, [1, 3]})
      {[2, 0], 1, [3]}
  """
  def next({[], current, []}), do: {[], current, []}

  def next({previous, current, []}) do
    next = Enum.reverse([current | previous])
    [current | next] = next
    {[], current, next}
  end

  def next({previous, current, next}) do
    previous = [current | previous]
    [current | next] = next
    {previous, current, next}
  end

  @doc """
  Moves to the left

      iex> Day09.previous({[], 1, [0, 2]})
      {[0, 1], 2, []}
      iex> Day09.previous({[0], 2, [1, 3]})
      {[], 0, [2, 1, 3]}
  """
  def previous({[], current, next}) do
    previous = Enum.reverse([current | next])
    [current | previous] = previous
    {previous, current, []}
  end

  def previous({previous, current, next}) do
    next = [current | next]
    [current | previous] = previous
    {previous, current, next}
  end
end