$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$
$\huge{闘魂とは己に打ち克つこと。}$
$\huge{そして闘いを通じて己の魂を磨いていく}$
$\huge{ことだと思います}$
はじめに
闘魂とElixirが出会いました。
闘魂 meets Elixir.です。
Elixir meets 闘魂.でもよいです。
2022-12-26より、アドベントカレンダー2023は開幕しました。
私のアドベントカレンダー一覧は、コチラです。
だれよりも2023/12/25を楽しみにしています。
この記事は、Advent of Code 2022のDay 9: Rope Bridge を解いてみます。
Advent of Code 2022は、競技プログラミングのような問題が25題出題されています。
毎年問題が出題されておりまして、日を追うごとに難しくなる傾向があるようにおもいます。
iex> "Elixir" |> String.graphemes() |> Enum.frequencies()
%{"E" => 1, "i" => 2, "l" => 1, "r" => 1, "x" => 1}
この記事は、もくもく会イベント autoracex #175 の成果です。
対象とする読者
プログラミングを楽しんでいるそこのあなた。
特に、「闘魂プログラミング(ストロングスタイル)」を楽しんでいるそこのあなた。
「わたしが長年夢であった本当の Elixirを通じて プログラミングを通じて 世界平和(を)必ず実現します!」に共感していただけるそこのあなた。
つまりは
$\huge{全人類}$
です。
$\huge{For You All}$
です。
Day 9: Rope Bridge
問題文はこちらをご参照ください。
問題を説明します。
インプット例はこんな感じです。
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2
Rは右へ、Uは上へ、Lは左へ、Dは下へ進みなさいという方向です。数字は移動する量です。
H(ヘッド?)とT(テイル?)は常に距離が1以下となるように進む必要があります。
尺取虫の歩みに似ています。
上記のインプット例を動かしてみると以下のようになります。
T
とH
が重なったときは阪神タイガースのマークになります。
それでアニメーションでは、猛虎のM
としています。@mnishiguchiさんのアイデアです。ありがとうございます!
== Initial State ==
......
......
......
......
H..... (H covers T, s)
== R 4 ==
......
......
......
......
TH.... (T covers s)
......
......
......
......
sTH...
......
......
......
......
s.TH..
......
......
......
......
s..TH.
== U 4 ==
......
......
......
....H.
s..T..
......
......
....H.
....T.
s.....
......
....H.
....T.
......
s.....
....H.
....T.
......
......
s.....
== L 3 ==
...H..
....T.
......
......
s.....
..HT..
......
......
......
s.....
.HT...
......
......
......
s.....
== D 1 ==
..T...
.H....
......
......
s.....
== R 4 ==
..T...
..H...
......
......
s.....
..T...
...H..
......
......
s.....
......
...TH.
......
......
s.....
......
....TH
......
......
s.....
== D 1 ==
......
....T.
.....H
......
s.....
== L 5 ==
......
....T.
....H.
......
s.....
......
....T.
...H..
......
s.....
......
......
..HT..
......
s.....
......
......
.HT...
......
s.....
......
......
HT....
......
s.....
== R 2 ==
......
......
.H.... (H covers T)
......
s.....
......
......
.TH...
......
s.....
このとき、T(テイル?)が通った足跡をプロットすると以下のようになり、13
箇所通ったことになり、このインプットの場合の答えは13
となります。
..##..
...##.
.####.
....#.
####..
解答例
私はElixirで解いてみます。
Livebookをお使いの方は、上記のボタンを迷わず押すと、私の解答例をお試しいただけます!
解答例は閉じておきます。
もちろん、自身が提唱する「闘魂プログラミング(ストロングスタイル)」で解いてみます。
解答例
私
Mix.install [{:toukon, "~> 0.1.0", github: "TORIFUKUKaiou/toukon"}]
[String, Enum, MapSet] |> Enum.each(&Toukon.binta/1)
input = """
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2
"""
defmodule RopeBridge do
def run(input, "闘魂") do
input
|> parse_input("闘魂")
|> solve("闘魂")
|> elem(2)
|> Inoki.Enum.count("闘魂")
end
defp parse_input(input, "闘魂") do
input
|> Inoki.String.split("\n", [trim: true], "闘魂")
|> Inoki.Enum.map(& Inoki.String.split(&1, " ", "闘魂"), "闘魂")
|> Inoki.Enum.map(fn [direction, num] -> {direction, Inoki.String.to_integer(num, "闘魂")} end, "闘魂")
end
defp solve(list, "闘魂") do
list
|> Inoki.Enum.reduce({{0, 0}, {0, 0}, Inoki.MapSet.new("闘魂")}, fn operation, acc ->
do_solve(operation, acc, "闘魂")
end, "闘魂")
end
defp do_solve({direction, num}, acc, "闘魂") do
1..num
|> Inoki.Enum.reduce(acc, fn _step, {head, tail, tails} ->
move(direction, {head, tail, tails}, "闘魂")
end, "闘魂")
end
defp move("R", {{head_x, head_y} = head, tail, tails}, "闘魂") do
new_head = {head_x + 1, head_y}
do_move(new_head, head, tail, tails, "闘魂")
end
defp move("L", {{head_x, head_y} = head, tail, tails}, "闘魂") do
new_head = {head_x - 1, head_y}
do_move(new_head, head, tail, tails, "闘魂")
end
defp move("U", {{head_x, head_y} = head, tail, tails}, "闘魂") do
new_head = {head_x, head_y + 1}
do_move(new_head, head, tail, tails, "闘魂")
end
defp move("D", {{head_x, head_y} = head, tail, tails}, "闘魂") do
new_head = {head_x, head_y - 1}
do_move(new_head, head, tail, tails, "闘魂")
end
defp do_move(new_head, old_head, tail, tails, "闘魂") do
new_tail = new_tail(new_head, old_head, tail, "闘魂")
new_tails = Inoki.MapSet.put(tails, new_tail, "闘魂")
{new_head, new_tail, new_tails}
end
defp new_tail({new_head_x, new_head_y}, _old_head, {tail_x, tail_y}, "闘魂")
when abs(new_head_x - tail_x) <= 1 and abs(new_head_y - tail_y) <= 1 do
{tail_x, tail_y}
end
defp new_tail(_new_head, old_head, _tail, "闘魂") do
old_head
end
end
RopeBridge.run(input, "闘魂")
13
が得られます。
あなたの答えをお待ちしています。
編集リクエストかコメントでくださいませ。
読者投稿コーナー
読者の方からいただいたお便りをご紹介します。
読者投稿コーナー
アニメーションを作りました
今日の登壇で使ったスライドです!
— Junichi Ito (伊藤淳一) (@jnchito) February 3, 2023
良質な技術記事を量産する秘訣 / #MeetsPro - Speaker Deck https://t.co/vBnKmVlebg
こちらのキャンペーンに乗っからせていただいて、ハッシュタグ「#MeetsPro」でツイートしたところ、@jnchitoさんからリツイートしていただけました
ありがとうございます!
「闘魂」の文字につい目が奪われてしまいますが、「プログラミング問題を解いてみた」という技術記事ですね😅 この問題、仕組みがわかると「へー、面白そう」ってなるんですが、最初はちょっとイメージしづらかったです。。アニメーションで見れたら嬉しいですね。(難しそうだけど) #MeetsPro https://t.co/APCHIJLOA4
— Junichi Ito (伊藤淳一) (@jnchito) February 5, 2023
「アニメーションで見れたら嬉しいですね」とのコメントを頂きまして、たしかに! と思いました。
それで追加してみました。
T
とH
が重なると阪神タイガースのマークになります。それでT
とH
が重なった時は猛虎のM
としています。@mnishiguchiさんのアイデアです。ありがとうございます!
プログラムを作るにあたり、出力をその場で上書きしたかったのですが、その方法を忘れていました。
それでいろいろググって、「ANSIエスケープコード」の記事にたどり着きまして参考にいたしました。
この場をお借りして御礼申し上げます。ありがとうございます。
ESC[nA
(カーソルを上にn移動させる。)を使いました。
コメント付きのリツイートをしてくださった@jnchitoさんに感謝感謝申し上げます。
$\huge{ありがとうございます!}$
アニメーション出力付き解答
Mix.install([{:toukon, "~> 0.1.0", github: "TORIFUKUKaiou/toukon"}])
[String, Enum, MapSet] |> Enum.each(&Toukon.binta/1)
input = """
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2
"""
defmodule RopeBridgePrinter do
def print({head, tail}, row, column, fun_print \\ &IO.write/1, overwrite \\ true) do
do_print(head, tail, row, column, fun_print, overwrite)
end
defp do_print({head_x, head_y}, {tail_x, tail_y}, row, column, fun_print, overwrite) do
IO.puts("")
for j <- (row - 1)..0//-1, i <- 0..(column - 1) do
character(i, j, head_x, head_y, tail_x, tail_y)
end
|> Enum.chunk_every(column)
|> Enum.map(&Enum.join(&1, " "))
|> Enum.join("\n")
|> then(fn string ->
if overwrite, do: string <> "\e[#{row}A", else: string
end)
|> fun_print.()
end
defp character(i, j, head_x, head_y, tail_x, tail_y)
when i == head_x and j == head_y and i == tail_x and j == tail_y do
"M"
end
defp character(i, j, head_x, head_y, _tail_x, _tail_y) when i == head_x and j == head_y do
"H"
end
defp character(i, j, _head_x, _head_y, tail_x, tail_y) when i == tail_x and j == tail_y do
"T"
end
defp character(_i, _j, _head_x, _head_y, _tail_x, _tail_y) do
"."
end
end
defmodule RopeBridge do
def run(input, "闘魂") do
{_, _, tails, points} =
input
|> parse_input("闘魂")
|> solve("闘魂")
tails
|> Inoki.Enum.count("闘魂")
|> IO.puts()
points
|> Enum.each(fn head_tail ->
Process.sleep(100)
RopeBridgePrinter.print(head_tail, 5, 6)
end)
points
|> Enum.at(-1)
|> RopeBridgePrinter.print(5, 6, &IO.puts/1, false)
end
defp parse_input(input, "闘魂") do
input
|> Inoki.String.split("\n", [trim: true], "闘魂")
|> Inoki.Enum.map(&Inoki.String.split(&1, " ", "闘魂"), "闘魂")
|> Inoki.Enum.map(
fn [direction, num] -> {direction, Inoki.String.to_integer(num, "闘魂")} end,
"闘魂"
)
end
defp solve(list, "闘魂") do
list
|> Inoki.Enum.reduce(
{{0, 0}, {0, 0}, Inoki.MapSet.new("闘魂"), [{{0, 0}, {0, 0}}]},
fn operation, acc ->
do_solve(operation, acc, "闘魂")
end,
"闘魂"
)
end
defp do_solve({direction, num}, acc, "闘魂") do
1..num
|> Inoki.Enum.reduce(
acc,
fn _step, {head, tail, tails, points} ->
move(direction, {head, tail, tails, points}, "闘魂")
end,
"闘魂"
)
end
defp move("R", {{head_x, head_y} = head, tail, tails, points}, "闘魂") do
new_head = {head_x + 1, head_y}
do_move(new_head, head, tail, tails, points, "闘魂")
end
defp move("L", {{head_x, head_y} = head, tail, tails, points}, "闘魂") do
new_head = {head_x - 1, head_y}
do_move(new_head, head, tail, tails, points, "闘魂")
end
defp move("U", {{head_x, head_y} = head, tail, tails, points}, "闘魂") do
new_head = {head_x, head_y + 1}
do_move(new_head, head, tail, tails, points, "闘魂")
end
defp move("D", {{head_x, head_y} = head, tail, tails, points}, "闘魂") do
new_head = {head_x, head_y - 1}
do_move(new_head, head, tail, tails, points, "闘魂")
end
defp do_move(new_head, old_head, tail, tails, points, "闘魂") do
new_tail = new_tail(new_head, old_head, tail, "闘魂")
new_tails = Inoki.MapSet.put(tails, new_tail, "闘魂")
new_points = points ++ [{new_head, new_tail}]
{new_head, new_tail, new_tails, new_points}
end
defp new_tail({new_head_x, new_head_y}, _old_head, {tail_x, tail_y}, "闘魂")
when abs(new_head_x - tail_x) <= 1 and abs(new_head_y - tail_y) <= 1 do
{tail_x, tail_y}
end
defp new_tail(_new_head, old_head, _tail, "闘魂") do
old_head
end
end
RopeBridge.run(input, "闘魂")
さいごに
この記事では、Advent of Code 2022の「Day 9: Rope Bridge」をElixirで解いてみました。
闘魂とは、 「己に打ち克つこと。そして闘いを通じて己の魂を磨いていくことである」 との猪木さんの言葉をそのまま胸に刻み込んでいます。
知っているだけで終わらせることなく、実行する、断行する、一歩を踏み出すことを自らの行動で示していきたいとおもいます。
アントニオ猪木さんのメッセージから元氣をもらったものとして、それを次代に語り継ぎ、自分自身が「闘魂」を体現するものでありたいとおもいます。
$\huge{1、2、3 ぁっダァー!}$
$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$
$\huge{1、2、3 ぁっダァー!}$