LoginSignup
6
3

More than 1 year has passed since last update.

闘魂Elixir ── Advent of Code 2022 (Day 9: Rope Bridge) をElixirで楽しむ!

Last updated at Posted at 2023-02-04

$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$

$\huge{闘魂とは己に打ち克つこと。}$
$\huge{そして闘いを通じて己の魂を磨いていく}$
$\huge{ことだと思います}$

Run in Livebook

はじめに

闘魂Elixirが出会いました。
闘魂 meets Elixir.です。
Elixir meets 闘魂.でもよいです。

2022-12-26より、アドベントカレンダー2023は開幕しました。

私のアドベントカレンダー一覧は、コチラです。

だれよりも2023/12/25を楽しみにしています。

この記事は、Advent of Code 2022Day 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以下となるように進む必要があります。
尺取虫の歩みに似ています。

上記のインプット例を動かしてみると以下のようになります。
THが重なったときは阪神タイガースのマークになります。
それでアニメーションでは、猛虎Mとしています。@mnishiguchiさんのアイデアです。ありがとうございます!

output.gif

== 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で解いてみます。
Run in Livebook
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 が得られます。

あなたの答えをお待ちしています。
編集リクエストかコメントでくださいませ。

読者投稿コーナー

読者の方からいただいたお便りをご紹介します。

読者投稿コーナー

アニメーションを作りました

output.gif

こちらのキャンペーン:interrobang::interrobang::interrobang:に乗っからせていただいて、ハッシュタグ「#MeetsPro」でツイートしたところ、@jnchitoさんからリツイートしていただけました:tada::tada::tada:
ありがとうございます!

「アニメーションで見れたら嬉しいですね」とのコメントを頂きまして、たしかに! と思いました。
それで追加してみました。
THが重なると阪神タイガースのマークになります。それでTHが重なった時は猛虎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 ぁっダァー!}$

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3