Powered by AppSignal & Oban Pro

Day20

2020/elixir/day20.livemd

Day20

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

Setup

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

Helpers

defmodule Aoc.Grid do
  @size 9
  @hsize div(@size, 2)

  def parse(data), do: parse(data, &(&1))

  def parse(data, fun) do
    raw = Enum.map(data, &String.graphemes/1)

    for {row, y} <- Enum.with_index(raw),
        {sym, x} <- Enum.with_index(row),
        into: %{} do
      {{y, x}, fun.(sym)}
    end
  end

  def mv(b) do
    for y <- 0..@hsize, x <- 0..@size, reduce: %{} do
      acc ->
        acc
        |> Map.put({y,x}, b[{@size-y, x}])
        |> Map.put({@size-y,x}, b[{y,x}])
    end
  end

  def mh(b) do
    for y <- 0..@size, x <- 0..@hsize, reduce: %{} do
      acc ->
        acc
        |> Map.put({y,x}, b[{y, @size-x}])
        |> Map.put({y,@size-x}, b[{y,x}])
    end
  end

  def cw(b) do
    for y <- 0..@size, x <- 0..@size, reduce: %{} do
      acc ->
        Map.put(acc, {x, @size-y}, b[{y, x}])
    end
  end

  def cw2(b) do
    for y <- 0..@size, x <- 0..@size, reduce: %{} do
      acc ->
        Map.put(acc, {@size-y, @size-x}, b[{y, x}])
    end
  end

  def ccw(b) do
    for y <- 0..@size, x <- 0..@size, reduce: %{} do
      acc ->
        Map.put(acc, {@size-x, y}, b[{y, x}])
    end
  end

  def m1(b), do: b |> cw() |> mh()
  def m2(b), do: b |> cw() |> mv()
  # def m3(b), do: b |> ccw() |> mh() # same as m2
  # def m4(b), do: b |> ccw() |> mv() # same as m1
  # def m5(b), do: b |> cw2() |> mh() # same as mv
  # def m6(b), do: b |> cw2() |> mv() # same as mh
  

  def cp(b), do: b

  def plot(grid, {my, mx} \\ {@size, @size}) do
    Enum.map(0..my, fn y ->
      Enum.reduce(0..mx, "", fn x, acc -> acc <> grid[{y, x}] end)
    end)
    |> Enum.join("\n")
    |> IO.puts()
  end
end
t1 = """
Tile 2311:
..##.#..#.
##..#.....
#...##..#.
####.#...#
##.##.###.
##...#.###
.#.#.#..##
..#....#..
###...#.#.
..###..###

Tile 1951:
#.##...##.
#.####...#
.....#..##
#...######
.##.#....#
.###.#####
###.##.##.
.###....#.
..#.#..#.#
#...##.#..

Tile 1171:
####...##.
#..##.#..#
##.#..#.#.
.###.####.
..###.####
.##....##.
.#...####.
#.##.####.
####..#...
.....##...

Tile 1427:
###.##.#..
.#..#.##..
.#.##.#..#
#.#.#.##.#
....#...##
...##..##.
...#.#####
.#.####.#.
..#..###.#
..##.#..#.

Tile 1489:
##.#.#....
..##...#..
.##..##...
..#...#...
#####...#.
#..#.#.#.#
...#.#.#..
##.#...##.
..##.##.##
###.##.#..

Tile 2473:
#....####.
#..#.##...
#.##..#...
######.#.#
.#...#.#.#
.#########
.###.#..#.
########.#
##...##.#.
..###.#.#.

Tile 2971:
..#.#....#
#...###...
#.#.###...
##.##..#..
.#####..##
.#..####.#
#..#.#..#.
..####.###
..#.#.###.
...#.#.#.#

Tile 2729:
...#.#.#.#
####.#....
..#.#.....
....#..#.#
.##..##.#.
.#.####...
####.#.#..
##.####...
##..#.##..
#.##...##.

Tile 3079:
#.#.#####.
.#..######
..#.......
######....
####.#..#.
.#...#.##.
#.#####.##
..#.###...
..#.......
..#.###...
"""

Solve

defmodule Day20 do
  alias Aoc.Grid, as: G
  @size 9

  @mut [
    &amp;G.cp/1, &amp;G.mv/1, &amp;G.mh/1, 
    &amp;G.cw/1, &amp;G.cw2/1, &amp;G.ccw/1,
    &amp;G.m1/1, &amp;G.m2/1
  ]

  def parse(data) do
    data
    |> String.split("\n\n", trim: true)
    |> Enum.map(fn block ->
      [title | grid] = String.split(block, "\n", trim: true)
      num = Regex.run(~r/\d+/, title, capture: :first) |> hd() |> String.to_integer()
      {num, G.parse(grid)}
    end)
  end

  def sides(block) do
    t = Enum.map(0..@size, fn x -> block[{0,x}] end) |> Enum.join("")
    b = Enum.map(0..@size, fn x -> block[{@size,x}] end) |> Enum.join("")
    l = Enum.map(0..@size, fn y -> block[{y, 0}] end) |> Enum.join("")
    r = Enum.map(0..@size, fn y -> block[{y, @size}] end) |> Enum.join("")

    {t, b, l, r}
  end

  def compare(b1, b2) do
    s1 = sides(b1)
      
    for mut <- @mut, reduce: [] do
      acc ->
        s2 = b2 |> mut.() |> sides()
        case match(s1, s2) do
          {:ok, match} -> [{mut, match} | acc]
          {:error, _} -> acc
        end
    end
  end

  def match({m,_,_,_}, {_,m,_,_}), do: {:ok, :tb}
  def match({_,m,_,_}, {m,_,_,_}), do: {:ok, :bt}
  def match({_,_,m,_}, {_,_,_,m}), do: {:ok, :lr}
  def match({_,_,_,m}, {_,_,m,_}), do: {:ok, :rl}
  def match(_,_), do: {:error, :no_match}

  def solve(data) do
    blocks = parse(data)
    matches = get_matches(blocks)
    t1(matches) |> IO.inspect(label: "t1")
  end

  def get_matches(blocks) do
    for {n1, b1} <- blocks,
        {n2, b2} <- blocks,
        n1 != n2, reduce: %{} do
      acc -> 
        case compare(b1, b2) do
          [] -> acc
          _m ->
            Map.update(acc, n1, [n2], fn matches -> [n2 | matches] end)
        end
    end
  end
 
  def t1(matches) do
    matches
    |> IO.inspect(limit: :infinity)
    |> Enum.filter(fn {_n, v} -> length(v) == 2 end)
    |> Enum.map(&amp;elem(&amp;1, 0))
    |> Enum.product()
  end
end

Day20.solve(t1)