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

Day9

2018/day9.livemd

Day9

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"},
  {:elixir_ds, "~> 0.1.0"}
])

Setup

{:ok, data} = KinoAOC.download_puzzle("2018", "9", System.fetch_env!("LB_AOC_SECRET"))

Solve

# can be also done with queue
# https://hexdocs.pm/elixir_ds/0.1.0/ElixirDS.Deque.html#rotate/2
defmodule Circle do
  defmodule Node do
    defstruct id: nil, r: nil, l: nil

    def new(data \\ 0) do
      %__MODULE__{id: data}
    end
  end

  defstruct current: nil, nodes: nil

  def new(data \\ 0) do
    node = data |> Node.new() |> Map.put(:r, data) |> Map.put(:l, data)
    nodes = %{node.id => node}
    %__MODULE__{current: node.id, nodes: nodes}
  end

  # cur <-> new <-> right
  def put_right(c, data) do
    cur_id = c.current
    r_id = get_in(c, [Access.key(:nodes), Access.key(cur_id), Access.key(:r)])
    n = data |> Node.new() |> Map.put(:l, cur_id) |> Map.put(:r, r_id)

    c
    |> put_in([Access.key(:nodes), Access.key(n.id)], n)
    |> put_in([Access.key(:nodes), Access.key(cur_id), Access.key(:r)], n.id)
    |> put_in([Access.key(:nodes), Access.key(r_id), Access.key(:l)], n.id)
    |> put_in([Access.key(:current)], n.id)
  end

  # left <-> new <-> cur
  def put_left(c, data) do
    cur_id = c.current
    l_id = get_in(c, [Access.key(:nodes), Access.key(cur_id), Access.key(:l)])
    n = data |> Node.new() |> Map.put(:l, l_id) |> Map.put(:r, cur_id)

    c
    |> put_in([Access.key(:nodes), Access.key(n.id)], n)
    |> put_in([Access.key(:nodes), Access.key(cur_id), Access.key(:l)], n.id)
    |> put_in([Access.key(:nodes), Access.key(l_id), Access.key(:r)], n.id)
    |> put_in([Access.key(:current)], n.id)
  end

  # new_left <-> left ^ <-> cur
  def pop_left(c) do
    cur_id = c.current
    lpop_id = c.nodes[c.current].l
    new_lid = c.nodes[lpop_id].l
    c
    |> put_in([Access.key(:nodes), Access.key(cur_id), Access.key(:l)], new_lid)
    |> put_in([Access.key(:nodes), Access.key(new_lid), Access.key(:r)], cur_id)
    |> pop_in([Access.key(:nodes), Access.key(lpop_id)])
  end

  def cw(c, n \\ 1)
  def cw(c, 0), do: c

  def cw(c, n) do
    c
    |> Map.put(:current, c.nodes[c.current].r)
    |> cw(n-1)
  end

  def ccw(c, n \\ 1)
  def ccw(c, 0), do: c

  def ccw(c, n) do
    c
    |> Map.put(:current, c.nodes[c.current].l)
    |> ccw(n-1)
  end

  def size(c), do: length(c.nodes.keys)

  def inspect(c) do
    do_inspect(c, c.current, c.nodes[c.current].r, [c.current])
  end

  def do_inspect(_c, idx, idx, acc), do: Enum.reverse(acc)
  def do_inspect(c, st, cur, acc) do
    do_inspect(c, st, c.nodes[cur].r, [cur | acc])
  end
end
:initialized
defmodule Day9 do
  def parse(data) do
    Regex.scan(~r/\d+/, data)
    |> List.flatten()
    |> Enum.map(&amp;String.to_integer/1)
  end

  def solve(data, t \\ :t1) do
    [pl, num] = parse(data)
    num = t == :t2 &amp;&amp; num*100 || num
    game(pl, num)
  end

  def game(pl, num) do
    c = Circle.new(0)
    sc = %{}

    Enum.reduce((1..num), {c, sc}, fn i, {c, sc} ->
      if rem(i, 23) == 0 do
        cur_pl = rem(i, pl)

        {n, c} = c |> Circle.ccw(6) |> Circle.pop_left()

        sum = i + n.id
        sc = Map.update(sc, cur_pl, sum, fn d -> d + sum end)

        {c, sc}
      else
        {c |> Circle.cw() |> Circle.put_right(i), sc}
      end
    end)
    |> elem(1)
    |> Map.values()
    |> Enum.max()
  end
end

t0 = "9 players; last marble is worth 25 points"
t1 = "10 players; last marble is worth 1618 points"
t2 = "13 players; last marble is worth 7999 points"
t3 = "17 players; last marble is worth 1104 points"
t4 = "21 players; last marble is worth 6111 points"
t5 = "30 players; last marble is worth 5807 points"

Day9.solve(t2) |> IO.inspect(label: "test 2")
Day9.solve(data) # 398371
# Day9.solve(data, :t2) #3212830280 ~20s