$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$
$\huge{闘魂とは己に打ち克つこと。}$
$\huge{そして闘いを通じて己の魂を磨いていく}$
$\huge{ことだと思います}$
はじめに
@torifukukaiou さんの パク リスペクト記事です
Elixir Livebook で Advent of Code 2023 の問題を解いてみます
実装したノートブックはこちら
問題はこちら
Part 1 はこちら
セットアップ
Kino AOC をインストールします
Mix.install([
{:kino_aoc, "~> 0.1.5"}
])
Kino AOC の使い方はこちらを参照
入力の取得
Day 10 の入力を取得します
私の答え
私の答えです。
折りたたんでおきます。
▶を押して開いてください。
details
回答用のモジュールです
入力を扱いやすい形にするための parse
と、回答を作るための resolve
関数を持っています
defmodule Resolver do
@pipe_map %{
"-" => %{left: true, right: true},
"|" => %{up: true, down: true},
"F" => %{down: true, right: true},
"7" => %{down: true, left: true},
"L" => %{up: true, right: true},
"J" => %{up: true, left: true},
"S" => %{start: true, up: true, down: true, left: true, right: true}
}
@next_direction %{
up: :down,
down: :up,
left: :right,
right: :left,
}
def parse(input) do
input
|> String.split("\n")
|> Enum.with_index()
|> Enum.reduce(%{}, fn {line, row_index}, acc ->
line_maze =
line
|> String.codepoints()
|> Enum.with_index()
|> Enum.into(%{}, fn {symbol, col_index} ->
{
{row_index, col_index},
%{
symbol: symbol,
route: Map.get(@pipe_map, symbol, nil)
}
}
end)
|> Map.filter(fn {_, symbol} -> !is_nil(symbol.route) end)
Map.merge(acc, line_maze)
end)
end
def resolve(maze) do
{start_position, start_pipe} =
maze
|> Enum.filter(fn {_, pipe} ->
Map.has_key?(pipe.route, :start)
end)
|> hd()
main_loop = get_main_loop(start_position, start_pipe, maze)
IO.inspect(main_loop)
{top_edge, bottom_edge} =
main_loop
|> Enum.map(fn {{row, _}, _} -> row end)
|> then(fn rows ->
{Enum.min(rows), Enum.max(rows)}
end)
{left_edge, right_edge} =
main_loop
|> Enum.map(fn {{_, col}, _} -> col end)
|> then(fn cols ->
{Enum.min(cols), Enum.max(cols)}
end)
IO.inspect({top_edge, bottom_edge, left_edge, right_edge})
top_edge..bottom_edge
|> Enum.reduce(0, fn row, row_acc ->
{_, col_sum, _} =
left_edge..right_edge
|> Enum.reduce({nil, 0, false}, fn col, {pre_stack, pre_count, pre_in_loop} ->
symbol = Map.get(main_loop, {row, col}, nil)
{across, stack} = across?(symbol, pre_stack)
in_loop = if across, do: !pre_in_loop, else: pre_in_loop
if is_nil(symbol) and in_loop do
{stack, pre_count + 1, in_loop}
else
{stack, pre_count, in_loop}
end
end)
IO.inspect(col_sum)
row_acc + col_sum
end)
end
defp get_main_loop(start_position, start_pipe, maze) do
Stream.iterate(0, & &1 + 1)
|> Enum.reduce_while(
{start_position, start_pipe, :start, [], nil},
fn index, {pre_position, pre_pipe, pre_direction, loop_list, start_direction} ->
{next_position, next_pipe, next_direction} =
search_next(pre_position, pre_pipe, pre_direction, maze)
loop_list = [{next_position, next_pipe.symbol} | loop_list]
if Map.has_key?(next_pipe.route, :start) do
start_symbol = get_symbol(start_direction, next_direction)
{_, loop_list} = List.pop_at(loop_list, 0)
{:halt, [{start_position, start_symbol} | loop_list]}
else
start_direction =
if index == 0, do: next_direction, else: start_direction
{:cont, {next_position, next_pipe, next_direction, loop_list, start_direction}}
end
end
)
|> Enum.into(%{})
end
defp search_next(position, pipe, pre_direction, maze) do
[:up, :down, :left, :right]
|> Enum.filter(& (&1 != pre_direction))
|> Enum.map(fn direction ->
get_next(position, pipe, direction, maze)
end)
|> Enum.filter(fn {_, next_pipe, _} ->
!is_nil(next_pipe)
end)
|> hd()
end
defp get_next({row_index, col_index}, pipe, direction, maze) when is_map_key(pipe.route, direction) do
next_position =
case direction do
:up -> {row_index - 1, col_index}
:down -> {row_index + 1, col_index}
:left -> {row_index, col_index - 1}
:right -> {row_index, col_index + 1}
end
next_direction = Map.get(@next_direction, direction)
next_pipe =
maze
|> Map.get(next_position)
|> check_next(next_direction)
{next_position, next_pipe, next_direction}
end
defp get_next(_, _, _, _), do: {nil, nil, nil}
defp check_next(nil, _), do: nil
defp check_next(next_pipe, next_direction) when is_map_key(next_pipe.route, next_direction) do
next_pipe
end
defp check_next(_, _), do: nil
defp get_symbol(:up, :up), do: "|"
defp get_symbol(:down, :down), do: "|"
defp get_symbol(:left, :left), do: "-"
defp get_symbol(:right, :right), do: "-"
defp get_symbol(:down, :right), do: "L"
defp get_symbol(:down, :left), do: "J"
defp get_symbol(:up, :right), do: "F"
defp get_symbol(:up, :left), do: "7"
defp get_symbol(:right, :down), do: "F"
defp get_symbol(:left, :down), do: "7"
defp get_symbol(:right, :up), do: "J"
defp get_symbol(:left, :up), do: "L"
defp across?(nil, stack), do: {false, stack}
defp across?("|", _), do: {true, nil}
defp across?(symbol, nil), do: {false, symbol}
defp across?("-", stack), do: {false, stack}
defp across?("J", "F"), do: {true, nil}
defp across?("7", "L"), do: {true, nil}
defp across?("7", "F"), do: {false, nil}
defp across?("J", "L"), do: {false, nil}
end
parse
で各パイプを座標をキー、記号、ルート(どの方向に行けるか)のマップをバリューとして読み取ります
入力
.....
.S-7.
.|.|.
.L-J.
.....
パース結果
%{
{1, 1} => %{symbol: "S", route: %{start: true, up: true, down: true, left: true, right: true}},
{1, 2} => %{symbol: "-", route: %{left: true, right: true}},
{1, 3} => %{symbol: "7", route: %{down: true, left: true}},
{2, 1} => %{symbol: "|", route: %{up: true, down: true}},
{2, 3} => %{symbol: "|", route: %{up: true, down: true}},
{3, 1} => %{symbol: "L", route: %{up: true, right: true}},
{3, 2} => %{symbol: "-", route: %{left: true, right: true}},
{3, 3} => %{symbol: "J", route: %{up: true, left: true}}
}
resolve
では、まずは Part 1 と同じようにしてループを探索していきます
このとき、後で使うため、スタート地点("S"
記号の地点)が実際にはどの記号だったのかを取得しておきます
上の例で言うと、スタート地点は右と下に移動できるので "F"
です
こうして取得したループ(メインループ)は以下のようになっています
%{
{1, 1} => "F",
{1, 2} => "-",
{1, 3} => "7",
{2, 1} => "|",
{2, 3} => "|",
{3, 1} => "L",
{3, 2} => "-",
{3, 3} => "J"
}
座標毎に各パイプの記号を記号を持つ形です
(メインループ以外のパイプは排除されています)
次にメインループの内側の領域を取得します
座標を横方向に辿って行ったとき(縦方向でも同様ですが)、パイプを奇数回横切ったときはループの内側で、偶数回横切った時はループの外側です
ループがどのような形であってもこれは変わりません
"|"
を通った時は「横切った」と言えます
"F-J"
や "L-7"
でも横切っています
"F-7"
や "L-J"
は横切っていません(ループの縁に沿った)
このルールに従い、各行についてループの中にいるときだけカウントした結果を全行合計すると答えが出ます
まとめ
Part 1 と比べて圧倒的に難しかったです
「内側とは何か?」「横切ったとは何か?」を上手く定義するのに時間がかかりました
最後に「スタート地点の実際の形を取得する」ところをなんとか乗り越えて正解できました