Help us understand the problem. What is going on with this article?

【Ruby】標準入力と出力のまとめ

入力と出力のまとめ

オンラインでスキルチェックをする時などに、入力された文字列や数値を扱う必要があったのでまとめます。

問題自体は簡単でも、この作業に手間取ってしまって時間がかかってしまうことがたくさんありました。
「さて、そろそろ簡単な問題でも解いてみようかな」と考えていた時に、私がよく調べていたものをまとめます。

たまに読んでくださる方がいるので、標準入力で受け取ったものを、どう処理するのかという点に関してもまとめました。

標準入力について

 puts "数字を入れてください"
 number = gets
 puts number

getsというメソッドを使用することで、入力をさせ、その値を受け取ることができます。上記のプログラムでは、キーボードで値を入力するのを待ってくれます。いきなり待たれて「故障してしまった!」とびっくりしないように、一行目を入れています。早速実行してみます。とりあえず、私の身長である180を入れてみることにします。

実行結果

数字を入れてください
180 #私が入力したもの
180 #numberに格納された180が出力される

スキルチェックでは

たいてい、どんな風に入力をするのか説明が丁寧に書いてあります。
それをgetsで受け取っていくような形になります。例えばこんな感じです。

「一行目に名前を示す文字列Nを、二行目に年齢を示す文字列Aを、三行目に好きなものを示す文字列Lを入力します。」こんな風に説明がなされるわけです。

(入力例)
N
A
L

具体的には以下のような感じになりますね。

(具体例)
タマ
8
カツオ

実際の問題を解く時には、こんな風にgetsを書いていくと順番に入力されます。

name = gets #入力一行目
age = gets #入力二行目
like = gets #入力三行目

出題者側が代わりにキーボード操作をしてくれて、どんなルールで入力がなされるのかが書いてあると思うとイメージがつかみやすいかと思います。

型に注意

ところで、getsとはget stringの略で、文字列型として入力を受け取ります。

先ほど私の身長を入力しましたが、理想の身長差はマイナス15センチらしいです。
そこで、理想の身長を出力するプログラムを作ってみましょう。

puts "身長を入れてください"
number = gets
puts "お相手の理想の身長は"
puts number - 15

(実行結果)

身長を入れてください
180
お相手の理想の身長は
Traceback (most recent call last):
input.rb:4:in `<main>': undefined method `-' for "180\n":String (NoMethodError)

途中まではうまく行っているのですが、4行目でエラーが出てしまっています。
理想の相手がいないことを示すNoMethodError

このエラー文はこんなことを行っています。

180\nという文字列に対して、-というメソッドが使われました。そんなメソッドは定義されていないので、ありません。

そこで、型を変更する記述を入れましょう。to_iで型を数値に変更することができます。

puts "身長を入れてください"
number = gets.to_i
puts "お相手の理想の身長は"
puts number - 15

実行結果

身長を入れてください
180
お相手の理想の身長は
165

型が正しくなりましたので無事に計算することができました!

改行に注意

ところで、先ほどのエラー文で、180\nと書いてありましたが、\nって何でしょうか。
実は改行のことです。簡単なプログラムを書いてみていきましょう。

input.rb:4:in `<main>': undefined method `-' for "180\n":String (NoMethodError)

文字列どうしを+で連結することができますので、入力された文字列に何か文字列をくっつけてみましょう。

puts "猫の名前は?"
name = gets
puts name + "です!"

(実行結果)

猫の名前は?
タマ #入力しました。
タマ #勝手に改行されています。
です!

「タマです!」と一行で出力されて欲しい。
chompメソッドを使うと、末尾の改行文字を取り除いた新しい文字列を返してもらえます!

puts "猫の名前は?"
name = gets.chomp
puts name + "です!"

(実行結果)

猫の名前は?
タマ
タマです!

情報がいっぱい

一度の入力で情報がいっぱい書いてあるような場合、そのままだと扱うのが面倒なときがあります。こんな問題はどうでしょうか。

