Ruby初心者がpaiza D〜Cランク序盤を解く中で使ったメソッドまとめと学習の気づき
1.アルゴリズムを学ぶとどんなことに役立つ?
まず、アルゴリズムの学習を始める前に、「アルゴリズムって難しいから目的が明確じゃなければ途中で挫折してしまいそうな予感がする」と感じたためアルゴリズムってどんなこのに役立つのかを調べることとしました。
私が導き出した結論からお伝えするとwebエンジニアがアルゴリズムを学ぶ理由は、
「複雑な問題を解決する思考プロセスを鍛えるため!!!」であると思います。
以下、3つの項目に分けて説明します。
1.技術力の確認と評価
- アルゴリズムを使った課題解決は、コードのロジックに無駄や重複がないかを判断する基準となる。
- 実務では、例えば、「関東地域の20代女性を対象にクーポンコードを発行する」処理を考える際、対象者の抽出のロジックを組む場面でアルゴリズムが役立つ。
2.データ構造の品質担保
- 適切なアルゴリズムを用いることで、データ構造を効率よく管理でき、不要なメモリ消費や処理速度の低下を防ぐ。
- 結果として、アプリケーションの品質、安定性を保てる。
3.問題解決能力の向上
- アルゴリズムの組み立てを通じて、課題を分解・最適化する思考プロセスが鍛えられ、問題解決能力の向上につながる。
こちらの記事を参考にしました。目的を明確にするとモチベーションが湧いてきますね🔥
【エンジニア必見!】コーディング面接の内容から対策方法まで分かりやすく解説
2.メソッドの一覧
chomp
- 入力された文字列の改行を取り除きます。
例
s = gets.chomp
puts s[-1]
# 入力 ruby
# 出力: y
解説
- s[-1]は、文字列の末尾一文字を取得します。(Rubyのインデックスは負の値で後ろから数えられます。)
times
- 整数に対して回数分だけ繰り返すメソッド
- ブロック変数 i には、配列などの要素を識別するための0から始まる番号(インデックス)が順番に渡されます。
例
3.times do |i|
puts i
end
# 出力:
# 0
# 1
# 2
n - 1 - i
- こちらの理解に苦戦し時間を使ってしまったので解説に入れます😭
- 配列の最後の要素から取り出したい時に使用します。
- 検索履歴のように最新のものを上とし、重複を削除する仕組みを作る時に使います。
例
words = ["apple", "banana", "orange"]
n = 3
n.times do |i|
p [i, n - 1 - i, words[n - 1 - i]]
end
# 出力:
# [0, 2, "orange"]
# [1, 1, "banana"]
# [2, 0, "apple"]
解説
- i = 0 → インデックス番号は 2 → “orange”
- i = 1 → インデックス番号は 1 → “banana”
- i = 2 → インデックス番号は 0 → “apple”
if
- 条件が真( true )のときに処理を実行する条件分岐の構文
- 式の評価が true なら中の処理を実行し、false なら実行されない。
例
n = gets.to_i
n.times do |i|
a, b = gets.split.map(&:to_i)
if i == 1
puts "#{a} #{b}"
end
end
# 入力
# 3
# 32 21
# 11 9
# 5 3
# 出力
# 11 9
each
- 配列、範囲、ハッシュの各要素ごとにブロックを繰り返し実行するメソッド
例1.配列
[10, 20, 30].each do |num|
puts num
end
# 出力:
# 10
# 20
# 30
例2.範囲
(1..3).each do |n|
puts n
end
# 出力:
# 1
# 2
# 3
*
- スプラット演算子
- 余った要素をまとめて配列にする演算子
例
n, *a = gets.split.map(&:to_i)
puts a
# 入力: 2 1324 1223
# 出力:
# 1324
# 1223
split
- 文字列を分割して配列化するメソッド
例1
s1, s2 = gets.split
puts s1
puts s2
# 入力: 10 20
# 出力 10
# 出力 20
解説
- 入力をそのまま受け取ります。→”10 20\n”
- .split → ["10","20"]
- 文字列を各要素に分けて配列にし処理できる形にします。
join
- 配列の要素を結合し一つの文字列にするメソッド。
- 引数に区切り文字を指定すると、その文字を間に挟んで結合する。
例
n, a, b = gets.split.map(&:to_i)
puts n.times.map { "(#{a}, #{b})" }.join(", ")
# 入力: 2 33 44
# 出力: (33, 44), (33, 44)
to_i
- 数値や文字列を整数(Integer)に変換します。
map
- 配列の各要素に処理を適用して、新しい配列を作るメソッド
a, b, c = gets.split.map(&:to_i)
puts a - b + c
# 入力: 5 3 2
# 出力: 4
解説
- &:to_i は「各要素に対して to_i メソッドを呼ぶ」という省略記法
- こちらを使用すると数値として扱えるため計算が可能となります。
to_f
- 数値や文字列を浮動小数点数(Float)に変換します。
format
- Ruby標準ライブラリにあるメソッドで、数値や変数を文字列の中に埋め込みながら、桁数や小数点以下の桁数、表示形式に揃えます
例 小数点以下m桁まで四捨五入して表示します
n = 3.141592
m = 3
puts format("%.#{m}f", n)
# 出力: 3.142
解説
format("%.#{m}f", n)
- %フォーマット指定の開始(第一引数)
- .#{m} : 小数点以下の桁数を指定(m は変数 → 例えば 3 なら「小数第3位まで」)
- f : 浮動小数点数を固定小数点形式で表示します。 - nは対象となる数値(第二引数)
参考:formatの公式ドキュメント
%nd
- decimal(10進数)整数を出力する指定子
- 幅より数値が小さい場合、デフォルトは「スペース埋め + 右詰め」になります。
例
n = gets.to_i
puts format("%3d", n)
# 入力: "12"
# 出力: " 12"
n = gets.to_i
puts format("%03d", n)
# 入力: 12
# 出力: 012
size
- 文字列の文字数を数値で返す(カウント)。
- 配列の場合は、要素数で返す。
- 参考:length、size、count メソッドの違いまとめ【Ruby】
"%2d" % (i * j)
- 上記での方法とは別に%(演算子)を使用して計算結果を埋め込みしています。
- "%2d" の中の % は書式指定の記号です。(参考:Ruby入門 - 演算子)
問題(九九表を罫線入りで出力 Ruby編(paizaランク C 相当))
九九表を、横の数値間では | (半角スペース バーティカルライン 半角スペース)、縦の数値間では = で区切って出力してください。
ただし、数値を出力する際は 2 けたになるよう半角スペース埋めで出力します。また、縦の数値間で = を出力する際は、その上の行と文字数が等しくなるように出力します。
答え
(1..9).each do |i|
line = (1..9).map { |j| "%2d" % (i * j) }.join(" | ")
puts line
puts "=" * line.size if i < 9
end
# 出力 (一部)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
==========================================
2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18
*こちら無料の範囲内の問題となっております。
Array
- 任意のRubyオブジェクトを要素として持つことができる配列クラスです。
参考:Ruby 3.4 リファレンスマニュアル
メモ:Ruby競プロTips(基本・罠・高速化108 2.7x2.7)
*こちらのメモは、Rubyのアルゴリズムの解き方について詳しく説明されていました。自分用のメモとしても残しておきます。
reverse_each
- 各要素に対して逆順にブロックを評価するメソッド。
参考:[Ruby 3.4 リファレンスマニュアル](Ruby 3.4 リファレンスマニュアル)
Set
- 集合(重複のないオブジェクトの集まり)を表すクラスです。
- addメソッドは、Setに要素を追加する。
- 標準ライブラリ(Rubyに最初から付属しているライブラリ)は、requireで呼び出して使用します。
参考:Rubyでaddメソッドを使う方法を現役エンジニアが解説
例 検索ワードの履歴を見る機能
require 'set'
n = gets.to_i
words = Array.new(n) { gets.chomp }
seen = Set.new
words.reverse_each do |w|
puts w if seen.add?(w)
end
# 入力:
4
red
green
blue
blue
# 出力:
blue
green
red
解説
words = Array.new(n) { gets.chomp }
- ブロックを渡すと、n回繰り返してブロックを実行し、その戻り値を配列の各要素に入れる動作を行います。
seen = Set.new - Set.newは空の集合を生成し、それを変数に代入します。
puts w if seen.add?(w) - 未出現なら集合に追加+true、重複ならnil(false)となり始めてでた単語だけ出力します。
include?
- 配列(Array)、文字列(String)、集合(Set)などで指定した要素と等しい要素を持つときに真を返すメソッド。
参考:Ruby 3.4 リファレンスマニュアル
条件演算子(三項演算子)
- 「条件式 ? 真の場合の値 : 偽の場合の値」の条件式の結果によってどちらかの値を返す演算子です。
例
s = gets.chomp.split
puts s.include?("red") ? "Yes" : "No"
# 入力:
red green blue blue green blue
# 出力:
Yes
unless
- 条件が偽またはnilの「〜ではない場合に」処理を行う条件分岐です。
- ifの逆を表します。
例
require 'set'
words = gets.split
seen = Set.new
words.each do |w|
unless seen.include?(w)
puts w
seen.add(w)
end
end
# 入力:
red green blue blue green blue
# 出力:
red
green
blue
解説
このように書くことで「まだ表示されていない単語だけを出力する」ことができます。
index
- 配列の中で、指定した要素が最初に現れる位置(インデックス番号)を返すメソッド。
例
words = gets.split
s = gets.chomp
puts words.index(s)
# 入力:
red green blue blue green blue
# 出力:
0
rindex
- 配列の中で、指定した要素が最後に現れる位置(インデックス番号)を返すメソッド。
参考:Ruby 3.4 リファレンスマニュアル
each_key
- ハッシュのキー(key)を順番に取り出すメソッド。
参考:Ruby 3.4 リファレンスマニュアル
each_value
- ハッシュの値(value)を順番に取り出すメソッド。
参考:Ruby 3.4 リファレンスマニュアル
tally
- 配列の要素を数えてハッシュで返す。
例
words = gets.split
counts = words.tally
counts.each_key { |w| puts w }
counts.each_value { |c| puts c }
# 入力:
red green blue blue green blue
# 出力:
red
green
blue
1
2
3
解説
- counts = words.tally では、配列 words の各要素を数え、「キー=要素」「値=出現回数」のハッシュを counts に代入しています。
- each_keyは、ハッシュのキー(単語)を順番に取り出して出力します。
- each_valueは、ハッシュの値(出現回数)を順番に取り出して出力します。
参考:Ruby 3.4 リファレンスマニュアル
Hash
- hashは、キー(名前)と値(データ)をセットにして保存する仕組みを持つクラスです。
- 単語の出現回数を数える場合、キーを単語、値をカウントに使うことができます。
例
words = gets.split
counts = Hash.new(0)
words.each { |w| counts[w] += 1 }
counts.each do |word, count|
puts "#{word} #{count}"
end
# 入力:
# red green blue blue green blue
# 出力:
# red 1
# green 2
# blue 3
解説
- Hash.new(0):- キーごとの初期値を0にして、カウント用のハッシュを作成します。
- words.each:ループで1単語ずつカウントします。
- counts.each:ハッシュを1組ずつ取り出して出力します。
tallyを使用する例
words = gets.split
counts = words.tally
counts.each do |word, count|
puts "#{word} #{count}"
end
# 入力:
# red green blue blue green blue
# 出力:
# red 1
# green 2
# blue 3
解説
- tallyは自動でカウントしてくれるのでメソッドなので、今回はHashを使いtallyが内部で行なっている処理を手動で実装し理解を深めました。
参考:Ruby 3.4 リファレンスマニュアル
論理演算子(宝くじ)
- || は「または(OR)」を意味し、どちらか一方の結果が真(true)であった場合に真を返す論理演算子です。
b = gets.to_i # 基準となる当選番号
n = gets.to_i # 比較する番号の個数
a = n.times.map { gets.to_i } # n回整数を入力し、配列aに格納
a.each do |e|
result = 'blank' # 初期値として「はずれ」を代入
if e == b
result = 'first' # 完全一致
elsif e + 1 == b || e - 1 == b
result = 'adjacent' # 前後1番違
elsif e % 10_000 == b % 10_000 # 下4桁が一致
result = 'second'
elsif e % 1000 == b % 1000 # 下3桁が一致
result = 'third'
end
puts result
end
# 入力:
# 142358
# 3
# 195283
# 167358
# 142359
# 出力:
# blank
# third
# adjacent
解説
- %(剰余演算子)を使うと、数値を割ったときの“余り”を求めることができます。
- たとえば 142358 を 10_000 で割ると、余りは 2358 となり、下4桁だけを取り出せます。
参考:Ruby 3.4 リファレンスマニュアル
参考:論理演算子の種類と使い方
参考:剰余演算子
3.Rubyのformatで90点止まり…100点を取るまでの試行錯誤
【実数をフォーマット指定して出力】複数の実数を出力(paizaランク C 相当)
こちらの問題で、formatを使用してもテストケース9だけがどうしてもクリアできませんでした...
どうやらその原因は、to_fとformatは、2進数による浮動小数点計算では対応できない値の結果により、丸められ誤差が生じるようです。
BigDecimalを使用すれば、浮動小数点数演算ライブラリであり、任意の精度で10進数で表現された浮動小数点を扱えるとういことが調べて分かりました。(有限桁で丸めるため完全に誤差はゼロにならないです。)
FloatとBigDecimalについては、下記の記事を参考にしましたが細かい説明はここでは省略します!
Rubyによる 小数 と Float と BigDecimalについて(初心者向け解説)
でも、ここで疑問が浮かびます...🤔💭
アルゴリズムの問題で金融系のような厳密な数値の回答が求められるのか?
しかも、C++の回答であればformatを使用した回答でもテストケース9を突破できているぞ???
BigDecimalを使用したテストの回答はもう知っているけど、モヤモヤとした感覚が拭えませんでした。
よし!もう少し時間かけてベストな回答を模索しよう!!!と取り組んだところ、
どうやら、format("%.#{m}f", n) だけだと「format が出力時に四捨五入する」に頼っているので不安定という答えに辿り着きました。
そこで、丸めを自分で指定すればformatを使用しても解けるのでは!!!
ということで最終的にputs format("%.#{m}f", n.round(m))の形で無事突破することができました👏
roundメソッドにつきましては、こちらの記事を参考にしました。
【Ruby】小数点以下の桁数を指定して四捨五入、切り上げ、切り捨て!
回答
q = gets.to_i
q.times do
n, m = gets.split
n = n.to_f
m = m.to_i
puts format("%.#{m}f", n.round(m))
end
反省点
この記事を書いてて気づいたのですが、他の言語での解答コードを丁寧に理解できる形で紐解けば、もっと早く答えにたどり着けていたように思います😭
気づき
rubyでアルゴリズムの学習を進めていると、「アルゴリズム学習はRubyのメソッド習得に繋がる」と思いました。なので、初学者にとってアルゴリズム学習はRuby基礎習得の良い入り口になるのかも...と考えています。
最後に
アルゴリズムの学習をするときに、問題の文章の意味から全く理解できなくて、声に出して「全く分からないんだけど!」と笑っています。たぶん側から見たら完全に怪しい人です…笑
でもその状態から、わかるまで噛み砕きながら学習を進めていく過程こそが面白いと私は思います。没頭して、問題を解き続けて気づいたら全く分からなかった問題が解けている状態になっていて「コンピューターに指示を出してコンピューターと会話できるようになっている!」ことに感動しています。
なので、私にとってアルゴリズムの学習は今のところすごく楽しいです。(まだ序盤で楽しめるよう難易度だからかもしれませんが...笑)
この調子で、これからも一歩ずつ解き進めていきたいと思います。