記事について(これは別に確認していただかなくて大丈夫です)
- 自分がpaizaのプログラミングスキルチェックで「標準入力の受け取り方」という初歩の初歩から躓いたため記事にまとめました
- したがって本記事のターゲットも「いざアルゴリズムの問題を解こうとしても入力の受け取り方が良く分からなくて進められない!!」という初学者の方々になるかと思います
- また、かなり初歩的な個所から言及しているので回りくどい内容となっております
予めご了承ください
- また、かなり初歩的な個所から言及しているので回りくどい内容となっております
- したがって本記事のターゲットも「いざアルゴリズムの問題を解こうとしても入力の受け取り方が良く分からなくて進められない!!」という初学者の方々になるかと思います
- 自分が Ruby を扱っている関係で Ruby で話を進めております
- 入力される値は今回数値だけを題材にしております
- また検証にはpaiza.ioを使用しております
必要となる基本知識
まずアルゴリズム以前に「入力値を受け取る」ことが出来なければ論外なので、ここを抑える必要があります
以下、paizaから提示いただいているサンプルコードの引用になります
このサンプルを基にAパート、Bパートに分けて説明を進めます
paiza - 値取得・出力サンプルコードinput_line = gets.to_i #【Aパート部分】 input_line.times do s = gets.chomp.split(" ") #【Bパート部分】 print "hello = #{ s[0] } , world = #{ s[1] }\n" end
【Aパート部分】gets
について
一行目のinput_line = gets.to_i
について、「gets
が何かはよく分からないが、gets
をto_i
して変数に代入しているな」というのが分かります
まずto_i
とは?
文字列を 10 進数表現された整数であると解釈して、整数に変換します。
引用:https://docs.ruby-lang.org/ja/latest/method/String/i/to_i.html
- 『
to_i
は 文字列型 → 整数 に変換する』メソッドである
じゃあ、gets
とは何なのか?
一行読み込んで、読み込みに成功した時にはその文字列を返します。 EOF に到達した時には nil を返します。
引用:https://docs.ruby-lang.org/ja/latest/method/IO/i/gets.html
- 『
gets
は入力を読み込む』メソッドである- 入力値は "文字列" として取得
- 入力値は "1行ずつ" 取得する
- 1行ずつ取得していき、取得する入力値の終端に到達したら nil を返す
gets
で関係してくる 改行コード について
ここで先述のgets
の特徴である『入力値は "1行ずつ" 取得する』という特徴に注意すべき点があります。
- それは『
gets
は複数行の入力を受け取る場合、行の区切りとして行の末尾に改行コード"\n"を追加する』という点です- 「複数行の入力」 = 「改行で区切って複数の値を入力」という事から、
各行の末尾に改行を示す改行コードという文字列が付加される
- 「複数行の入力」 = 「改行で区切って複数の値を入力」という事から、
-
gets
は『複数行の入力を取得する場合には各行末尾に改行コードが付加される』- 改行コードとは
\n
のような文字列
- 改行コードとは
以下、getsメソッドのサンプルコードの引用ですが、
出力結果には "\n" が末尾に付加されており、改行されているのが分かります。
(複数行の入力であろうというのが分かる)
getsメソッドのサンプルコードf = File.new("oneline_file") f.gets # => "This is line one\n"
引用:https://docs.ruby-lang.org/ja/latest/method/IO/i/gets.html
ここまでを踏まえて
▶ 以下、『入力値が4の場合』のサンプルコードになります
4
input = gets
# 入力値をそのまま利用
p input # => "4"
p input.class # => String
p input + input # => "44"
# `to_i`を使用
p input.to_i # => 4
p input.to_i.class # => Integer
p input.to_i + input.to_i # => 8
- 「数値 4」を入力しても、出力結果は「文字列 "4"」が返されているのが分かる
- 入力値をそのままで計算を試みても"文字列同士の連結"と認識されたりしてしまい思ったような結果が得られない
- このため入力値から数値での計算を行うには
to_i
で数値に変換する一手間が必要となる
- このため入力値から数値での計算を行うには
▶ 以下、『入力値が複数行の場合』のサンプルコードになります
-
gets
メソッドの「入力値は "1行ずつ" 取得する」という特徴に一癖あります- 次のサンプルコードから分かるように、
『gets
を呼び出すたびに入力から1行ずつ呼び出して取得』します
- 次のサンプルコードから分かるように、
1
2
3
p gets # => "1\n"
p gets # => "2\n"
p gets # => "3"
p gets # => nil
- また、「1行ずつ取得していき、取得する入力値の終端に到達したら nil を返す」という特徴から取得する入力の末端(入力の最終行)に到達したら "nil" を返し「それ以上は入力がありません」という事を意味します
- よって4行目の "nil" はこれ以上は入力値が無い事を意味します
【Bパート部分】chomp
とsplit(" ")
について
「gets
で入力値を受け取っているのは分かった。ただ、s = gets.chomp.split(" ")
で入力値を操作しているが目的は何なのか?」と思います。
じゃあ、chomp
とは何なのか?
self の末尾から rs で指定する改行コードを取り除いた文字列を生成して返します。
引用:https://docs.ruby-lang.org/ja/latest/method/String/i/chomp.html
- 『
chomp
はメソッドを呼び出した文字列から末尾の改行コードを取り除く』メソッドである- デフォルトは改行コード
\n
を取り除く- この点 paiza の問題を解く分にはデフォルトのままで問題ない
- デフォルトは改行コード
じゃあ、split
とは何なのか?
第 1 引数 sep で指定されたセパレータによって文字列を limit 個まで分割し、結果を文字列の配列で返します。
引用:https://docs.ruby-lang.org/ja/latest/method/String/i/split.html
- 『
split
はメソッドを呼び出した文字列を "指定した文字列で区切る" & "文字列の配列で結果を返す"』メソッドである-
paiza - 値取得・出力サンプルコードのように、1 バイトの空白文字
" "
を指定した場合は『先頭と末尾の空白を除いたうえで、空白文字列で分割』して配列を返してくれる
-
paiza - 値取得・出力サンプルコードのように、1 バイトの空白文字
ここまでを踏まえて
以下、『入力値が複数行の場合』のサンプルコードになります
1
2 3 4
5
input1 = gets
input2 = gets
# 【1】単体の数字から chomp メソッドで改行コードを除き整数変換
p input1 # => "1\n"
p input1.chomp # => "1"
p input1.chomp.to_i # => 1
# 【2】空白区切りの複数の数字から chomp メソッドで改行コードを除き整数変換
p input2 # => "2 3 4\n"
p input2.chomp # => "2 3 4"
p input2.chomp.to_i # => 2
# 【3】空白区切りの複数の数字から chomp メソッドで改行コードを除き、
# 空白で分割して配列を返した上で各要素を整数変換
input2_ary = input2.chomp.split(" ")
p input2_ary # => ["2", "3", "4"]
p input2_ary[0].to_i # => 2
p input2_ary[1].to_i # => 3
p input2_ary[2].to_i # => 4
-
【1】について
- 入力が複数行なので入力値を確認すると末尾に改行コードが入っているため、改行コードを除く手間が必要であることが分かる
- 「改行コードを除いた上で整数変換」はpaiza - 値取得・出力サンプルコード の
s = gets.chomp.split(" ")
で行っていることである
-
【2】について
- 【1】とは異なり空白区切りで "2␣3␣4" のように入力値が渡されている事例
- このまま整数変換すると先頭の"2"は変換して残るが、後続は消失してしまう
(この点については下の [折り畳み] にて後述)
- このまま整数変換すると先頭の"2"は変換して残るが、後続は消失してしまう
- 【1】とは異なり空白区切りで "2␣3␣4" のように入力値が渡されている事例
-
【3】について
- 【2】の対策として
split
メソッドで1つずつ値として取得できるように配列に格納する- 空白区切りなので
split(" ")
で要素を格納
- 空白区切りなので
- これによって値が消失することなく操作可能になる
- 【2】の対策として
[折り畳み]【2】なぜ整数変換で先頭の数値しか残らないか
【結論】to_i
の仕様である「整数とみなせない文字があればそこまでを変換対象」とする処理により後続の数値は消失する
整数とみなせない文字があればそこまでを変換対象とします。変換対象が空文字列であれば 0 を返します。
例p "0x11".to_i # => 0 p "".to_i # => 0
引用:https://docs.ruby-lang.org/ja/latest/method/String/i/to_i.html
このことから 2␣3␣4
の 2
はto_i
の対象として整数変換可能と認識されるが、
後続の ␣3␣4
は空白(数値ではない文字)を挟む関係で「整数とは見なされない」ため消失する
(余談)
以上より、【1】に限っては明示的に chomp
で改行コードを除かなくとも「"1" は整数、"/n" はでは無い」とみなされるので期待する結果が得られる
input1 # => "1\n"
input1.to_i # => 1
ここまでの前提知識を踏まえて以下の通り実践寄りに説明を進めます
改めて「値取得・出力サンプルコード」を解いてみる
以下の問題が出題されたとする
入力される値2 2 5 3 4
このテストケースでは、最初の値は、その後入力される行数を示す(2行の入力がある)
2行目以降は、helloとworldの値が[,]区切りで書かれています。期待する出力hello = 2 , world = 5 hello = 3 , world = 4
[コードから分かる点]
- 複数行の入力である
- 一行の数値同士の間は半角空白(" ")で区切っている
- '期待する出力'から "2␣5" とそのまま扱ってはいけない
- 一つ一つ数値として見なす必要がある
[コードから注意する点]
- 複数行の入力だから改行コードの除去は必要 =>
chomp
が必要 - 一行の数値同士の間は半角空白(" ")で区切っており、'期待する出力'から "2␣5" とそのまま扱ってはいけないのであるならば、一旦配列に格納して1要素として扱えるようにした方が無難 =>
split(" ")
が必要
ここまでで習ったことだけで問題を解いてみる
※ベストな解答は paiza - 値取得・出力サンプルコード に示されていますが、この記事で習ったことだけを利用して解いてます。
2
2 5
3 4
_input1 = gets # この変数は使用しない
input2 = gets
input3 = gets
input2_ary = input2.chomp.split(" ")
input3_ary = input3.chomp.split(" ")
p "hello = #{input2_ary[0]} , world = #{input2_ary[1]}"
p "hello = #{input3_ary[0]} , world = #{input3_ary[1]}"
"hello = 2 , world = 5"
"hello = 3 , world = 4"
◎paiza - 値取得・出力サンプルコード と比べて以下のような改善点がありますが、期待する出力が確認できました。
- 変数名の先頭にアンダースコア( _ )を使う理由については、こちらの記事が参考になります
【改善点について】
- 問題文より、一行目の入力値
input1
を使用していない点が好ましくない- 「このテストケースでは、最初の値は、その後入力される行数を示す(2行の入力がある)」と説明されているため、この点を踏まえれば paiza - 値取得・出力サンプルコード に近づける
- 同じ形で出力するのであれば繰り返し処理を用いるのがスマート
最後に
自分が初学者な立場なので色々と回りくどい表現になってしまいましたが参考になれば幸いです。
改善点など本記事に関連した内容を別記事にまたまとめたいと思います。
参考サイト、関連記事など
>基本的な入出力の受け取り方について
>paiza.ioの基本的な使い方について
>Rubyのリファレンスマニュアル
>関連記事