今回は paiza の「3Dプリンタ」の問題に挑戦!
問題概要
〇 入力されるのは立体のデータ
- 立体は **1×1×1 の立方体(セル)**の集合で表現される。
- 座標系は (x, y, z) (奥行き・横幅・高さ)。
- データは X, Y, Z のサイズで与えられる。
〇 入力
- 1行目:
X Y Z
- 以降:各 z 層について
-
X
行の文字列(それぞれ長さY
) - 各文字は '
#
'(セルあり)または '.
'(セルなし) - 層と層の間には "
--
" の区切りが入る。
-
〇 出力
- 立体を x軸正方向(正面)から見た図 を出力する。
- 出力は
Z
行 ×Y
列 の文字列。 - 各セル (y,z) について、どこかの奥行き x に '
#
' があれば見えるので '#
'、そうでなければ '.
'。
〇 条件
- 1 ≦ X, Y, Z ≦ 50
入力例:
3 26 5
............#.............
......#..#.....#####......
#.....................#..#
--
............#.............
......#..#......#.........
#.....................#..#
--
............#.............
......####.......#........
####..................####
--
............#.............
......#..#........#.......
#..#..................#..#
--
............#.............
......####.....#####......
####..................####
--
出力例:
####..####..#..#####..####
#..#..#..#..#.....#...#..#
####..####..#....#....####
#.....#..#..#...#.....#..#
#.....#..#..#..#####..#..#
✅OK例:二次元配列
const rl = require('readline').createInterface({ input:process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const [X, Y, Z] = lines[0].split(' ').map(Number);
const result = Array.from({ length: Z }, () => Array(Y).fill('.'));
for (let z = 0; z < Z; z++) {
for (let x = 0; x < X; x++) {
const row = lines[1 + z * (X + 1) + x]; // この行が層z, 行x
for (let y = 0; y < Y; y++) {
if (row[y] === '#') {
result[z][y] = '#'; // (y,z) にセルが見える
}
}
}
}
// z=0 が上なので、出力は逆順
for (let z = Z - 1; z >= 0; z--) {
console.log(result[z].join(''));
}
// result.reverse().forEach(r => console.log(r.join('')));
});
- 入力処理
- 1行目で
X
,Y
,Z
を読み込む-
X
= 奥行き(x方向のセル数) -
Y
= 横幅(y方向のセル数) -
Z
= 高さ(z方向のセル数)
-
- 残りの行は3D立体データ。各層(zごと)に
X
行あり、層の間に "--
" が入っている。
- 1行目で
- 出力用の配列
result
を用意- サイズは
Z
行 ×Y
列。 - 初期値はすべて '
.
'。 -
result[z][y]
が「高さz・横yの見た目セル」を表す。
- サイズは
- データを走査
- 各層
z
について:- 層
z
の最初の行はlines[1 + z * (X + 1)]
。
→(X+1)
を掛けるのは "--
" の区切り行を含むため。 - 各
x
行について文字列を読み取る。 - 行内の各
y
を走査し、'#
' があればその(y,z)
の位置が正面から見えるのでresult[z][y] = '#'
に更新。
- 層
- 各層
- 出力
-
z=0
が入力では下の層だが、出力では上に表示する必要があるため、Z-1
から0
へ逆順でresult
を出力。 - 各行は
result[z].join('')
で文字列化して出力。
-
✨OK例:三次元配列
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const [X, Y, Z] = lines[0].split(' ').map(Number);
// 3次元配列 [x][y][z]
const cube = Array.from({ length: X }, () =>
Array.from({ length: Y }, () => Array(Z).fill('.'))
);
let lineIndex = 1; // 2行目以降からデータ開始
for (let z = 0; z < Z; z++) {
for (let x = 0; x < X; x++) {
const row = lines[lineIndex++].trim();
for (let y = 0; y < Y; y++) {
cube[x][y][z] = row[y];
}
}
lineIndex++; // "--" を飛ばす
}
// 出力:z=1 が上、z=Z が下 → 逆順で出す
for (let z = Z - 1; z >= 0; z--) {
let row = "";
for (let y = 0; y < Y; y++) {
let visible = '.';
for (let x = 0; x < X; x++) {
if (cube[x][y][z] === '#') {
visible = '#';
break;
}
}
row += visible;
}
console.log(row);
}
});
- 入力読み込み準備
- 1行目で
X Y Z
を取得。-
X
= 奥行き -
Y
= 横幅 -
Z
= 高さ
-
- それ以降の行を
lines
に保持。
- 1行目で
- 3D配列を作成
-
cube[x][y][z]
の形でセルを管理。 - 初期値は全部 '
.
'。 -
Array.from
を使ってX
×Y
×Z
の配列を生成。
-
- 入力データを
cube
に格納-
lineIndex = 1
(データ開始位置)。 - 各層
z
ごとに:- その層には
X
行がある。 - 各行
row
を取り出し、文字ごとにcube[x][y][z]
に代入。-
row[y]
が '#
' → セルあり - '
.
' → セルなし
-
- その層には
- 1層の処理が終わるたびに "
--
" 区切り行をスキップ。
-
- 正面図を作成
- 出力は 高さ
Z
× 横幅Y
。 - ただし「
z=1
が上、z=Z
が下」なのでz
を逆順に走査。 - 各 (
z,y
) について:- 奥行き方向に x=0..X-1 をチェック。
- どこかで '
#
' が見えたら、その位置は '#
' として確定。 - 最初に見えた時点でループを抜ける。
- 出力は 高さ
- 出力
- 1行ごとに文字列を組み立て、
console.log
で出力。 -
z
の逆順処理により「正面図が上から下へ」正しく描かれる。
- 1行ごとに文字列を組み立て、
📝まとめ
- 入力の構造を理解するのが最重要
- X(奥行き)行が1つの層(高さz)を構成し、層の間は "--" で区切られている。
- 出力は「投影図」
- 「正面から見たときに見えるセル」を判定する問題。
- (y,z) の視点で奥行き方向 x を走査して、最初に '#' が出たら可視。
- 実装の2パターン
- 二次元配列に直接記録
-
result[z][y]
に「見えるかどうか」をその場で更新。 - メモリ効率がよく、シンプル。
-
- 三次元配列に立体を保持 → 投影処理
-
cube[x][y][z]
に格納し、その後で可視判定。 - 少し冗長だが「立体を完全に持つ」ので直感的で分かりやすい。
-
- 二次元配列に直接記録
- 出力の注意点
- 入力上の z=0 が底だが、出力では上から z=0 を表示する必要がある。
- そのため 出力は z を逆順で処理するのがポイント。