太郎くんはお寿司屋さんにいきました。
入力は一行で、半角スペースありで太郎くんの食べたお寿司のネタが3つ以上の任意の数入力されます。
3つめに食べたお寿司のネタを出力してください。

(入力)
neta_1 neta_2 neta_3 ... neta_n

(具体例)
たまご えび はまち まぐろ うに

(期待される出力)
はまち

もし、入力がn回に分けられているなら、3回getsして、3回目をputsすれば済むのにめんどくさいです。そこで、半角スペースで文字列を区切って配列で返すメソッドを使ってみましょう。

splitを使う

splitは、引数を区切り文字として文字列を分割し、配列を返します。
下記の例ですと、半角スペースで文字列を分断し、それぞれを配列の要素として変数sushiに入れていきます。(inputをgetsで入力された文字列としています。)

input = "たまご えび はまち まぐろ うに"
sushi = input.split(" ")
puts sushi[2] #=> はまち

配列の番号は最初が0から始まる事にも注意しましょう!
ちなみに上記のプログラムでsushiを出力しますと、順番に改行ありで出力されます。

input = "たまご えび はまち まぐろ うに"
sushi = input.split(" ")
puts sushi
(実行結果)
たまご
えび
はまち
まぐろ
うに

情報がいっぱい+処理がいっぱい

配列にする段階で、何か処理をしたいという場合があるかもしれません。
こんな問題はどうでしょう。

太郎くんは歴史に興味があります。
1926年は昭和1年で、1989年は昭和64年です。

西暦から1925を引くと、昭和何年になるかが計算できます。
1926以上1989以下の西暦が、任意の数、半角スペースありで一行で入力されます。
昭和に変換した年を改行ありで順に出力してください。

(入力)
year_1 year_2 year_3 ... year_n

(具体例)
1926 1930 1959 1938

(期待される出力)
1
5
34
13

とりあえず、力技で解く

input = "1926 1930 1959 1938"
years = input.split(" ") #とりあえず分割して配列に

showa = []#空の配列を用意

years.each do |year|
  showa << year.to_i - 1925 #配列yearsの要素一つ一つを数値に変換して、1925を引いて、配列に入れる
end

puts showa #昭和に変換された数字の入っている配列を出力する

結構行数を使ってしまいました。

mapを使う

配列型にmapを使うと、それぞれの要素に決まった処理を行って、新しい配列を作ってくれます。先ほどの問題だと、以下のような形で解くことができますね。

input = "1926 1930 1959 1938"
years = input.split(" ").map{|year|year.to_i - 1925}
puts years

mapの使い方

配列.map{|変数|変数に対する処理}

配列の要素を一つ一つ変数に入れて、処理をしたものを順番に入れて作った新しい配列を返してくれます。これは便利ですね!

出力の整形

ところで、出力の形式が決まってしまっていたらどうでしょう。
さっきの問題にこのような指定がある場合です。

昭和に変換した年を「,」で区切り一行で出力してください。
(解答例)
1,5,34,13

joinを使う

配列の要素それぞれを文字列に変換し、引数を区切り文字として結合した文字列を返してくれます。例えばこんな感じです。

array = [1926,1930,1959,1938]
puts array.join(",") #=> 1926,1930,1959,1938

引数をか変えれば、区切りはもちろん変わります!

array = %w(1926 1930 1959 1938)#こんな風にも配列は書けますね!
puts array.join("★") #=>1926★1930★1959★1938

配列を扱う

結局、標準入力されたものを配列化して扱うんケースが多いんじゃないか!?
ということで、標準入力を配列にした後のことをもう少し考えてみます。

配列の差

こんな風に配列の和や差を取ることができます。

irb(main):001:0> [1,3,5] + [2,4,6]
=> [1, 3, 5, 2, 4, 6]

irb(main):002:0> [1,3,4,5,5] - [5]
=> [1, 3, 4]

uniqを使って重複をまとめる

[1,2,4,3,3,4,5,5] #=> [1, 2, 4, 3, 5]

selectを使って検索する

