今回は、クラスを使って応用問題に挑戦していく!
クラス名にしたラビリンス(Labyrinth)がかっこよすぎて、コードを書くのが楽しすぎた(子どもかw)
というか、自力でBクラスの問題を解けるようになってきたの成長を感じる😎
問題概要
あなたは出口のない迷路(ラビリンス)に迷い込んだ。
脱出するには、迷路の「地点」を指示に従って移動しながら、その地点に置かれたアルファベットをつなげて「呪文」を作る必要がある。。
迷路の構成
- 迷路には N 個の地点がある(地点は番号で管理)
- 各地点からは、他の地点へ向かう 2本の一方通行の道がある
入力内容
- 1行目
- 地点の数 N
- 移動の回数 K
- 移動を開始する地点の番号 S
- 続くN行(地点情報)
- i番目の行には、地点 i に置かれたアルファベット a_i
- 1本目の道がつながる地点の番号 r1_i
- 2本目の道がつながる地点の番号 r2_i
- 続くK行(移動指示)
- i回目の移動で選ぶ道の番号 M_i(1か2)
求めるもの
- 最初の地点 S からスタートし、与えられた指示に従って地点を移動する。
- 移動で訪れたすべての地点(開始地点も含む)に置かれたアルファベットを順に繋げた文字列を作る。
- その文字列(呪文)を出力する。
✅ OK例:シンプル
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (line) => {
lines.push(line);
});
rl.on('close', () => {
// 入力の解析
const [N, K, S] = lines[0].split(' ').map(Number);
// 迷路の地点クラス
class Point {
constructor(char, first, second) {
this.char = char;
this.first = Number(first);
this.second = Number(second);
}
}
// 地点情報を配列に格納
const points = lines.slice(1, N + 1).map(line => {
const [char, first, second] = line.split(' ');
return new Point(char, first, second);
});
// 移動指示の配列 (数字1 or 2)
const moves = lines.slice(N + 1).map(Number);
// 移動開始地点のインデックスに変換 (0始まりにする)
let current = S - 1;
// 呪文の文字列として訪れた地点の文字を格納する配列
const spell = [];
spell.push(points[current].char); // 最初の地点の文字も追加
// 移動回数分ループ
for (const move of moves) {
// moveは1か2なので対応する道をたどる
current = (move === 1) ? points[current].first - 1 : points[current].second - 1;
spell.push(points[current].char);
}
// 呪文を結合して出力
console.log(spell.join(''));
});
🔍ポイント解説
-
クラス
Pointを用いて地点情報をわかりやすく管理 -
配列
pointsに地点の情報を格納。地点番号は 1 始まりなので、アクセスは 0 始まりに調整 -
spell配列に最初の地点の文字をまず格納し、その後移動先の文字も追加していく -
移動の指示
movesは1か2なので、firstかsecondの経路を選択し、現在地を更新 -
最後に
spellの文字を連結して呪文として出力
✨OK例:ラビリンス
class Labyrinth {
static spell = [];
static points = [];
static current = 0;
constructor(str, first, second){
this.str = str;
this.first = Number(first);
this.second = Number(second);
}
static start(point){
this.current = point - 1;
this.spell.push(this.points[this.current].str);
}
static move(choice){
const point = this.points[this.current][choice] - 1;
this.spell.push(this.points[point].str);
this.current = point;
}
static cast() {
console.log(this.spell.join(''));
}
}
const rl = require('readline').createInterface({input:process.stdin});
const lines = [];
rl.on('line', (input) => {
lines.push(input);
});
rl.on('close', () => {
const [N, K, S] = lines[0].split(' ').map(Number);
lines
.slice(1, N+1)
.forEach(p => {
const [str, first, second] = p.split(' ');
Labyrinth.points.push(new Labyrinth(str, first, second));
});
Labyrinth.start(S);
const choices = lines.slice(N+1).map(Number);
for(const choice of choices){
if(choice === 1){
Labyrinth.move('first');
}
else if(choice === 2){
Labyrinth.move('second');
}
}
Labyrinth.cast();
});
🔍ポイント解説:
🔹 ブラケット記法
this.points[this.current][choice]
choice が ‘first’ または ‘second’ という文字列、
this.points[this.current] は現在の地点オブジェクト、
そこに . で直接アクセスする代わりに、[choice] と書くことで動的にプロパティを指定できる。
例えば、choice = ‘first’ なら this.points[this.current].first と同じ意味になる。
これは変数でプロパティ名を指定したいときに使うテクニック。
🔹 static プロパティとメソッドの利用
-
spellやpoints、currentは全てクラス全体で共有する情報なのでstaticとして定義。 -
メソッドも
staticにしているので、インスタンス化しなくてもLabyrinth.start()などとして呼び出せる。 -
これで状態管理が一か所にまとまり、扱いやすくなっている。
🔹 0始まりのインデックスに変換している理由
- 入力の地点番号は1から始まるが、配列のインデックスは 0 から。
- そのため -1 して変換している。
🗒️まとめ
- ブラケット記法でプロパティを動的に指定
- 2つ目のコードの方が、3つ目4つ目の道を拡張したい時に容易
class Labyrinth {
// 呪文をつなげていく配列(クラス全体で共有)
static spell = [];
// 迷路の地点オブジェクトを格納する配列(クラス全体で共有)
static points = [];
// 現在いる地点のインデックス(0始まり、クラス全体で共有)
static current = 0;
// 1地点分の情報を持つインスタンスの初期化
constructor(str, first, second){
this.str = str; // 地点に置かれたアルファベット(呪文の1文字)
this.first = Number(first); // 1つ目の道の行き先(地点番号)
this.second = Number(second); // 2つ目の道の行き先(地点番号)
}
// ゲーム開始時の初期処理:現在地設定と呪文に最初の文字を入れる
static start(point){
this.current = point - 1; // 与えられた地点番号を0始まりのインデックスに変換
this.spell.push(this.points[this.current].str); // 最初の地点の文字を呪文に追加
}
// 移動処理:choice は 'first' か 'second' の文字列でどちらの道を選んだかを表す
static move(choice){
// ここがポイント!ブラケット記法を使ってプロパティを動的にアクセス
// this.points[this.current] は現在の地点オブジェクト
// [choice] は 'first' または 'second' を動的に指定してその道の行き先番号を取得
// -1 して0始まりのインデックスに変換
const point = this.points[this.current][choice] - 1;
// 移動先の地点の文字を呪文に追加
this.spell.push(this.points[point].str);
// 現在地を移動先に更新
this.current = point;
}
// 呪文をつなげた文字列を出力
static cast() {
console.log(this.spell.join(''));
}
}
const rl = require('readline').createInterface({input:process.stdin});
const lines = [];
rl.on('line', (input) => {
lines.push(input);
});
rl.on('close', () => {
// 入力の1行目から N=地点数, K=移動回数, S=開始地点 を取得
const [N, K, S] = lines[0].split(' ').map(Number);
// 2行目からN+1行目までの地点情報をLabyrinth.pointsに追加
lines
.slice(1, N+1)
.forEach(p => {
const [str, first, second] = p.split(' ');
Labyrinth.points.push(new Labyrinth(str, first, second));
});
// ゲーム開始地点のセット
Labyrinth.start(S);
// 移動指示を配列にしてループ処理
const choices = lines.slice(N+1).map(Number);
for(const choice of choices){
if(choice === 1){
Labyrinth.move('first'); // 1番目の道を選択
}
else if(choice === 2){
Labyrinth.move('second'); // 2番目の道を選択
}
}
// 呪文を出力
Labyrinth.cast();
});