はじめに
Advent of code 2023 Day 14 の Part 1 を解きます
問題文はこちら
実装したノートブックはこちら
セットアップ
Kino AOC をインストールします
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Kino AOC の使い方はこちらを参照
入力の取得
"Advent of Code Helper" スマートセルを追加し、 Day 14 の入力を取得します
私の答え
私の答えです。
折りたたんでおきます。
▶を押して開いてください。
回答
まずは入力例の小さいマップで解いてみましょう
入力を文字毎の配列に変換します
map =
"""
O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#....
"""
|> String.trim()
|> String.split("\n")
|> Enum.map(fn row ->
String.codepoints(row)
end)
実行結果
[
["O", ".", ".", ".", ".", "#", ".", ".", ".", "."],
["O", ".", "O", "O", "#", ".", ".", ".", ".", "#"],
[".", ".", ".", ".", ".", "#", "#", ".", ".", "."],
["O", "O", ".", "#", "O", ".", ".", ".", ".", "O"],
[".", "O", ".", ".", ".", ".", ".", "O", "#", "."],
["O", ".", "#", ".", ".", "O", ".", "#", ".", "#"],
[".", ".", "O", ".", ".", "#", "O", ".", ".", "O"],
[".", ".", ".", ".", ".", ".", ".", "O", ".", "."],
["#", ".", ".", ".", ".", "#", "#", "#", ".", "."],
["#", "O", "O", ".", ".", "#", ".", ".", ".", "."]
]
北にスライドさせる = 上方向にずらす、ということですが、列方向の操作はやりづらいので、縦横を入れ替えるために 90 度回転します
turned_map =
map
|> Enum.zip()
|> Enum.map(fn col ->
Tuple.to_list(col)
end)
|> Enum.reverse()
実行結果
[
[".", "#", ".", "O", ".", "#", "O", ".", ".", "."],
[".", ".", ".", ".", "#", ".", ".", ".", ".", "."],
[".", ".", ".", ".", "O", "#", ".", "O", "#", "."],
[".", ".", "#", ".", ".", ".", "O", ".", "#", "."],
["#", ".", "#", ".", ".", "O", "#", ".", "#", "#"],
[".", "#", ".", "O", ".", ".", ".", ".", ".", "."],
[".", "O", ".", "#", ".", ".", ".", ".", ".", "."],
[".", "O", ".", ".", ".", "#", "O", ".", ".", "O"],
[".", ".", ".", "O", "O", ".", ".", ".", ".", "O"],
["O", "O", ".", "O", ".", "O", ".", ".", "#", "#"]
]
この状態で左方向にずらせば北にずらしたのと同じことになります
岩をスライドさせるためのモジュールを定義します
defmodule Rock do
def turn(map) do
map
|> Enum.zip()
|> Enum.map(fn col ->
Tuple.to_list(col)
end)
|> Enum.reverse()
end
def slide(map) do
Enum.map(map, fn row ->
slide(row, length(row) - 1)
end)
end
def slide(row, 0), do: row
def slide(row, current_index) do
current_block = Enum.at(row, current_index)
next_block = Enum.at(row, current_index - 1)
case {current_block, next_block} do
{"O", "."} ->
row
|> List.update_at(current_index, fn _ -> "." end)
|> List.update_at(current_index - 1, fn _ -> "O" end)
|> slide(length(row) - 1)
_ ->
slide(row, current_index - 1)
end
end
def sum_load(map) do
map
|> Enum.map(fn row ->
row
|> Enum.reverse()
|> Enum.with_index(1)
|> Enum.map(fn {mark, index} ->
case mark do
"O" -> index
_ -> 0
end
end)
|> Enum.sum()
end)
|> Enum.sum()
end
end
ある位置の記号と右隣の記号が .O
であればスライドできるため、次の状態は O.
になります
それ以外の ..
, O.
, OO
, ##
, #O
, O#
, #.
, .#
では、どのパターンもスライドできないので変化しません
順次岩をスライドさせていくため、このスライドを右側から順に行い、スライドできたら(状態が変わったら)右端から再度スライドしていくことで、全ての岩をスライドさせることができます
実行例
-
.O#.OO.
初期状態 -
.O#O.O.
右端から順にチェックしていくと、2番目の岩を動かすことができた -
.O#OO..
改めて右端から順にチェックしていくと、3番目の岩を動かすことができた -
O.#OO..
改めて右端から順にチェックしていくと、1番目の岩を動かすことができた -
O.#OO..
最後に左端までチェックして何も動かすことができなかったのでスライド終了
これを全ての行に対して実行すれば良いことになります
実際に入力例でスライドさせてみましょう
map
|> Rock.turn()
|> Rock.slide()
実行結果
[
[".", "#", "O", ".", ".", "#", "O", ".", ".", "."],
[".", ".", ".", ".", "#", ".", ".", ".", ".", "."],
["O", ".", ".", ".", ".", "#", "O", ".", "#", "."],
[".", ".", "#", "O", ".", ".", ".", ".", "#", "."],
["#", ".", "#", "O", ".", ".", "#", ".", "#", "#"],
[".", "#", "O", ".", ".", ".", ".", ".", ".", "."],
["O", ".", ".", "#", ".", ".", ".", ".", ".", "."],
["O", ".", ".", ".", ".", "#", "O", "O", ".", "."],
["O", "O", "O", ".", ".", ".", ".", ".", ".", "."],
["O", "O", "O", "O", ".", ".", ".", ".", "#", "#"]
]
全ての丸い岩が可能な分だけ左に移動しました
左から順に重量の影響は 10, 9, 8, ..., 3, 2, 1 となっているため、左右反転させて岩の 列番号 + 1
を合計します
map
|> Rock.turn()
|> Rock.slide()
|> Rock.sum_load()
実行結果は 136
となり、問題文の結果と一致しました
同様の操作を実際の入力に対して実行しましょう
map =
puzzle_input
|> String.split("\n")
|> Enum.map(fn row ->
String.codepoints(row)
end)
map
|> Rock.turn()
|> Rock.slide()
|> Rock.sum_load()
まとめ
ややこしくはありますが、ここまでは考えやすかったです
Part 2 がもっとややこしくて考えないといけないことが多く、記事を分けました