配列の中にあるものを、ある条件で検索して、当てはまるものだけを返したい場合、selectが使えます。
Enumerableなものがレシーバになれます。ざっくりと、eachつかえるようなものだと思っていただけたらイメージがつきやすいと思います。

Enumerable#filter

配列.select{ |変数|変数に対する真偽値を返すようなもの }

配列の要素を一つ一つ変数に入れ、ブロックを評価して真を返す要素で新しい配列を返すのがselectです。評価が偽になるようなもので新しい配列を返す場合はrejectを使います。

input = "2,3,4,6,8,9,10" #この中から奇数、偶数を出力したいとします。

irb(main):001:0> input = "2,3,4,6,8,9,10"
=> "2,3,4,6,8,9,10"

#配列にします
irb(main):002:0> input.split(",")
=> ["2", "3", "4", "6", "8", "9", "10"]

#数値に変換します
irb(main):003:0> array = array.map{|n| n.to_i }
=> [2, 3, 4, 6, 8, 9, 10]

# 配列の中身を一つ一つ変数に入れ、trueになるものだけでできた配列を返します。

# 奇数の場合
irb(main):00:0> array.select{|n| n.odd? }
=> [3, 9]

# 偶数の場合
irb(main):005:0> array.select{|n| n.even? }
=> [2, 4, 6, 8, 10]

オブジェクトにする

処理を行うことが少し多くなってきた場合、配列からオブジェクトに一度置き換えることを検討すると、見通しがよくなるかもしれません。

英語と数学と国語の点数が、"30,50,20"のように入力され、
複数人の場合は"30,30,50 40,80,20"のように、半角スペースが入リます。

三教科の最高得点を
50
80
のように人数分出力してください。
ただし、何名のテストの点が入力されるかは不明です。

このようなとき、力技で解くことももちろん可能ですが、クラスを作って見通しをよくしておくと、バグを探しやすく、なおかつ複雑な問題に対処しやすくなるかもしれません。

# 人クラスを定義し、英語と数学と国語の点数を用意します。
class People
  attr_accessor :english, :math, :japanese

# 一人分の点数を初期値にします
  def initialize(scores)
    scores = scores.split(",").map{|score| score.to_i}
    self.english = scores[0]
    self.math = scores[1]
    self.japanese = scores[2]
  end

# インスタンスメソッドで3科目の中の最大値を返します。
  def max_score
    scores = []
    scores << self.english
    scores << self.math
    scores << self.japanese
    scores.max
  end
end

# 3人分の得点を入れるとします。
input = "80,50,20 86,54,23 67,33,44"

input = input.split(" ")

max_scores = input.map{|scores| People.new(scores).max_score }

puts max_scores

実行結果
80
86
67

インスタンスメソッドを定義して使用するサンプルなのですが、実際にはinitializeをするときにmaxも用意しておく方が手間は少ないです。

class People
  attr_accessor :english, :math, :japanese, :max

# 一人分の点数を初期値にします
  def initialize(scores)
    scores = scores.split(",").map{|score| score.to_i}
    self.max = scores.max # この時点で用意してしまう。scores点数の配列だし。
    self.english = scores[0]
    self.math = scores[1]
    self.japanese = scores[2]
  end
end

そもそも、これだけでよくないか?

class People
  attr_accessor :max

# 一人分の点数を初期値にします
  def initialize(scores)
    scores = scores.split(",").map{|score| score.to_i}
    self.max = scores.max 
  end
end

そもそもクラスいらなくないか? 一行で終わるやろ!

puts input.split(" ").map{|scores| scores.split(" ")}.map{|scores| scores.map{|scores| scores.split(",").max}}

ですが、これは今作ったオブジェクトの振る舞いから処理の順番を辿って作ったものであって、なかなか最初に思いつくのは難しいのではないかと思います。

Structを使用する

構造体クラスを使用して、各種の属性を管理するのも便利です。

使い方
Struct.new(:attr)
先ほどのPeopleクラスを構造体で
People = Struct.new(:math,:english,:japanese)

class Struct

終わりに

お役に立っていたら幸いです!ありがとうございました!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした