こんにちは、tomosukeです。Qiita投稿は初めてです。
以前から他で技術投稿などを趣味でしてきましたが、この度paizaとQiitaがコラボ企画を立ち上げてくれたので、それに乗っかる形で幾つか問題を解きつつ、考え方を頑張ってかみ砕いて説明してみたいと思います。
なお筆者はpaizaランクAです。
コラボ企画へのリンクはこちら
今回のお題は「3Dプリンタ」
概要
3Dプリンタで印刷するための3次元データが入ってきます。全部印刷したとき、正面(手前)から見た風景を答えてください、と言ったような内容です。(うむむ、これ伝わるのか?)
以下の図が提示されています。図はすべてpaizaの問題文から引用しています。
となったときに、これを正面(手前)から見たらどうなりますか?と言う問題。
このように見たときの、図で言うと「正面から見た図2」の結果をプログラムで出力してください、という問題です。
考えるポイントは?
先にお断りしておくと、最適解は示しません。ランクB問題は処理を最適化せずとも良いレベルなので、多少冗長でもいいと思っています。またpaizaが提示した「入力例とそれに対する出力期待値」でしか動作検証していないこともご了承ください。
1.入力データがどんな順番で来るかを理解する
ここを勘違いして進めていくと後で困るので、しっかり見ていきましょう。以下に問題文から引用します。
一番上にある行の「3 3 3」は動作条件になるパラメータです。プログラムで読み取り、X=3, Y=3, Z=3 のように内部で代入して使いましょう。
2行目以降からは、3Dプリンタで空間を埋めるデータの有無が表現されています。
- 「#」はデータがある
- 「.」はデータがない、つまり隙間があると言うこと
再び図を登場させますと
最初の入力データは「入力例1 の2〜4行目」にあたりますが、次のようになっています。
###
##.
#..
この2つ(再度登場させた図と上に書いた#などが書かれた3行)を見比べてください。図では
- 一番奥にはブロックが3つある
- 奥から2番目は、左から2つブロックがあり、右端は空いている
- 一番手前は左端だけブロックがあり、右2つは空いている
まずはここを「うん、なるほど」となるまで理解を深めてください。そこまで理解して進めないと問題を適切に解けなくなります。必要に応じて紙に書くなどしてイメージで理解するのがお勧めです。
2.作戦を練る
問題全体を見ると、入力データが3次元なので「3次元配列が必要なのでは」と考えそうですね。しかしここでは、2次元配列を利用します。
2次元配列でいいと考える理由ですが、それは
出力する答えがY−Z面だから
です。上に掲載した図を再度登場させます。
正面から見た図2を見てください。平面ですよね。平面ということは、プログラムによる処理の結果は2次元配列に入れれば良い、と考えられます。
そしてもう1つ大事なのは、3次元の入力データを計算して2次元配列に入れて上手くいくのか、ということ。筆者はこう考えました。
データが奥の方から入ってくるので、ブロックがあるとき(#)は2次元配列に格納し、ブロックがないとき(.)は2次元配列に格納しない(=skipする)でいけそう
ただし、Y-Z面で見たときに最初からずーっとブロックがない場合は、該当する配列の位置に何も書き込まなくなるので、最初だけ()はドット(.)でも書き込む
この作戦に基づいたコードが以下になります。
ソースコード(ruby)
他の言語の解が必要な方は、OpenAI ChatGPTやGoogle Gemini、Microsoft Copilotなどの生成AIをご利用ください。プロンプトとしては、ソースコードをテキストファイルに保存して生成AIにアップロードした上で
添付ファイルをPython3に書き換えてください
こんな感じで変換できると思います。ただし変換結果が完全に正しいかは保証できかねるのでご了承ください。
# 戦略
# - 回答する平面は Y-Z 面となることをまず理解する(あ〜たしかにね、となるまで絵を描いたりして理解する)
# - 入力データの順番が、Z軸の下の方から一枚ずつX-Y平面が積み重なるイメージ
# - x==0 のときは条件なしで入力データを配列に書く。x > 0 では "." のときは書き込みをスキップする
# 入力ファイルの最初の1行目を読み取って、パラメータを変数に格納する
X,Y,Z = gets.split("\s").map {|s|s.to_i}
# 2次元配列を定義
shoumenAry = Array.new(Y) { Array.new(Z) }
Z.times do |z|
X.times do |x|
data = gets.chomp
data.each_char.with_index do |c,y|
if x == 0 then # 最初の行だけ "." があっても書き込む
shoumenAry[y][z] = c
else
if c != "." then
shoumenAry[y][z] = c
end
end
end
end
# "--" は1面処理したら必ず来るので、それをここで拾って捨てる。一応エラー処理も入れておく
data = gets.chomp
if data != "--" then
puts "Error: illegal situation"
exit -1
end
end
# output : shoumenAryの表示。
stack = Array.new
Z.times do |z|
s = ""
Y.times do |y|
s += shoumenAry[y][z]
end
stack.push(s)
end
stack.size.times do |i|
puts stack.pop
end
ソースコードの補足ですが
- 2次元配列は定義時に初期値(ゼロで埋めるなど)を入れてません。入れてもいいのですが、最初(x=0の領域)に必ず入力値を配列に入れるように処理する方針としたため
- 出力処理のところ、入力データはZ=0から順次入ってくるものの出力の要求仕様は上から順に出さなければならないので、わざわざ出力用に配列(stack)を用意して、FILO処理(FirstIn LastOut:最初に入れたモノが最後に出てくる)をしています