· 4分で読了
2022 Advent Of Code(day9) – Rope Bridge
この記事は中国語から自動翻訳されたものです。翻訳によりニュアンスが失われている場合があります。
最近は問題を見ると、まず面倒くさそうかどうかを観察してから書き始めるかを決めるようになってしまっている。こういう悪い癖は直さないといけないなQQ。
Part1
問題の説明を読んだ後、頭を使う必要があったのは尾の移動方法と隣接判定をどう決めるかという点だった。僕のほうでは単純な2Dベクトルを直接書いて実装した。
function isNeighbor(a, b) {
return (
(a.x === b.x ||
a.x - 1 === b.x ||
a.x + 1 === b.x) &&
(a.y === b.y || a.y - 1 === b.y || a.y + 1 === b.y)
);
}
注:書き終えた後に気づいたが、実際には2点間の距離を直接求めれば、1つずつ比較する必要はなかった。
入力の処理
入力は比較的シンプルで、基本的には各方向の移動をベクトルにマッピングするだけだ:
U: (0, 1)
D: (0, -1)
R: (1, 0)
L: (-1, 0)
尾の移動ロジック
ロープの頭が移動した際、尾が隣接していなければ尾も移動する。これは内積で角度を求めることで、尾がどう移動すべきかを計算できる。x軸の単位ベクトルとy軸の単位ベクトルとの角度を求めれば、尾をどう移動させるべきかがわかる。
if (!this.head.isNeighbor(this.tail)) {
const vec = this.head.vector(this.tail);
const degree = vec.product(new Point(1, 0)) / vec.length();
const degree2 = vec.product(new Point(0, 1)) / vec.length();
if (degree === 0) {
if (degree2 > 0) {
this.tail.add(0, 1);
} else {
this.tail.add(0, -1);
}
} else if (Math.abs(degree) === 1) {
if (degree === 1) {
this.tail.add(1, 0);
} else {
this.tail.add(-1, 0);
}
} else {
this.tail.add(degree > 0 ? 1 : -1, degree2 > 0 ? 1 : -1);
}
}
問題は尾が通過した地点を問うているので、Set を使えばそのまま完了できる。完全なコード実装は以下の通り:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
isNeighbor(point) {
const vec = new Point(point.x - this.x, point.y - this.y);
return vec.length() <= 1;
}
add(x, y) {
this.x += x;
this.y += y;
}
vector(point) {
return new Point(this.x - point.x, this.y - point.y);
}
length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
product(point) {
return point.x * this.x + point.y * this.y;
}
}
class Rope {
constructor(isFirst = false) {
this.isFirst = isFirst;
this.head = new Point(0, 0);
this.tail = new Point(0, 0);
}
move(direction) {
if (this.isFirst) {
switch (direction) {
case "U":
this.head.add(0, 1);
break;
case "D":
this.head.add(0, -1);
break;
case "L":
this.head.add(-1, 0);
break;
case "R":
this.head.add(1, 0);
break;
}
}
if (!this.head.isNeighbor(this.tail)) {
// calculate vector
// move to direction
const vec = this.head.vector(this.tail);
const degree = vec.product(new Point(1, 0)) / vec.length();
const degree2 = vec.product(new Point(0, 1)) / vec.length();
if (degree === 0) {
// horizontal, vertial
if (degree2 > 0) {
this.tail.add(0, 1);
} else {
this.tail.add(0, -1);
}
} else if (Math.abs(degree) === 1) {
if (degree === 1) {
this.tail.add(1, 0);
} else {
this.tail.add(-1, 0);
}
} else {
this.tail.add(degree > 0 ? 1 : -1, degree2 > 0 ? 1 : -1);
}
}
}
}
もしかしてイースターエッグが隠されていて、尾が引き伸ばされてできる形状が何かの絵になっているのではないかと思い、ロープの座標もプロットしてみた。

だが、特に変わった様子はなかったようだ。
Part2
元々は頭と尾だけを考慮すればよかったが、今度はロープの長さが10になったため、移動の仕方も少し異なる。とはいえ全体のロジックはほぼ同じで、元のRopeの頭と尾を繋ぎ合わせれば実現できる。
const ropes = [
new Rope(true),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
];
const set = new Set();
direction.forEach((dir, ii) => {
const [d, step] = dir.split(" ");
for (let i = 0; i < Number(step); i++) {
ropes.forEach((rope, j) => {
if (j === 0) {
rope.move(d);
} else {
rope.head = ropes[j - 1].tail;
rope.move(d);
}
});
set.add(
`${ropes[ropes.length - 1].tail.x} ${ropes[ropes.length - 1].tail.y}`
);
}
}); 関連記事
- 測定が目標になるとき:窓税からPull Request数まで かつて僕は小さなツールを自作し、四半期で自分がどれだけPRに貢献したか、レビューコメントをどれだけ残したか、チケットをどれだけ消化したかを集計して、上司にアウトプットを証明しようとしたことがある。上司は淡々と、評価はアウトプットだけで見るものではないと言った。数年後、僕はようやく理解した――測定が目標になるとき、それはもはや良い測定ではなくなるのだ。英国の窓税、ハノイのネズミ駆除の報奨金から、現代のPR数による開発者評価に至るまで、そのメカニズムはまったく同じだ。
- Cloudflare Images を画像ストレージ・変換ソリューションとして使う ウェブページに画像を1枚置くのはフロントエンドにとって最も簡単なことだが、リサイズや各種フォーマットの生成、さらにはトラフィックの負荷に耐えることまで完璧にやろうとすると、実際には一つの包括的なソリューションが必要になる。僕はその後、すべて Cloudflare Images に任せるようになり、オリジナル画像1枚だけを渡すようにしている。
- もう AWS Access Key を使うのはやめよう Access Key は AWS において見落とされがちなセキュリティリスクだ。OIDC と IAM Role を組み合わせることで、GitHub Actions にシークレットを一切保持させることなく、安全に AWS リソースを操作できるようにする。
- データベース主キー:AUTO_INCREMENT、UUID、そしてUUIDv7 バックエンド開発で度々直面する主キーの決定。auto incrementを使うべきか、それともUUIDか?衝突への懸念は?UUIDv7とcreated_at + インデックスの性能差はどれほどか?実際に2,000万件のデータで検証したベンチマークと設計上の意思決定を解説する。