※過去記事はこちら。AlteryxユーザーのためのAdvent of Codeの始め方、1日目、2日目、3日目、4日目、5日目、6日目、7日目、8日目
9日目です。
タイトルは、「Rope Bridge」。ロープの橋、ですね。
少し加筆しました(2022/12/10 AM)
ここまで来ると難易度が高くて当たり前になってきます。とはいえ、まだ解けます!
入力データとしては、以下のようになっています。
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2
これは、最初のアルファベットは、R=Right、L=Left、U=Up、D=Downということで、動く方向です。数字は距離です。
ですので、最初のR 4は、右に4動く、という意味になっています。
で、単にこの通り動かしたポジションを求めるだけなら秒殺なのですが、これはロープとのことで、ロープの終端が通ったポジション数を求める、というのが今回の課題です。ロープの終端は、先端とは違う動きをするようで、X、Y座標のいずれかが同じであれば、ロープの先端と同じ動きをするのですが、異なる場合は斜めに動く、ということのようです(ここがポイント)。そして、ロープの先頭が隣り合っている場合は、ロープの終端は動きません(ここもポイント)。
つまり、このような動きになります。HはHeadでロープの先頭。TはTailでロープの終端です。以下は縦軸、横軸が同じ場合。
..... ..... .....
.TH.. -> .T.H. -> ..TH.
..... ..... .....
... ... ...
.T. .T. ...
.H. -> ... -> .T.
... .H. .H.
... ... ...
軸がずれている場合は、必ず斜めに動きます。H(もしくは一つ前のロープの場所)に近づくように斜めに動きます。
..... ..... .....
..... ..H.. ..H..
..H.. -> ..... -> ..T..
.T... .T... .....
..... ..... .....
..... ..... .....
..... ..... .....
..H.. -> ...H. -> ..TH.
.T... .T... .....
..... ..... .....
基本的にはIF条件を工夫していけば良い、ということになります。
ちなみに、Part2はロープの長さが10倍になります。
・ネ
・
・タ
・
・バ
・
・レ
解いてみる
今回も時間の都合上、ワークフローのみ掲載します(後で時間があるときに詳細記載します)。
マクロルートも可能なのですが、ロープの終端の処理が簡単になる気がしなかったので、マクロではないルートにしました。
基本的な考え方として、まず頭の動きを作ります。これは簡単ですね・・・。行生成ツールで行を増やすだけです。
その後、頭の動きに沿って動くお尻の部分を作ります。ここでは反復マクロを使うか、複数行フォーミュラを使います。基本的にはIF文で動きを再現していきます。ただ、複数行フォーミュラを使う場合は注意がありまして、後ほど解説します。
Part2はPart1で作ったお尻の動きをそのまま8つ追加すれば完了です。
今回はマクロを使っていませんが、Part2の方はコピペ多様しているので、その部分はマクロ化することができます(反復マクロ)。
マクロを使うと、以下のようになります。
マクロ:Part2用
ちなみに、今回複数行フォーミュラを多用していますが、1つの複数行フォーミュラでX、Y座標を同時に書き換える技を使っています。とはいっても、テキストでデータを格納しつつ、計算などは分割してやるという複雑な方法を取っています。
例えば、「1 1」という感じでテキストで格納しておき、何か判定する際は、Getword関数やREGEX_REPLACE関数などで切り出して計算する、ということが可能です。計算式が複雑になってしまうので、全くおすすめできませんが、どうしても、といった場合はこういうやり方も可能です。
個人的にはGetWord関数だと空白区切りで格納するだけでポジションを簡単に指定できて文字数も短いのでわかりやすいです。REGEX_REPLACEは関数名がそもそも長いのと、パラメータも長くなるので使っていません。SubString関数でもできるんでしょうが、切り出す場所の指定が悩ましいので使っていません。
例えば、GetWord関数を使った切り出し方を見てみましょう。
123 110
という入力が与えられたとします(項目名はField1としておきます)。
前半の「123」を切り出す場合は、
Getword([Field1],0)
となります。
後半の「110」を切り出す場合は、
Getword([Field1],1)
となります。これを数値として計算したい場合は、ToNumber関数で囲めばオッケーです。
ちなみに、今回複数行フォーミュラで使ってる数式です。
IF [Row-1:Tail]="" THEN "0 0" //初期化用
ELSEIF Getword([Row-1:Tail],0)=ToString([PosX]) AND Getword([Row-1:Tail],1)=ToString([PosY]) THEN //same x,y(X,Yが同じ場合)
[Row-1:Tail]
ELSEIF Getword([Row-1:Tail],0)=ToString([PosX]) AND Getword([Row-1:Tail],1)!=ToString([PosY]) THEN //same x (X軸が同じ場合)
IF (ABS([PosX]-ToNumber(Getword([Row-1:Tail],0)))+ABS([PosY]-ToNumber(Getword([Row-1:Tail],1)))<=1) THEN // 頭と隣接している場合(重なりもOK)
[Row-1:Tail]
ELSE // 頭と離れる場合は斜め移動
IF [PosY]>ToNumber(Getword([Row-1:Tail],1)) THEN
ToString([PosX]) + " " + ToString(ToNumber(Getword([Row-1:Tail],1))+1)
ELSE
ToString([PosX]) + " " + ToString(ToNumber(Getword([Row-1:Tail],1))-1)
ENDIF
ENDIF
ELSEIF Getword([Row-1:Tail],0)!=ToString([PosX]) AND Getword([Row-1:Tail],1)=ToString([PosY]) THEN //same y (Y軸が同じ場合)
IF (ABS([PosX]-ToNumber(Getword([Row-1:Tail],0)))+ABS([PosY]-ToNumber(Getword([Row-1:Tail],1)))<=1) THEN // 頭と隣接している場合(重なりもOK)
[Row-1:Tail]
ELSE // 頭と離れる場合は斜め移動
IF [PosX]>ToNumber(Getword([Row-1:Tail],0)) THEN
ToString(ToNumber(Getword([Row-1:Tail],0))+1) + " " +ToString([PosY])
ELSE
ToString(ToNumber(Getword([Row-1:Tail],0))-1) + " " +ToString([PosY])
ENDIF
ENDIF
ELSE // 軸が異なる場合
IF (ABS([PosX]-ToNumber(Getword([Row-1:Tail],0)))+ABS([PosY]-ToNumber(Getword([Row-1:Tail],1)))<=2) THEN // 頭が隣接の場合は動かない
[Row-1:Tail]
ELSE // 頭が離れる場合
IF ([PosX]>ToNumber(Getword([Row-1:Tail],0))) AND ([PosY]>ToNumber(Getword([Row-1:Tail],1))) THEN
ToString(ToNumber(Getword([Row-1:Tail],0))+1)+" "+ToString(ToNumber(Getword([Row-1:Tail],1))+1) // UpRight
ELSEIF ([PosX]<ToNumber(Getword([Row-1:Tail],0))) AND ([PosY]<ToNumber(Getword([Row-1:Tail],1))) THEN
ToString(ToNumber(Getword([Row-1:Tail],0))-1)+" "+ToString(ToNumber(Getword([Row-1:Tail],1))-1) // DownLeft
ELSEIF ([PosX]>ToNumber(Getword([Row-1:Tail],0))) AND ([PosY]<ToNumber(Getword([Row-1:Tail],1))) THEN
ToString(ToNumber(Getword([Row-1:Tail],0))+1)+" "+ToString(ToNumber(Getword([Row-1:Tail],1))-1) // DownRight
ELSEIF ([PosX]<ToNumber(Getword([Row-1:Tail],0))) AND ([PosY]>ToNumber(Getword([Row-1:Tail],1))) THEN
ToString(ToNumber(Getword([Row-1:Tail],0))-1)+" "+ToString(ToNumber(Getword([Row-1:Tail],1))+1) // UpLeft
ELSE
""
ENDIF
ENDIF
ENDIF
実際の複数行フォーミュラツールの設定は以下のような形でした。
正直な話、これを複数行フォーミュラの小さい画面で書くのは結構な苦行ですね・・・(と思ったら、フォーミュラツールと同じで、Ctrl+マウスホイールでフォントサイズ変えられますね・・・)。
まとめ
- 9日目も、Advent of Codeではしばしばあるタイプの問題です。マクロで解くと結構PCスペックがネックになるようです。
- 最速の人で63分。それ以上は4時間ということなので、とりあえず解けたオッケーっていう世界になっています。あとはずっとこんな感じですかね・・・。
- 個人タイム:もうこれだけで計測できないので、順位だけ・・・Private Leaderboardで10位でした。
コメント