paizaランク取得のためにこのくらい学習したということを就活でざっくりとアピールできればと思い投稿していますので見やすさは皆無です。
練習問題を解きながらChatGPTへと質問したり、調べたりという形でメモを残しているので、情報が不十分であったり、正確性に欠けることがあるかもしれません。
またマークダウンを意識して残してないので、コメントアウトのつもりで「#」を使っていてもQiitaでは見出しになっている箇所が多々あります。
「コメントアウト」
行の先頭に半角で「#」を書くと、その行はコメントアウトされる。
「=begin」と「=end」でコメントアウトしたい記述を囲むと長い行もまとめてコメント化できる。
「エラーメッセージ」
例:hi.rb:1:in <main>': undefined method
put' for main:Object (NoMethodError)
①「hi.rb」は実行したファイル名
②コロン(:)で区切られた「1」の数字は何行目でエラーが起きたかを示す。
③「in <main>':」の説明は省略。 ④「undefined method
put' for main:Object」はエラーの内容。
⑤「(NoMethodError)」がエラーの名前。
「irb」
ターミナルで「irb」を実行後、Rubyのプログラムを入力してエンターを押すと実行して結果を出力する。
※putsなどの画面表示するメソッドを使わなくても表示される。
例:1 + 2 → 3
irbを終了する場合は「exit」と入力して実行。
※irbはRuby言語を話し、ターミナルはshell言語を話しているという認識。
※windowsでirbを使うと日本語を入力できない時があり、その際はirb起動時に「irb --noreadline」とオプションをつけてみる。
「binding.irb」
プログラムの中に記述しておくと、そのファイルをターミナルで実行した際、binding.irbの行まで実行してプログラムが一時停止してirbが起動する。exitでirbから抜けるとプログラムが再開される。
「debug」
irgのように1行ずつプログラムを実行できる機能とデバックに便利な機能を持ち、2つの使い方がある。
①ターミナルから「rdbg ファイル名」で実行する方法。
②プログラム中で「require “debug”」と「binding.break」の2行を書いて、そこで一時停止して入力したプログラムを実行する方法。一時停止中に「n」コマンドを打つとプログラムを1行ずつ実行し、「c」コマンドで実行を再開する。
「putsメソッド」
「puts "○○"」と書くと、putsの後の○○という文字がコンソールに出力される。
putsの後には半角スペースをあける。
文字列はクォーテーション( ' もしくは " )で必ず囲むが、数値はクォーテーションで囲まない。
「printメソッド」
print は 改行なし で出力を行うメソッド。
「pメソッド」
「p 変数」で後ろに書いた変数やオブジェクトを表示する。
putsと似ているがデバックの際に用いられる。
「計算」
+-*/%で計算。
それぞれメソッドであり内部的には以下のようになっている。
puts 10.+(3) # => 13
puts 10.-(3) # => 7
puts 10./(2) # => 5
puts 10.*(3) # => 30
puts 10.%(3) # => 1
「abs メソッド」
絶対値(absolute value)を求めるメソッド。
数値が正ならそのまま、負なら符号を取って正に変換する。
x = -10 y = 7 z = 0
puts x.abs #=> 10
puts y.abs #=> 7
puts z.abs #=> 0
負の数にしたい場合
num = -x.abs
※x.abs は 常に正の値になり、そこにマイナス記号が付くため必ず負の数になる。
符号の反転をしたい場合
num = -x
※x = 5 のとき → -x は -5 になり、x = -5 のとき → -x は 5 になる
自然数の下?桁を取得したい場合
p 142358 % 1000 #⇒ 358
・少数の計算
割り算(/)は通常小数点を切り捨てられるけど、小数で計算すれば少数までの答えを得られる。
例:puts 5.0 / 2.0 → 2.5
少数を少数(Float)オブジェクトという。
・累乗
「**」で累乗の計算が可能。
例:puts 2 ** 8 → 256
・AND 演算 / OR 演算
AND 演算を実行するには、「&」演算子を使い、
OR 演算を実行するには、「|」演算子を使う。
例:a = 0 b = 1
puts a & b #⇒ 0
※ANDはどっちも1の時だけ1になる
例:a = 0 b = 1
puts a | b #⇒ 1
※ORはどちらかが1なら1になる(両方1でも1)
・XOR 演算
2つのビットが異なるときに 1 になる演算。
両方が 1 または 0 の場合は 0 になる。
例:a = 0 b = 1
puts a ^ b #⇒ 1
・NOT 演算
ビットを反転させる演算。0 は 1 に、1 は 0 に反転する。
例:a = 0
puts ~a & 1 #⇒ 1
2進数 1 桁の A と B を使って A + B を計算
結果の1桁目はSum、2桁目(繰り上がり)はCarryと呼ばれ以下のように求められる。
carry = a & b, sum = a ^ b、
・左シフト演算
ビット列を左にシフトする。
シフト後の空いたビット位置には 0 が入り、シフトする回数は指定された値になる。
例:a = 0001
a << 2 #⇒ 0100
・右シフト演算
ビット列を右にシフトする。
シフト後の空いたビット位置には符号ビット(負の数の場合は 1)や 0 が入り、シフトする回数は指定された値になる。
例:a = 1000
a >> 2 #⇒ 0010
※ビットの計算のキーワード
半加算機 / 全加算機 / ド・モルガンの法則
https://paiza.jp/works/mondai/reviews/show/a561c9fafd39956e44e0a450ee516de7
「代入演算子」
・a &= b は a = a & b
基本ビット演算でしか使われない。
対応するビットが両方とも 1 のときに 1 になり、それ以外は 0 になる演算。
a = 13 b = 11
puts a &= b #⇒ 9
1101 (13)
& 1011 (11)
——————
1001 (9)
※2進数にして演算を行った結果を返している。
・a ||= b は a || (a = b)
a が nil または false のときだけ b を代入する。
ハッシュの初期値を設定する際によく用いられる
⚠️ a = a || bではない。
⬇︎ なぜか?
ハッシュでは以下のように使われることがよくある。
h = {} h[:x] ||= 10
しかしもしこれが a = a || b の動きをしてしまうとすると a が存在しないときにエラーが起きてしまう。
↑エラーは起きない。a が存在しなくとも b に評価がいって b に値があれば b が代入されるだけ。
ただ a ||= b をしたいなら a = a || b ではない。
a || (a = b) が a ||= b と同じ動きをするわけを言語化すると、
左項の a が nil または false の時だけ右項の (a = b) が評価されるからというだけ
少し発展
h = Hash.new(0) # デフォルト値を 0 に設定したハッシュ
h[:x] || (h[:x] = 10)
p h #=> {}
この場合、h[:x]を参照しても存在はしていないが、デフォルト値の0が設定されているので h[:x] の値は 0 、つまり真として評価される。
そのため右項の (h[:x] = 10) は評価されずに終わる。
結果としてデフォルト値が設定されていると絶対右項は評価されず、ハッシュ内にキーが存在するわけでもないので h を出力しても空のハッシュのままになっている。
@scivola様により訂正していただきましたm(__)m
「Math.sqrt メソッド」
square root(平方根) の略で、数値の平方根を求めるメソッド。
Math.sqrt(n) は n の平方根 を返し、小数(浮動小数点数) で結果が出る。
負の数には使えない(Math.sqrt(-1)
はエラー)。
puts Math.sqrt(9) # => 3.0
puts Math.sqrt(16) # => 4.0
puts Math.sqrt(2) # => 1.4142135623730951
整数部分の平方根を求める場合
puts Math.sqrt(10).to_i # => 3 (本来 10 の平方根は約 3.16)
「マンハッタン距離」
2点間の距離を「縦・横の移動距離の合計」で測る方法。
(碁盤の目のような道路を移動するイメージをすると分かりやすい)
・計算方法
2点 (x₁, y₁) と (x₂, y₂) のマンハッタン距離は、以下の式で求められる。
公式:| x₁ - x₂ | + | y₁ - y₂ |
※計算式にある縦棒 「| |」 は、絶対値 を意味している。
つまり、x座標の差の絶対値と、y座標の差の絶対値を足すだけ。
例1: (2, 3) と (5, 1) の距離
p (2 - 5).abs + (3 - 1).abs #=> 5
「ユークリッド距離」
2点間の「直線距離」を求める方法。
・計算方法
2点 (x₁, y₁) と (x₂, y₂) のユークリッド距離は、以下の式で求められる。
公式:√ {( x₁ - x₂ )² + ( y₁ - y₂ )² }
def euclidean_distance(x1, y1, x2, y2)
Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
end
puts euclidean_distance(1, 2, 4, 6) # => 5.0
puts euclidean_distance(3, 5, 3, 5)
※三次元の場合
3点 (x₁, y₁, z₁) と (x₂, y₂, z₂) のユークリッド距離は、以下の式で求められる。
公式:√ {( x₁ - x₂ )² + ( y₁ - y₂ )² + ( z₁ - z₂ )² }
def euclidean_distance_3d(x1, y1, z1, x2, y2, z2)
Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2)
end
puts euclidean_distance_3d(1, 2, 3, 4, 6, 8) # => 7.0710678118654755
配列(ベクトル)で求めるとどの次元でも使えるので便利
def euclidean_distance_arr(p1, p2)
Math.sqrt(p1.zip(p2).map { |a, b| (a - b) ** 2 }.sum)
end
puts euclidean_distance_arr([1, 2], [4, 6]) # => 5.0
puts euclidean_distance_arr([1, 2, 3], [4, 6, 8]) # => 7.0710678118654755
「ビネの公式を使ったフィボナッチ数を求める式」
def fibonacci(n)
phi = (1 + Math.sqrt(5)) / 2
psi = (1 - Math.sqrt(5)) / 2
※小数誤差を丸める
((phi ** n - psi ** n) / Math.sqrt(5)).round
end
puts fibonacci(7) # → 13
「変数」(スネークケース)
変数もクォーテーションで囲まない。
2語以上を組み合わせた変数名をつけるときは、アンダーバー(_)を用いる。
数字で始まる変数はだめ。
「number = number + 3」の省略 →「number += 3」
「変数展開」
変数の値を文字列(“”)の中に含める方法。
文字列の中で、#{変数名}とすることで、変数を代入している値に置き換えて、文字列に含めることができる。
変数展開は文字列の連結(+)と役割としては同じだが、連結だと数値と文字列を足すことはできない。
しかし変数展開を用いると、数値の入った変数も問題なく文字列に含めることが可能。
変数を文字列に含める場合は、基本的に変数展開を使うようにする。
※ダブルクォーテーションを使った文字列の場合しか変数展開はされない。シングルクォーテーションの場合は、変数展開が行われず、そのまま文字列として出力されてしまう。
「複数代入」
複数の変数に対して一度に値を代入する方法。
x, y = 2, 3 # x = 2, y = 3
配列を使った複数代入
arr = [10, 20, 30]
a, b, c = arr # a = 10, b = 20, c = 30
a, b, *rest = arr # a = 1, b = 2, rest = [3, 4]
部分的に代入する場合
arr = [1, 2, 3, 4]
a, b = arr[0], arr[1] # a = 1, b = 2
変数の入れ替え
x = 5 y = 10
x, y = y, x # x = 10, y = 5
「if文」
ifとendで囲む。
条件不成立で実行される内容を記述する際はelseを入れる。
段階的に条件をつける場合にはelsifを入れる。(実行されるのは最初に合致した条件処理のみ)
複数条件で絞る場合には
「&&」:複数の条件がすべてtrueならtrue。
「||」:複数の条件のうち1つでもtrueならtrue。
「unless」
!=と同じ働きをする。
記述の方法は下記のようにifを用いず、条件が満たされなかった時にtrueとなり、処理が実行される。
例:a = 200
unless a == 100
puts “100ではない”
end
「後置if/unless」
endを書く必要がなく、一行でif/unless文を書くことができる。
例:a = 200
puts “後置if” if a = 200
puts “後置unless” unless a == 100
「!」
trueとfalseを反転する。
下記後置if文に注目すると本来trueでないと処理が実行されないが「!」があるのでif文も実行される。
例:x = false
puts “xはfalseです” unless x
puts “xはfalseです” if !x
「even?メソッド/odd?メソッド/zero?メソッド」
evenは偶数か判定し、oddは奇数かを判定。zeroは0かどうか判定。?でtrueかfalseかを返す。
「any? メソッド」
配列やハッシュ、範囲などの要素の中に、ブロックの条件を満たすものが1つでもあれば true を返すメソッド。
numbers = [1, 3, 5, 7, 8]
result = numbers.any? { |num| num.even? }
puts result # => true (8が偶数なので)
「is_a? メソッド」
オブジェクトが 特定のクラスやモジュールに属しているか を判定するメソッド。
num = 42
puts num.is_a?(Integer) # => true
puts num.is_a?(String) # => false
「case」(3択以上の分岐で便利)
case 変数と最初に記述し、whenの後の値と変数の値が合致した場合その処理が実行される。
複数のwhen候補と合致しても最初に合致したwhen節のみ実行される。
どことも合致しなければelseの処理を実行。(elseがなきゃ何も起こらない)
例:order = “モカ”
case order
when “コーヒー”
puts “コーヒーです”
when “モカ”
puts “モカです”
else
puts “そんなものはない”
end
【caseの後に変数を書かない方法】
when節に条件式を記述する場合はcaseの後に変数を入れない。
例:wallet = 300
case
when wallet >= 500
puts “コーヒーか豆が買えます”
when wallet >= 300
puts “豆が買えます”
else
puts “買えるものはない”
end
caseを使った変数への代入
distance = case level
when 1
1
when 2
4
when 3
5
end
※levelが1なら1動けて、2なら4動けるというような設定ができる。
普段は省略されることが多いthenも上記の例では使うことで見やすくなる
distance = case level
when 1 then 1
when 2 then 4
when 3 then 5
end
※caseとは関係ないが以下のようなハッシュにすることで同じような状況で使える。
distance = { 1 => 1, 2 => 2, 3 => 5 }
「while」
条件を満たす間(trueの間)ずっと繰り返す。
例:candy = 0
while candy > 6
candy += 1
puts “あめが#{candy}個”
end
「until」
条件が真(true)になるまでループを繰り返す 制御構造。(whileの逆)
number = 10000
number += 1 until number % 13 == 0
puts number #⇒ 10010
※10000以上で13で割り切れる最小の自然数を求めるコード
「timesメソッド」
決まった回数を繰り返すメソッド。doからendまでを「ブロック」と呼び、その中の処理を繰り返す。
※一行にして記述しても良い。
※doとendではなく代わりに{}で囲むことで同じことが可能。
例:3.times do
puts “3回”
end
3.times do puts “3回” end
3.times { puts “3回” }
【ブロックパラメータ】
ブロック(doとend)が使われるものには、ブロックパラメータ(|
と|
の間に囲まれた変数)をつけることができ、指定する場合は、次のように記述する。
timesでの例:数値オブジェクト.times do |変数|
繰り返したい処理
end
ブロックパラメータには「0」から「指定した整数-1」までが入るので、数値オブジェクトを5にした場合、0~4までの値が順番に変数に入っていく。(変数はiやnなどなんでも良い)
「to_s メソッド」
オブジェクトを文字列(String)に変換するためのメソッド。
to_sに引数を与えることで進数を変えることができる。
puts 10.to_s(2) #=> "1010" (10 を 2進数で表現)
2進数を別の方法で表現
a = 0b1010 # 10進数で 10
b = 0b1011 # 10進数で 11
※数値の前に 0b をつけることで、2 進数(バイナリ)として扱う ことができる
そのほかのプレフィックス
8進数:0o または 0
16進数:0x
⚠️ ただ結局どんな進数で代入されても内部的には 10 進数で管理されるため、全て 10進数の10を代入しているのと同じ。
例:a = 0b1010 puts a #⇒ 10
「factorial メソッド」
ある整数 n に対して n!(階乗) を計算するメソッド。
puts 5.factorial #=> 120
使えない時は
require 'mathn'
puts Math.factorial(5) #=> 120
★もしくは自分で定義する
階乗は n!=n×(n−1)×(n−2)×...×1 となるから…
def factorial(n)
(1..n).reduce(1, :*)
end
puts factorial(gets.chomp.to_i)
「階乗の末尾に「0」がいくつ付くか?」を確認する
n = gets.to_i count = 0 divisor = 5
while n >= divisor
count += n / divisor
divisor *= 5
end
puts count
※普通の整数の末尾にある「0」の数を数える場合(他の数でもその数で割ればOK)
n = 10000 count = 0
while n % 10 == 0
count += 1
n /= 10
end
puts count
※末尾にある特定の文字を数える場合は reverse して each_char を使って同じようにcountする。
「Prime ライブラリ」
素数(prime number)を扱うための組み込みライブラリ。
(内部で エラトステネスの篩(ふるい) を使って素数を効率的に求めている)
Prime モジュールを使うには、明示的に require する必要がある。
require 'prime’
Prime ライブラリで使えるメソッド
「Prime.prime? メソッド」
引数が素数かどうかを判定できるメソッド。
puts Prime.prime?(7) # => true
puts Prime.prime?(1) # => false
「Prime.each メソッド」
範囲内の素数を取得するメソッド。
Prime.each(10) { |prime| print prime } #⇒ 2357
※配列で取得も可能
p Prime.each(10).to_a #⇒ [2, 3, 5, 7]
「Prime.first メソッド」
最初から n 個 までの素数を取得したいときに使うメソッド
p Prime.first(5) #⇒ [2, 3, 5, 7, 11]
「Prime.prime_division メソッド」
素因数分解できるメソッド。
p Prime.prime_division(60) #⇒ [[2, 2], [3, 1], [5, 1]]
※60 = 2^2 × 3^1 × 5^1 なので [[2, 2], [3, 1], [5, 1]] が返される。
「Prime.instance メソッド」
素数を無限に取得するメソッド。
p prime_enum.take(10) #⇒ [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
「配列」(同系統の変数をまとめたもの)
配列は、[値1, 値2, 値3]のように作り、配列内のそれぞれの値のことを要素と呼ぶ。
配列も1つの値なので、変数に代入可能。配列を代入する変数名は複数形が多い。
配列を代入した変数をputsすると、配列の要素が1つずつ改行されて出力される。
(Integer, String, Floatなどそれぞれクラスがあるが配列はArray。)
array = Array.new(3, 10)
p array #=> [10, 10, 10]
※要素が3つで初期値10の配列が作られる。
「to_aメソッド」
配列にして返すメソッド。
例:(1..3).to_a #=> [1, 2, 3]
("a".."d").to_a #=> ["a", "b", "c", "d”]
「インデックス番号(0から始まる)」
配列の要素には、前から「0, 1, 2, ...」と数字が割り振られている。
配列の各要素は、「配列の変数名[インデックス番号]」とすることで特定できる。
変数と同様に、配列の要素の値も変数展開を用いて文字列の中に含めることが可能。
「スライス記法」
array[start, length] の形式
start
: 配列の開始インデックス(0始まり)。
length
: 取り出す要素の個数。
a = [1, 2, 3, 4, 5, 6, 7, 8] index = 2 i = 3
puts a[index, i].join(” “) #=> ”3 4 5”
※indexを「-2」などと指定した場合後ろから二つ目が開始インデックスになる。
範囲指定も可能(firstやlast, takeにはできない)
puts a[1..2].join(” ”) #=> ”2 3”
「配列に要素を追加/削除」
・配列の先頭に要素を追加する方法
配列の変数名.unshift(”要素”)
・配列の末尾に要素を追加する方法
配列の変数名.push(”要素”) もしくは 配列の変数名 << “要素”
・配列の先頭の要素を削除する方法
配列の変数名.shift
・配列の末尾の要素を削除する方法
配列の変数名.pop
[1..]
インデックス 1 以降のすべての要素を返すスライス
a = [1, 2, 3, 4, 5]
puts a[1..] #=> [2, 3, 4, 5]
[...-1]
最後のインデックス以外すべての要素を返すスライス
puts arr[...-1] # => [1, 2, 3, 4]
「drop メソッド」
先頭 n 個の要素を取り除くメソッド。
arr = [10, 20, 30, 40, 50]
puts arr.drop(2) #=> [30, 40, 50]
「drop_while メソッド」
条件に合致する要素を取り除くメソッド。
arr = [10, 20, 5, 30, 40]
puts arr.drop_while { |x| x < 25 } #=> [30, 40]
「配列の足し算/引き算」
足し算の例:a1 = [1, 2, 3] a2 = [4, 5] p a1 + a2 結果[1, 2, 3, 4, 5]
引き算の例:a1 = [1, 2, 3] a2 = [1, 3, 5] p a1 - a2 結果[2]
足し算はそのまま、二つの配列の要素が一つの配列に纏まる。
引き算はa1とa2を比べてa1にだけある要素を抽出できる。
配列の末尾に 0 を追加する
要素数を5にしたい場合
A = [1, 2, 3] n = 5
B = A + [0] * (n - A.size)
p B
「each文(繰り返し処理)」
「配列.each do |変数名|」と書き、「end」までの間に実行したい処理を書く。
each文は配列の要素の数だけ繰り返し処理を行う。
「|」で囲まれた変数に配列の要素が1つずつ入っていき、その上でeach文の中の処理が実行される。
each文内の変数名に規定はないものの、配列の変数名の単数形が多い。
※each文内の変数はeach文の中でしか使うことができない。(変数の使用可能範囲のことをスコープと呼ぶ)
「break」
繰り返し処理を途中で終わらせることができる。
例:[1, 2, 3].each do |x|
break if x == 2 ←xが2だった場合そこで繰り返し処理強制終了。
puts x
end
「next」
繰り返し処理の中でその条件のときはそこで終わりにして、次の条件に進む。
例:[1, 2, 3].each do |x|
next if x == 2 ※xが2の回は飛ばすので2は出力されない。
puts x
end
「exit」
プログラム全体の実行を終了するために使わる。
つまり、exit を呼ぶと、その時点でプログラムが完全に終了し以降のコードは実行されない。
条件分岐やループを抜けるだけではなく、プログラム自体が停止する。
「Rangeオブジェクト」
「3から5までの数を1ずつ増やして繰り返したい」といった場合に使う範囲を表すオブジェクト。
上記の例の場合「3..5」がRangeオブジェクトと呼ばれる。
Rangeにeachをつけるとその範囲の数値を代入して繰り返す。
例:(3..5).each do |x|
puts x
end
「繰り返し処理で番号をつける」
番号をつけるには、番号を保存するための変数(変数indexなど)をeach文の外で用意して、each文の処理の中で値を1だけ増やして更新するようにする。
「sizeメソッド(lengthメソッド)」
配列の要素数を返してくれるメソッド。
例:puts [1, 3, 9].size → 3が出力される
「sumメソッド」
配列の全要素を足した値を返してくれるメソッド。
例:puts [1, 2, 3].sum → 6が出力される
sizeメソッドとsumメソッドを合わせると平均値の計算が可能。(全要素の合計 / 要素数)
例:a = [1, 2, 3]
puts a.sum / a.size → 2が出力される
【少数の計算:to_fメソッド】
小数点以下を計算したい場合は、どちらか又は両方の数をto_fメソッドで少数オブジェクトに変換する。
例:例:a = [1, 1, 3]
puts a.sum.to_f / a.size → 1.6666666666666667が出力される
「uniqメソッド/uniq!メソッド」
uniqは対象の配列から重複した要素を取り除いて1つだけにした新しい配列を作って返す。
uniq!は対象の配列で重複した要素を取り除いて1つだけにする。
uniq!は元の配列自体が変わってしまうので「破壊的変更」という。
uniq例:array = [1, 1, 2] uniq!例:array = [1, 1, 2]
array2 = array1.uniq array2 = array1.uniq!
p array1 #⇒ [1, 1, 2] p array1 #⇒ [1, 2]
p array2 #⇒ [1, 2] p array2 #⇒ [1, 2]
※新しいオブジェクトが作られたか、破壊的に変更されたかは「object_idメソッド」で分かる。
これは各オブジェクトのidを返すメソッドで新しいオブジェクトならidが異なるが、破壊的変更の場合同じidとなる。
上記の例の場合uniqではarray1と2は別のidのはずだが、uniq!ではarray1と2が同じidになっている。
「uniqのブロックとto_s」
例:p [1, 3, 2, "2", "3"].uniq { |n| n.to_s } #⇒ 出力結果:[1, 3, 2]
uniqで重複が一つになった場合、その重複した要素の中で最初に記述されている値のクラスが残るよう。
今回の場合stringの要素よりもintegerの要素の方が先に記述されているのでintegerが配列に残っている。
「sampleメソッド」
配列の要素を1個ランダムに選んで返すメソッド。
例:p [1, 2, 3].sample #⇒ 1~3 の数字のどれか1つ
「shuffleメソッド」
配列の要素をランダムに並び替えるメソッド。
例:p [1, 2, 3].shuffle #⇒ [2, 1, 3] などランダム
「sortメソッド」
配列の要素を順に並び替えるメソッド。(数値の時は昇順。文字列の時は大文字優先でabc順。)
配列の要素を一度に2つずつ取り出して比較し、適切な位置へ移動させる
例:p [3, 1, 2].sort #⇒ [1, 2, 3]
※sort は内部的にクイックソートやマージソートなどのアルゴリズムを使っているので、単純に隣り合う要素を順番に比較しているわけではない。
「<=>演算子」
比較演算子 の一つで、左右の値を比較して順序を決めるために使う。
puts 5 <=> 10 # => -1 (5 は 10 より小さい)
puts 10 <=> 10 # => 0 (10 と 10 は等しい)
puts 15 <=> 10 # => 1 (15 は 10 より大きい)
sort で <=> を使う例
words = ["apple", "banana", "pear", "grape"]
sorted_by_length = words.sort { |a, b| b.length <=> a.length }
p sorted_by_length #⇒ ["banana", "grape", "apple", "pear"]
※上記の例ではブロック引数の逆にすることで 比較の順番が逆 になり、結果的に 降順(大きい順)に並び替えるようになっている。
普通にsortしてreverseすればいいだけなのでどっちでもいい
「sort_by メソッド」
与えられたブロックの評価結果を基準にソートを行うメソッド。
辞書順ソート(レキシコグラフィカルソート)
まず 第一優先のキーでソートし、次に 第二優先のキーでソートする。
pairs = [[3, 5], [2, 7], [3, 2], [1, 9]]
sorted = pairs.sort_by { |apple, banana| [-apple, -banana] }
p sorted
上記の例の場合、まず -apple でソートし、次に -banana でソートしている。
「-」がついているのは降順にするため。
この例ではまず 各apple 同士で比較して、その apple が同じ数なら、そこは banana で比較する。
そうして二重配列に要素が2つあっても大きい順に並べることができる。
上記のりんごとバナナの例を sort と <=> 演算子を使って実行すると、、、
pairs = [[3, 5], [2, 7], [3, 2], [1, 9]]
sorted = pairs.sort do |a, b|
※りんごの数が異なるなら、降順にソート
if a[0] != b[0]
b[0] <=> a[0]
else
※りんごの数が同じなら、バナナの数が多い順にソート
b[1] <=> a[1]
end
end
p sorted # => [[3, 5], [3, 2], [2, 7], [1, 9]]
※sort は2つずつ取り出して比較するメソッドなので、まず最初の a[0] != b[0] は [3, 5] の 3 と [2, 7] の 2 に対して同じではないか?という比較をしている。
内部の仕組み上、順番ではないもののその他の要素についても続けて比較していく。
sort_byを使ったハッシュの並び替え
keyで昇順ソート
info = { "apple" => 5, "banana" => 2, "cherry" => 3 }
sort = info.sort_by { |key, value| key }
ソート後にto_hでハッシュに戻す場合
puts sort.to_h # {"apple"=>5, "banana"=>2, "cherry"=>3}
※keysメソッドを使ってもどっちでもいい。
値で降順ソート
info = { "apple" => 5, "banana" => 2, "cherry" => 3 }
sort = info.sort_by { |key, value| value }.reverse
配列で返るのでそのまま出力でいい場合
sort.each { |key, value| puts "#{key}: #{value}" }
ソート後にto_hでハッシュに戻す場合
puts sort.to_h #⇒ { "apple"=>5, "cherry"=>3, "banana"=>2 }
「reverseメソッド」
配列や文字列の並び順を逆にするメソッド。
例:p [3, 1, 2].reverse #⇒ 出力結果は[2, 1, 3]
p [3, 1, 2].sort.reverse #⇒ 出力結果は[3, 2, 1]
p “いいこっか”.reverse #⇒ 出力結果は”かっこいい”
「downto()メソッド」
初期値から1ずつ引いていき、最小値になるまで繰り返し処理を行うことができ、変数には初期値から最小値になるまでの数値が格納される。
初期値.downto(最小値) { |変数| 繰り返しを行う処理 }
例:2.downto(0) { |i| p i } #⇒ 出力結果は2 1 0
※例えば(2..0).each { |i| p i } だと初期値2より最後の値(0)が低いため処理が行われないがdowntoなら大丈夫
「upto()メソッド」
初期値から最大値になるまで 1 つずつ増やしながら繰り返し処理を行うことができ、変数には初期値から最大値になるまでの数値が格納される。
※downto()メソッドの真逆なため例省略。
「step()メソッド」
初期値から限界値を超えるまで指定値を加算しながら繰り返し処理を行うことができ、変数には初期値から限界値を超えるまでの数値が格納される。
初期値.step(限界値, 指定値) { |変数| 繰り返しを行う処理 }
10.step(100, 5) do |num|
puts num
end
※10から100までの間で5ずつ増加させながら表示するコード
逆順に繰り返すことも可能
100.step(10, -10) do |num|
puts num
end
配列のインデックスを step で使うことで、特定の間隔で配列を操作可能
arr = ["a", "b", "c", "d", "e", "f", "g"]
arr.each_index.step(2) do |i|
print arr[i] #⇒ aceg
end
arr.each_with_index.step(2) do |(element, index)|
puts "#{index}: #{element} " #⇒ 0: a 2: c 4: e 6: g
end
「join()メソッド」
配列中の文字列を連結するメソッド。
繋げるだけなら他の方法もあるが、繋げる時に間へ入れる文字を引数で指定することができる。
例:puts [”アイス”, “クッキー”, “チョコ”].join(”と”) #⇒ 出力結果はアイスとクッキーとチョコ
※上記をeachで再現しようとすると末尾に不要な”と”が入ってしまい調整が難しい。
「splitメソッド」
文字列を区切り文字で分割して配列にするメソッド。
何も指定しないとスペースで分割されるものの、区切り文字を指定することでその文字で分割可能。
例:p “アイス クッキー チョコ”.split #⇒ 出力結果は[”アイス”, “クッキー”, “チョコ”]
p “アイスとクッキーとチョコ”.split(”と”) #⇒ 出力結果は[”アイス”, “クッキー”, “チョコ”]
⚠️区切り文字の指定には正規表現を使うこともでき、複数の区切り文字の指定が可能。
例:p “アイス/クッキー&チョコ”.split(/[/&]/) #⇒ 出力結果は[”アイス”, “クッキー”, “チョコ”]
「mapメソッド」
配列の各要素へ処理を行い、変換してできた要素を持った、新しい配列を作るメソッド。
mapメソッドを用いると例2のように要素を他の種類のオブジェクトへ変換することも可能。
例:result = [1, 2, 3].map do|x|
x * 2
end
p result #⇒ 出力結果は[2, 4, 6]
例2:p result = [100, 200, 300].map { |x| “#{x}円” }
#⇒ 出力結果は[”100円”, “200円”, “300円”]
※eachメソッドはブロックで処理を行うことが目的で、mapメソッドは各要素を変換した新しい配列を得ることが目的
そしてmapメソッドのように、各要素に対してあるメソッドを呼び出すだけのブロックは特別に短く記述ができる。
例:p result = [”abc”, “123”].map { |text| text.reverse } #⇒ 出力結果は[”cba”, “321”]
⬇︎ さらに短く(シンボルto_proc)
p result = [”abc”, “123”].map(&:reverse) #⇒ 出力結果は[”cba”, “321”]
「selectメソッド/ filterメソッド」
条件式に一致した要素を取得するためのメソッド。
例えば以下の例は、各要素に対してブロックを評価した値が真であった要素を全て含む配列を返す。
例:[1, 2, 3, 4, 5].select {|x| x.even?} #⇒ 出力結果は[2, 4]
=[1, 2, 3, 4, 5].select(&:even?)(「&:」の書き方)
※find は条件に一致する最初の要素を返すのに対し、select/filter は条件に一致するすべての要素を配列で返す。
「block_given?メソッド」
ブロックを渡されたかどうかを判別するメソッド。
ブロックを渡すメソッド自体はメソッド定義に何も書き加えることなく呼び出すことは可能なので、そのメソッドの中でblock_given?を実行するとブロックの有無を判定してくれる。
「yield」
メソッドの中で実行する処理を、メソッド呼び出し元にブロックで書くことができる。
例:def dice
if block_given?
yield
else
puts [1, 2, 3, 4, 5, 6].sample
end
end
dice #⇒ 出力結果は1-6のどれかの数字
dice do
puts[4, 5, 6].sample
end #⇒ 出力結果は4-6のどれかの数字
「ブロックを引数で受け取る」
ブロックを受け取る引数は先頭に&を書き、変数に代入されたブロックはcallメソッドで実行可能。
def foo(&b)
b.call
end
foo do
puts “block”
end #⇒ 出力結果はblock
※この時ブロックをProcというオブジェクトとして扱っておりブロックの処理をオブジェクト化したもの。
「first メソッド」
配列の最初の要素、または指定した個数の要素を取得する。
arr = [0, 8, 1, 3]
puts arr.first #⇒ 0(最初の要素を取得)
puts arr.first(2) #⇒ 0と8が改行されて出力(最初の2つを取得)
puts arr.first(2).join(" ") #⇒ ”0 8”を出力(最初の2つを半角スペースで繋げて出力)
「last メソッド」
配列の最後の要素、または指定した個数の要素を取得する。
arr = [0, 8, 1, 3]
puts arr.last(2).join(" ") #⇒ "1 3”を出力(後ろから2つを半角スペースで繋げて出力)
「take メソッド」
先頭から指定した個数の要素を取得する。
puts arr.take(2).join(" ") #⇒ ”0 8”を出力(最初の2つを半角スペースで繋げて出力)
※first と似ているが引数なしでは使えない
※指定した数が配列の長さより大きくてもエラーにならず、あるだけそのまま返す
arr = [0, 8, 1, 3]
puts arr.take(10).join(" ") #⇒ ”0 8 1 3”を出力
「delete_at メソッド」
arr = [10, 20, 30, 40, 50]
インデックス2(30番目の要素)を削除
removed_element = arr.delete_at(2)
p arr # => [10, 20, 40, 50]
puts removed_element # => 30(削除された要素が返される)
「count メソッド」
文字列や配列に含まれる要素の出現回数をカウントするためのメソッド。
要素数全体を数える
arr = [1, 2, 3, 2, 1]
result = arr.count # => 5
その配列内に特定の要素が何回現れるかを取得
arr = [1, 2, 3, 2, 1]
2 が何回出現するか
result = arr.count(2)
p result # => 2
Enumerable に含まれる count メソッドは、ブロックの条件に一致する要素を数える。
(繰り返し処理をする用途で用いられるcountメソッドってこと)
arr = [1, 2, 3, 4, 5]
2より大きい要素の個数をカウント
result = arr.count { |x| x > 2 }
p result # => 3
「min_by メソッド」
ブロックの結果が最小となる要素を返す メソッド。
min_by を使うことで 「ある条件に基づいて最小を決める」 ことができる。
words = ["apple", "banana", "cherry"]
puts words.min_by { |word| word.length } # => "apple" (文字数が最小の単語)
※上記のケースで同じ文字数の要素があったら最初に見つけた要素を返す。
people = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 35 }
]
youngest = people.min_by { |person| person[:age] }
puts youngest[:name] # => "Bob"(最も若い人)
「max_by メソッド」
ブロックの結果が最大となる要素を返す メソッド。
max_by を使うことで 「ある条件に基づいて最大を決める」 ことができる。
身長差が最大のペアを探すケース
身長差が近いものが隣り合うようにsortし、each_consで二組のペアにしてその差が一番大きいものをmax_byで取得している。
a = [170, 160, 180, 165, 175].sort
pair = a.each_cons(2).max_by { |a, b| (a - b).abs }
puts pair
★最大のペアを探した上でその結果を返すなら
puts a.each_cons(2).max_by { |a, b| (a - b).abs }.inject(:-).abs
「ハッシュ」
Rubyでは{}
がハッシュのリテラル(記法)として使われる。
{キー1 => 値1, キー2 => 値2}のように作り、それぞれの値にキーと呼ばれる名前をつける。
値の部分には配列や別のハッシュを置くことも可能。
ハッシュも1つの値なので、変数に代入可能。
ハッシュの各要素の値は、対応するキーを使って、ハッシュ[キー]とすることで用いることができる。
ハッシュ[新しいキー] = 値と書くことで、ハッシュに新しい要素が追加される。
ハッシュ[既にあるキー] = 値と書くことで、キーと対応している値を更新できる。
「Hashクラス」
・空のハッシュを生成
hash = Hash.new
p hash # => {}
・デフォルト値の設定
hash = Hash.new(0) #
p hash[:unknown] # => 0
※存在しないキーを参照すると 0 を返す。
⚠️デフォルト値が参照型(配列・ハッシュなど)にする場合は注意⚠️
❌:h = Hash.new([])
h[:a] << 1 h[:b] << 2
p h #⇒ {} (キーが登録されない)
p h[:a] #⇒ [1, 2] すべてのキーで同じ配列が共有されてしまう
★デフォルト値をブロックで指定すると解決できる!
⭕️:h = Hash.new { |hash, key| hash[key] = [] }
h[:a] << 1 h[:b] << 2
p h #⇒ { :a=>[1], :b=>[2] }
・配列やキー・値のペアを元にしてハッシュを作成する。
キーと値のペアを直接指定
h = Hash[:a, 1, :b, 2]
p h #⇒ {:a=>1, :b=>2}
→ [:a, 1, :b, 2] のように「キー, 値, キー, 値…」の形で指定する。
「to_h メソッド」
配列や他の Enumerable の各要素が [キー, 値] の組になっている場合、それらをハッシュに変換するためのメソッド。
元が配列の場合
x = ["a", "b", "c"]
result = x.map { |key| [key, 0] }.to_h
p result # => { "a" => 0, "b" => 0, "c" => 0 }
元が二重配列の場合
array = [[”a”, 0], [”b”, 0], [”c”, 0]]
hash = array.to_h
p hash # => { “a” => 0, “b” => 0, “c” => 0 }
このように、各要素がペアになっている配列をハッシュに変換できる。
to_hで変換方法を指定する
a = 3.times.map { gets.chomp.split }.to_h { |key, value| [key, value.to_i] }
※3行で入力された”アイス 200”などの値をハッシュ化するときに200をto_iにして格納。
ハッシュの使い方の応用
例1:
n = gets.chomp.split.map(&:to_i)
商品名と価格をハッシュに格納
products = {}
n.times do
name, price = gets.chomp.split
products[name] = price.to_i
end
例2:
n = gets.chomp.to_i
s = n.times.map { gets.chomp } # ["Ito", "Suzuki", "Sato"]
info ハッシュを s の名前リストで初期化(すべて 0)
info = s.map { |name| [name, 0] }.to_h #⇒ { "Ito" => 0, "Suzuki" => 0, "Sato" => 0 }
例3:
names = ["John", "Eve", "Ken"] ages = [11, 22, 33]
info = {}
names.each_with_index { |name, index| info[name] = ages[index] }
p info # => {"John"=>11, "Eve"=>22, "Ken"=>33}
⬇︎ Hashクラス と zip を使う形
info = Hash[names.zip(ages)]
p info # => {"John"=>11, "Eve"=>22, "Ken"=>33}
例4:
words = gets.chomp.split
seen = {}
words.each do |word|
if seen[word]
puts "already"
else
puts word
seen[word] = true
end
end
※ハッシュのvalueにtrueを入れることで条件分岐に使える。
「fetch メソッド」
Hash クラスに組み込まれているメソッドで、指定したキーに対応する値を取得するために使う。
hash = { "apple" => 100, "banana" => 200 }
puts hash.fetch("apple") # => 100
存在しないキーに対してデフォルト値を指定する
puts hash.fetch("orange", -1) # => -1
配列の全要素が何回現れるかをカウントする場合、ハッシュを使って要素ごとの出現回数を集計
arr = ["apple", "banana", "apple", "cherry", "banana", "banana"]
count = {}
arr.each do |item|
※||= は左辺が nil または false の場合に右辺を代入する
count[item] ||= 0
count[item] += 1
end
p count # => {"apple"=>2, "banana"=>3, "cherry"=>1}
⬇︎ featchを使用する形
arr.each do |item|
count[item] = count.fetch(item, 0) + 1
end
「keys メソッド」
Hashオブジェクトにおいて、ハッシュの「キー」の一覧を配列として取得するためのメソッド。
keys を使うことでハッシュの全てのキーだけを配列として返す。(値は返さない)
keys メソッドの使いどころ
①キーの一覧を取得する
②キーをソートする
③存在するか確認する など
①キーの一覧取得
info = { "apple" => 5, "banana" => 2, "cherry" => 3 }
p info.keys #⇒ ["apple", "banana", "cherry"]
※ハッシュのキーは 挿入順 に保持されるため、keys で返される順番はハッシュにデータが追加された順番に対応している。
eachと組み合わせて取得したキーでバリューも出力
info = { "apple" => 5, "banana" => 2, "cherry" => 3 }
info.keys.each do |key|
print "#{key}: #{info[key]} " #⇒ apple: 5 banana: 2 cherry: 3
end
②キーをソート
info = { "banana" => 2, "apple" => 5, "cherry" => 3 }
puts info.keys.sort #⇒ ["apple", "banana", "cherry"]
③存在の確認
info = { "apple" => 5, "banana" => 2, "cherry" => 3 }
puts info.keys.include?("apple") #⇒ true
「values メソッド」
ハッシュのすべての値を配列として取得 するメソッド。
hash = { a: 10, b: 20, c: 30 }
p hash.values #⇒ [10, 20, 30]
・配列の中にあるかを確認
hash = { apple: 100, banana: 200, orange: 150 }
p hash.values.include?(200) #⇒ true
・合計値/最小値/最大値を求める
hash = { apple: 100, banana: 200, orange: 150 }
p hash.values.sum #⇒ 450
p hash.values.min #⇒ 100
p hash.values.max # => 200
・特定の値を持つキーを取得
hash = { apple: 100, banana: 200, orange: 100 }
max = hash.values.max
keys = hash.select { |_, v| v == max }.keys
p keys #⇒ [:banana]
「ハッシュのネスト」
直に作成
users = { "alice" => { age: 25, city: "Tokyo" }, "bob" => { age: 30, city: "Osaka" } }
puts users["bob"][:age] #⇒ 30
ネストしたハッシュをeachで回す
users.each do |name, info|
print "#{name} (#{info[:age]}歳) - #{info[:city]} "
#⇒ alice (25歳) - Tokyo bob (30歳) - Osaka
end
直接入れ込みしてネスト
original = { a: 1, b: 2 } new = {}
new_hash[:nest] = original
puts new #⇒ { :nest => { :a => 1, :b => 2 } }
eachを使ってoriginal の各キー・値を new[:nest] にコピーしてネスト
original = { a: 1, b: 2 } new = { nest: {} }
original.each do |key, value|
new[:nest][key] = value
end
puts new #⇒ {:nested=>{:a=>1, :b=>2}}
動的にネストを作成(デフォルト値({})付き)
data = Hash.new { |hash, key| hash[key] = {} }
data["apple"][:color] = "red"
data["banana"][:color] = "yellow"
「merge メソッド」
Hashオブジェクトに対して使用でき、引数のハッシュの要素を自身のハッシュに統合するためのものメソッド。
hash1 = { a: 1, b: 2 }
hash2 = { b: 3, c: 4 }
hash1.merge!(hash2)
puts hash1 #⇒ {:a=>1, :b=>3, :c=>4}
上記の例では キー b が両方にあるため、hash2 の値 3 に上書きされる。
merge! だと破壊的変更ができる。
例えば merge! にブロックを渡す
hash1 = { a: 1, b: 2 }
hash2 = { b: 3, c: 4 }
hash1.merge!(hash2) { |key, old_val, new_val| old_val + new_val }
puts hash1 #⇒ {:a=>1, :b=>5, :c=>4}
b の値が 2 と 3 で競合しているが、old_val + new_val(2 + 3)が適用されて値は 5 になる。
ネストしたハッシュにも merge 可能
original = { a: 1, b: 2 } new = { nest: { c: 3 } }
new[:nest].merge!(original)
puts new_hash #⇒ {:nest=>{:c=>3, :a=>1, :b=>2}}
「シンボル」(シンボルクラス)
キーの部分を「"」や「'」で囲む代わりに、先頭にコロン「:」を付けた書き方をすることもでき、「:キー」という書き方のことをシンボルと言う。
(文字列とシンボルは厳密には異なるものの基本的には同じ)
ハッシュのキーをシンボルで記述したら、取り出す時もシンボルで指定が必要。
ハッシュのキーにシンボルを用いる場合、「:key =>」を「key:」というように省略することができる。
(あくまでシンボルなので、要素を取得する場合には「:key」とする。)
★[:1, :2, :3]というシンボルを作る
symbols = (1..3).map { |i| i.to_s.to_sym }
p symbols # => [:1, :2, :3]
配列を組み合わせてハッシュを作る
keys = [:a, :b, :c]
values = [1, 2, 3]
hash = keys.zip(values).to_h
p hash # => {:a=>1, :b=>2, :c=>3}
「to_symメソッド」
文字列をシンボルに変換するメソッド。
シンボルはメモリ効率がよく、定数的な意味を持つため、ハッシュのキーなどに使われる。
result = x.map { |key| [key.to_sym, 0] }.to_h
p result # => { :a => 0, :b => 0, :c => 0 }
ハッシュ外での使用
str = "example” sym = str.to_sym
puts sym # => “example”
ハッシュ外でもシンボルを使うことでメモリ効率がいい
str1 = "hello” str2 = "hello”
sym1 = :hello sym2 = :hello
p str1.object_id == str2.object_id # => false(毎回違うメモリ)
p sym1.object_id == sym2.object_id # => true(同じシンボルは1つだけ)
「Set」
重複しない値を格納するコレクション。つまり同じ値を2回以上追加しても、1回しか格納されない。
順番を保証しない。配列のように順番でアクセスすることができない。
setを出力するには to_a で配列にしたり each を使って1つずつ出力する必要がある。
そしてrequire ‘set’ が必須。
require ‘set’
set = Set.new([1, 2, 2, 3, 3, 3])
puts set # => #
to_a使用
puts set.to_a.join # => 123
each使用
set.each { |item| print item } # => 123
また Set.new の引数は Enumerable(反復可能なオブジェクト)でなくてはならない。
a = [1, 2, 3]
❌ set = Set.new(a[0])
⭕️ set = Set.new([a[0]])
「add メソッド」
Set クラス のメソッドで、セットに新しい要素を追加するためのメソッド。
require 'set’
s = Set.new([1, 2, 3])
s.add(4) # セットに 4 を追加
s.add(2) # 2 は既にあるので変化なし
puts s.to_a.join # => 1234
「nil」
「何もない」という値は、Rubyでは「nil」(読み方:ニル)という特別な値で表現される。
例えばハッシュに存在しないキーの値を指定して取り出そうとした時に、何も表示されない状態。
ifの条件の中でハッシュが用いられた際、nilの場合はfalseとして扱われる。
「default メソッド」
nilになるキーを指定した場合の値をdefault=メソッドで設定できる。
例:menu = {coffee: 300, water: 100}
menu.default = 0
p menu[:tea] #⇒ 出力結果は0
「compact メソッド」
配列内の nil 要素を取り除いた新しい配列を返すメソッド。
arr = [1, nil, 2, nil, 3]
new_arr = arr.compact
p new_arr # => [1, 2, 3]
p arr # => [1, nil, 2, nil, 3] (元の配列は変更されない)
※compact!で破壊的変更になる。
「deleteメソッド」
ハッシュからキーと値を削除するメソッド。
ハッシュ.delete(キー)と書くことで指定したキーと値を削除する。
「ハッシュの繰り返し処理」
||に挟むブロックの中の変数をキー用と値用の2つ指定することができる。
ハッシュ.each do |キーの変数, 値の変数|
繰り返し実行する処理
end
例:hash = { a: 1, b: 2, c: 3 }
hash.each do |key, value|
puts "Key: #{key}, Value: #{value}"
end
#⇒ Key: a, Value: 1
Key: b, Value: 2
Key: c, Value: 3
※キーだけを繰り返し処理したい場合はeach_keyメソッドを使い、
値だけを繰り返し処理したい場合はeach_valueメソッドを使う。
「配列内ハッシュ」
配列の要素は、文字列や数値だけでなく、ハッシュも使うことができる。
配列[インデックス番号]でハッシュを取り出せるので、そのハッシュを代入した変数を使い、変数[キー]とすることでハッシュの要素の値を用いることができる。
この扱いは省略可能。配列[インデックス番号][キー]で、特定のハッシュ要素を抽出。
配列={ハッシュ, ハッシュ} → 変数=配列[インデックス番号] → puts 変数[ハッシュキー]
⬇︎省略
配列={ハッシュ, ハッシュ} → 配列[インデックス番号][ハッシュキー]
each文でも配列内がハッシュなら繰り返される変数にはハッシュが代入される。
実行処理のところで、変数[ハッシュキー]とすればハッシュの要素が出力される。
「mergeメソッド」
デフォルト値はselfの設定のまま、selfとothersのハッシュの内容を順番に統合した結果を返す。
self と others に同じキーがあった場合はブロック付きか否かで判定方法が異なる。
ブロック付きのときはブロックを呼び出してその返す値を重複キーに対応する値にし、
ブロック付きでない場合は常に others の値を使う。
※othersがハッシュではない場合、othersのメソッドto_hashを使って暗黙の変換を試みる。
例:h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 246, "c" => 300 }
h3 = { "b" => 357, "d" => 400 }
h1.merge(h2) #=> {"a"=>100, "b"=>246, "c"=>300}
h1.merge(h2, h3) #=> {"a"=>100, "b"=>357, "c"=>300, "d"=>400}
h1.merge(h2) { |key, oldval, newval| newval - oldva } #=> {"a"=>100, "b"=>46, "c"=>300}
h1.merge(h2, h3) { |key, oldval, newval| newval - oldval } #=> {"a"=>100, "b"=>311, "c"=>300, "d"=>400}
h1 #=> {"a"=>100, "b"=>200} ←元のまま
「メソッドの定義方法」(スネークケース)
「def メソッド名」と「end」の間に、まとめたい処理を書くことでメソッドをつくることができ、これを「メソッドを定義する」と言う。
定義したメソッド名を記述することで、メソッドの処理内容を実行することができる。
メソッドを呼び出す際は「オブジェクト.メソッド」の形式で、このオブジェクトの部分をレシーバと呼ぶ。
「引数」
メソッドに与える追加情報。メソッドを呼び出す時、一緒に引数を渡すことで、メソッドの中でその値を利用できる。
「def メソッド名(引数名)」で引数を指定し、そこへ引数を渡してメソッドを呼び出すには「メソッド名(値)」とする。
引数はメソッド内では変数のように使用する。
また分かりやすいように受け取りでも呼び出しでも()で引数を囲っているが、省略可能。
※引数のあるメソッドでは引数を渡さずして呼び出すことはできない。
※メソッド内で定義した引数や変数はメソッドの中でしか使うことができない。(スコープ)
また引数は複数受け取ることができ、複数の引数を渡してメソッドを呼び出すには、定義するときと同じように、コンマ(,)を用いる。
メソッドを定義したときの引数の順番と、渡す引数の順番は対応しているので呼び出すときは、引数の順番に注意。
【引数におけるデフォルト値設定】
引数を省略してメソッドが呼び出された時に使われる値の指定で、メソッド定義の際「引数 = デフォルト値」と書く。
例:def order(item = “コーヒー”)
“#{item}をください”
end
puts order #⇒ 出力結果はコーヒーをください
「キーワード引数」
キーワード引数を用いた書き方では、呼び出し側で引数を明記でき、値がどの引数に入るのかがわかりやすくなる。
また呼び出しをするとき、キーワード引数を使えばどんな順番で記述しても問題ない。
通常のメソッドの書き方に加えて、
① 定義側で、引数の後にコロン(:)を付ける
② 呼び出し側で、値の前に「引数名:」を書く
とすることで、キーワード引数を持つメソッドになる。
【キーワード引数におけるデフォルト値設定】
「引数: デフォルト値」と記述
例:def order(item:, size: “ショート”)
“#{item}を#{size}サイズでください”
end
puts order(item: “カフェラテ”) #⇒ 出力結果はカフェラテをショートサイズでください
「戻り値」
メソッドの中で「return 値」と書くことで処理結果を呼び出し元で受け取る。
ただ基本的にreturnは省略されていることが多く、省略した場合はメソッド内の最後の式を評価した値が戻り値となる
returnは、メソッドの処理を終了させる性質も持っていて、returnの後にあるメソッドの処理は実行されないので注意。
if文で使うような条件式をreturnすると、その条件式の結果として得られる真偽値(trueまたはfalse)を返す。
※真偽値を返すメソッドは、メソッド名の末尾に「?」をつける慣習がある。
「Procとlambda」
コールバック関数のように使えるRubyのオブジェクト。
厳密にはコールバック関数そのものではなく、無名関数(ブロック)をオブジェクトとして扱うためのもの。
ブロックはメソッドの引数として処理を渡せる機能であるが、同じような処理が複数あるのなら、あらかじめそれを定義しておき、それを使いましたい。
そのための機能がProcとlambda。
「lambda構文」
lambdaはProc.newとほぼ同じ動作。
引数の数を厳密にチェックしているため、呼び出す時に引数の数が違うとエラーとなる。
lambda内でのreturnは、lambda自身のreturnであるものと見なされため、lambdaを呼び出したコードには影響しない。
「Proc」
Procオブジェクトはdefとほぼ同じ。
defをインスタンスにしたもので、Proc.newメソッドによってdefのインスタンス変数を生成できる。
引数の扱いは柔軟で、defと同じように引数が足りない場合でもnilが引数に代入されてエラーは起きない。
proc内でreturnは呼び出し元でreturnを実行したと見なされるため、procを呼び出したメソッドやブロックからも抜けることになる。
「クラスの定義」(キャメルケース)
クラスは「class クラス名」で定義する。
クラス名は必ず大文字で始め(定数と同じ)、「end」を書く必要があることに注意。
クラスはオブジェクトの種族を表すもので、オブジェクトはその種族の用意したメソッドを使用可能。
例えばeven?メソッドはIntegerクラスにあるため、StringやArrayクラスには適用できない。元々rubyでIntegerクラスの中にeven?メソッドが用意されているから使える。
「オブジェクト.class」で属するクラスを表示する。
※オブジェクトのクラスによって呼び出せるメソッドが異なるが、methodsメソッドで呼び出せるメソッド一覧を表示できる。(自分で作ったクラスのメソッドも確認可能)
class User
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
user1 = User.new("Alice", 25)
puts user1.age #⇒ 25
動的にクラスを生成する
User = Class.new do
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
「self」
selfの特殊な使い方
レシーバがどのオブジェクトかを調べることができる。
その場所でメソッドを呼び出した時のレシーバを返す。
selfでレシーバを取得できることを利用して、例で使っているtoppingメソッドを省略せずにオブジェクト.メソッドの形式で書くとself.toppingとなる。
①と②でobject_idメソッドを使ったら同じidになるはず。
ただ以下例のようにインスタンスメソッドでのselfはそのクラスのインスタンスのことだが、クラスメソッドでのselfはそのクラスとなる。
インスタンスかクラスかは最初が大文字かなどで判断し、例えば同じ@name変数を使っていてもクラスメソッドのものかインスタンスメソッドのものかで別物。
例:class Drink
def name
p self ②←①と同じ出力結果
“モカ” + topping
end
def topping
“エスプレッソ”
end
end
drink = Drink.new
p drink ①←②と同じ出力結果
puts drink.name #⇒ 出力結果は”モカエスプレッソ”
「インスタンス(オブジェクト)の生成」
クラスを元に新しくインスタンスを生成するには「クラス名.new」とする。
「変数名 = クラス名.new」で、生成したインスタンスを変数に代入。
このインスタンスに情報をもたせるには、クラスで用意したインスタンス変数に値を代入する。
具体的には「インスタンス.変数名 = 値」もしくは「@変数名 = 値」とすることで、そのインスタンス変数に値をセットできる。
※インスタンスとオブジェクトはほぼ同じ意味だが、クラスから作られたことを強調したい時に呼びがち。
「インスタンス変数」
変数名の頭に@をつけて作るインスタンス(オブジェクト)がもつ変数。
ローカル変数と異なり同じオブジェクトであれば、複数のメソッドを跨いで使うことができる。
(寿命はそれを持つオブジェクトの寿命を同じ)
※例えば通常メソッド内で変数の定義をしたら、そのメソッド内でしか使うことができないが、@をつけるインスタンス変数なら別のメソッドでもその変数を利用可能。
一つのクラスにインスタンス変数は複数定義可能。
【attr_accessor】
①インスタンス変数をオブジェクトの外で取得するには、クラスにインスタンス変数を戻り値とするメソッドを追加してそれを呼び出す必要がある。
②またインスタンス変数へ代入するためのメソッドの追加も必要。(後述の例だとname=のようにメソッドを記述し、nameの書き込み専用メソッドとなる)
ただ上記2点(getter/setter)を簡単に記述できるのが「attr_accessor シンボル」。
「attr_accessor :name」とした場合、:nameがインスタンス変数となる。
「attr_accessor 」は「attr_reader」と「attr_writer」が合わさったもの。
本来以下の例のように定義するインスタンス変数を省略した形。
(オブジェクト外で使うなら)
例:①def name
@name ➡︎ attr_reader :name
end
⬇︎
attr_accessor :name
⬆︎
②def name=(text)
@name = text ➡︎ attr_writer :name
end
※attr_reader が getter、attr_writer は setter、attr_accessor は両方の役割を持つ。
「instance_valiablesメソッド」
そのオブジェクトが持つインスタンス変数の変数名一覧を取得するメソッド
ただインスタンス変数は代入した時には作られるので注意。
「インスタンスメソッド」
インスタンスから実行でき、個別具体的な処理を書くときに使う。
クラスの中で定義されたメソッドで、インスタンスに対して使うように呼び出す。
具体的には、「インスタンス.メソッド名」のようにすることで呼び出す。
「インスタンスメソッド内でインスタンス変数を扱う」
特殊な変数「self」を用いて「self.変数名」とすることで、インスタンス変数を扱うことができるようになる。
変数「self」に呼び出したインスタンス自身が代入される。
また「self.変数名 = 値」もしくは「@変数名 = 値」でインスタンス変数に値を代入できる。
「initializeメソッド」
他のインスタンスメソッドと同じように定義する。
「クラス名.new」でインスタンスが生成された直後に、initializeメソッドが呼び出されその中の処理が実行される。
「クラス.new」に対して引数を渡すことで、initializeメソッドにその値を渡すことも可能。
「クラスメソッド」
クラスから実行でき全体に関する処理を書くときによく使う。
「def self.メソッド名」もしくは「def クラス名.メソッド名」(クラス名を変えたらここも変える必要が出る短所あり)で定義可能。
また「class << self」と書いてから通常のメソッドを定義し、「end」で囲むことで複数のクラスメソットをまとめて書くのに便利。
インスタンスメソッドとの違いはメソッド名の前にクラス名を書く必要がある点で、つまりレシーバを「インスタンス」とするか「クラス」とするかの違い。
クラスメソッドの中で同じクラスのクラスメソッドを呼ぶ時はメソッド名だけでOK。
ただインスタンスメソッドからクラスメソッドを呼ぶことはできても、クラスメソッドからインスタンスメソッドを呼ぶことはできない。
※クラスメソッドはクラスが実行するためオブジェクトを作る必要はない。
【#記法と.記法】
インスタンスメソッドとクラスメソッドをマニュアルなどで記述する際の記法。
インスタンスメソッドはクラス名#メソッド名のように#を間に入れる。
クラスメソッドはクラス名.メソッド名のように間に.を入れるか、::を入れる場合もある。
「クラス変数」
クラスで共有される変数のことで、「@@変数名」のように@2つで記述。インスタンス変数と同様「attr_」などで定義が必要。
クラスのインスタンス間で値を共有する場合に便利だが、継承されたクラス間でも共有される。
ただし、クラス変数は継承先のクラスで上書きされる可能性があるため、予期しない振る舞いを引き起こすことがあり、使用は慎重に行う必要がある。
※実務でクラス変数はあまり使用されないよう。
「インスタンスの配列」
クラスから生成したインスタンスも、配列の要素にすることが可能。
各インスタンスを要素とする配列を変数に代入しその配列に対してeach文を用いる。
「Struct」
簡単に データをまとめて扱うための軽量なクラス。
例:Struct.newでUserというクラスを作る
User = Struct.new(:name, :age, :birth, :state)
user1 = User.new("Alice", 25, "08/08", "Tokyo")
puts user1.age #⇒ 25
puts user1[2] #⇒ "08/08"
Struct を使うと、attr_accessor や initialize を書かなくても、自動でゲッター・セッターが作られるので楽。
配列のようにインデックスでアクセス可能。
データ管理だけでなくメソッドも追加できる
User = Struct.new(:name, :age) do
def greet
"Hello, my name is #{name}!"
end
end
user1 = User.new("Alice", 25)
puts user1.greet #⇒ "Hello, my name is Alice!”
ただフィールドを後から増やすことはできないので、上記の例だとnameとage以外のものはUserで扱うことができない。
ハッシュとStructの融合
Person = Struct.new(:age, :birth)
hash = {}
x = ["Alice", "Bob", "May"]
x.each do |name|
hash[name] = Person.new(30, "1994-01-01")
end
※それぞれの名前をkeyにして、valueにStructで作ったクラスのインスタンスを当てている。
puts instances["Alice"].age #⇒ 30
puts instances["Bob"].birth #⇒ "1994-01-01"
rubyではインスタンス名を含む変数名を動的に変更することはできないものの、上記のようにハッシュのキーを動的に変更して対応することは可能。
「継承」
あるクラスを元にして新たなクラスをつくること。
継承元をスーパークラスと呼び、継承先はサブクラスと呼ぶ。
例:class クラス名 < スーパークラス名
end
★もし子クラスのインスタンスが作られた時、親クラスにのみinitializeメソッドがあった場合それが実行される。
【ancestorsメソッド】
そのクラスの継承関係(親クラス群)を表示するメソッド。
「親子のクラスでの同名メソッド」
親子のクラスで同名のメソッドがある時は自分のメソッドが優先される。
(正確には自分のクラスから該当メソッドを探していき親へ親へと辿っていく中で最初にヒットしたメソッドを優先)
【super】
親クラスの同名メソッドを呼び出して、戻り値を返す。変数のように扱える。
「private」
privateをクラスに記述すると、クラスのそれ以降に定義したメソッドはprivateなメソッドになる。
つまりレシーバを指定してメソッドを呼び出すという方法では呼び出せなくなる。
結果的にメソッドを呼び出すことができる場所をクラス定義の中だけに限定できる。
逆にクラス定義の外でも呼び出せるように戻したい場合は、publicと記述するとその下はpublicなメソッドになる。
(一般的には最初にpublicなメソッドをまとめて書き、その後privateを記述してまとめる)
「クラスメソッドのprivate」
クラスメソッドをprivateにしたい場合はdefの前(同じ行)に「private_class_method」を入れる。
ただ「class << self」でまとめてクラスメソッドを定義する方法であればprivateを利用することができる。
「モジュール」
モジュールとは主にメソッドを共同利用するための部品。
複数のクラスで共同利用するための手順
①モジュールを作る
②モジュールにメソッドを定義する
③モジュールのメソッドをクラスで使う
①モジュールを作る
モジュールの定義では先頭を大文字から始めるキャメルケースで書く。
ただモジュールはインスタンスを作ることができないため、インスタンスを作る時はクラス、作らない時はモジュールを使う。
module モジュール名
end
②モジュールにメソッドを定義する
モジュールはクラスと同じようにインスタンスメソッドやクラスメソッド(正確にはモジュールメソッド)を定義できる。
module WhippedCream
def whipped_cream
@name += “ホイップクリーム”
end
end
③モジュールのメソッドをクラスで使う
クラス定義の中で「include モジュール名」を使いモジュールを引数で指定し、クラスにモジュールをインクルードする。
これによりそのクラス内でモジュールが、インスタンスメソッドとして利用可能になる。
※クラスメソッドとして利用する場合には「extendメソッド」を使う。「extend モジュール名」と記述。
include同様引数にモジュールを指定することでクラスメソッドとして利用できるようになる。
呼び出しでインスタンスがなければextend。
「モジュールにクラスメソッドを定義する」
モジュールにはクラスメソッドや定数を定義して呼び出す使い方もある。呼び出し方はクラスと同様。
クラスメソッドの定義例)
module WhippedCream
def self.info
“トッピング用ホイップクリーム”
end
end
puts WhippedCream.info #⇒ 出力結果はトッピング用ホイップクリーム
定数の定義例)
module WhippedCream
price = 100
end
puts WhippedCream::Price #⇒ 出力結果は100
※モジュールの中で定義した定数を使う際は「モジュール名::定数名」と::で繋げる。
「Enumerable」
ArrayクラスやHashなどにインクルードされてメソッド群を提供しているモジュール。
Enumerableを持つオブジェクトをEnumerableオブジェクトといい、
Array / Range / Hash / Set / Enumerator などが挙げられる。
(each で繰り返し処理ができるもの は Enumerableオブジェクト)
eachやmap, select, group_by, none?メソッド(配列に対して「全要素が該当しない」ことを調べる)などがその中の一つ。
このモジュールはインクルード先のクラスにeachメソッドが定義されていれば、自分の作ったクラスでも利用可能。
「Mathモジュール」
Rubyが用意しているモジュールの一つ。
sinやcosなどの数学計算用のクラスメソッドとPI(円周率)などの定数が定義されている。
例:puts Math::PI #⇒ 出力結果は3.141592653589793
puts Math.cos(Math::PI) #⇒ 出力結果は-1.0
※Mathモジュールの中の定数PIを使いたい時は自分でモジュールを定義した時と同様「::」でつなぐ。
「名前空間を作る」
同じクラス名を複数の場所で使いたいが、別のクラスなので別々に定義して呼び分けたい場合に使うモジュール。
単純にモジュールを定義する際に、その名前を変えて同様の処理のモジュールを複数作っておくことで、呼び出す際にモジュール名で呼び分けることができる。呼び出す際は「モジュール名::クラス名」と書くことでクラスを使い分ける。
例:module BecoCafe module MachuCafe
class Coffee class Coffee
def self.info def self.info
“深みのあるコーヒー” ”甘みのあるコーヒー”
end end
end end
puts BecoCafe::Coffee.info #⇒ 出力結果は深みのあるコーヒー
puts MachuCafe::Coffee.info #⇒ 出力結果は甘みのあるコーヒー
※プログラムで一番外側に書かれているクラスやモジュールは::Becocafeや::BecoCafe::Coffeeのように、先頭に::をつけて書くこともできる。
【ファイルを分ける】
例えばindexのファイルとmenuのファイルに分け、indexでmenuのコードを実行する場合。
クラスの定義を別のファイルに記述したら、index.rbの一番上の行で、「require_relative “ファイル名”」とするか「require "./menu"」とすることで、menu.rbのコードを読み込み実行できるようになる。
requireは別の使い方があるためrequire_relativeを推奨。
includeとの違いはファイル名を渡して、そのファイルに定義された内容を使えるようにするということ。
【ライブラリ】
色々なプログラムで共有して使うプログラムのことをライブラリと呼ぶ。
Rubyでは大きく3つのライブラリがある。
①組み込みライブラリ:何も準備せずに利用できる、Integer, String, Arrayなどのクラス。
②標準天賦ライブラリ:使う前にrequireメソッドを実行して準備する。例えばJSONといったクラス。
③Gem:使う前にインストールが必要。rubygems.orgというサイトで公開されている。
「Gemの利用方法」
①ターミナルで「gem install Gem名」または「gem i Gem名」と実行してインストールする。
(過去インストールしたGemの一覧は「gem list」で確認可能)
②インストールしたGemは通常、プログラム内でrequireメソッドを実行することで利用可能になる。「require “Gem名”」
※①の時Macで「ERROR: executing gem …(Gem::FilePermissionError) You don’t have write permissions for …」というエラーが出た場合にはsudo gem install awesome_printと先頭にsudoを加えて実行。(p237)
「Bundler」
複数のGemをまとめて管理する仕組み。①Gemfileを作成し(ない場合)、②bundle installコマンドの実行。
①Gemfileは「bundle init」コマンドでひな形を作成。
エディターでGemfileを開き最後の行に使いたいGemを追記していく。「gem “Gem名”」
②Gemファイルと同じフォルダに移動して「bundle install」,「bundle i」,「bundle」のどれかのコマンドを実行。
※bundle installコマンドを実行するとGemfile.lockというファイルが自動で作成されるが、Gemfileとセットのものなのでいじる必要はなく、バックアップの際も取っておく。
「bundleのコマンド」
「bundle update」:新しいVerのGemをインストール。Gemfile.lockが更新され、新バージョンのGemが書き込まれる。
これにGem名を指定することでそのGemとそのGemが使っているGemをまとめてバージョンアップ可能。
「bundle exec」:指定したバージョンのGemを使う。
「例外処理」
コード上でイレギュラーが発生した場合に処理するためには「begin~rescue~end」を使う。
rescueの後に例外クラスを書き、その例外が発生した時にrescue節が実行される。
ただメソッド内やブロック内で例外処理を書く場合はbeginとendを省略可能。
例:bill = gets.to_i
number = gets.to_i
begin
warikan = bill / number
puts “1人あたり#{warikan}円です”
rescue ZeroDivisionError
puts “0人では割り勘できません”
end
※上記コードは例外処理のサンプルコードだが、入力値に0が入らないように事前にチェックする方が良いプログラム。
実際はファイルが開けなかったときやネットに繋がらなかった時など事前に例外が発生するか分からない時に使う。
【例外の詳しい情報を得る】
発生した例外を画面に表示したり、ログファイルに記録したりするなど、例外の詳しい情報を取得したいとき。
Rubyでは例外もオブジェクトなので例外クラスの後に「=> e」とつけると変数eに例外オブジェクトが代入される。
以下はファイルの内容を表示するプログラムで存在しないファイル名を指定した場合エラーを表示する。
例:cat.rbファイル
def cat(filename)
File.open(filename) do |file|
file.each_line{ |line| puts line}
end
rescue SystemCallError => e
puts “例外が発生しました”
puts “例外クラス: #{e.class}”
puts “例外メッセージ: #{e.message}”
end
filename = ARGV.first
cat(filename)
ーーーーーー
「ruby cat.rb nothing.txt」を実行した場合の出力結果(nothing.txtは存在しない体)
例外が発生しました
例外クラス: Error::ENOENT
例外メッセージ: No such file or directory @ rb_sysopen - notfound.txt
※SystemCallErrorはファイル操作などに失敗した際に発生する例外クラスでError::ENOENTはそのサブクラス。
※ARGVはRubyが用意した特別な定数で、ターミナルで指定した引数を要素として持つ配列。ARGV .firstで先頭の要素取得。
Rubyの例外クラスは階層構造になっており、全てExceotionクラスを継承している。例外クラスを省略するとStandardErrorの例外クラスを捕捉する。
Exceptionと書けば全ての例外を捕捉できるが、通常のプログラムでよく発生する例外を束ねているのがStandardErrorなのでほぼこれで十分。
StandardError外のエラーはメモリ不足やプログラムの構文ミスなど例外処理で続行させるより、停止させる方が安全。
【raiseメソッド】
自分で例外を発生させるメソッド。引数に例外のメッセージを指定する。
書き方は「raise “例外メッセージ”」でifなど条件文の中で記述するとraiseが実行されたらそこでプログラムは終わる。
raiseメソッドで例外メッセージのみを指定したときは、RuntimeErrorクラスが発生したことになる。
もし特定の例外クラスを指定したい場合「raise 例外クラス, “例外メッセージ”」と記述する。
【ensureメソッド】
「ensure~end」の間に書かれた処理は例外の有無に関わらず必ず実行される。
例えば「begin~rescue~ensure~end」のようなプログラムだった場合、begin節が実行されてもrescue節が実行されてもどちらでもensureが実行される。
「文字列を調べる-正規表現」
文字列の中に指定したルールの文字列が含まれるか調べたり、ルールに従って文字列を置き換えたりする時には、正規表現と呼ばれる機能を使う。
Ruby では正規表現リテラル(デリミタ)は通常スラッシュ / で囲むようにする。
「match メソッド」
マッチすると、MatchData オブジェクト(マッチした情報を持つオブジェクト)を返す。
マッチしなかった場合は nil を返す。
p “カフェラテ”.match(/ラテ/) #⇒ #
puts “カフェラテ”.match(/ラテ/) #⇒ ”ラテ"
※マッチした内容を後から参照できるため、キャプチャグループなどの情報が必要な時に使われる。
p "カフェラテ".match(/(コーヒー)(ラテ)/) #⇒ nil
p "カフェラテ".match(/(ラテ)(カフェ)/) #⇒ nil
※各パターンが全てマッチする必要があり、patternを複数にする場合には順番も大事。
matchで取得してそのまま変数に入れると MatchData オブジェクト の状態なのでto_sで形式をstringにした方が使いやすい。
rate = “カフェラテ”.match(/ラテ/) #⇒ #
rate = “カフェラテ”.match(/ラテ/).to_s #⇒ "ラテ"
「match?メソッド」
文字列を「/」で囲んでmatch?メソッドへ渡すとその文字列を含むかtrue/falseで判定可能。
例:p “カフェラテ”.match?(/ラテ/) #⇒ 出力結果はtrue
※引数の「/ラテ/」のように/で囲むと、正規表現(Regexp)オブジェクトとなり、これをパターンという。
【条件と合致する文字列判定】
\zをパターンに含めることで末尾が引数である文字列かどうか判定する。
例:p “カフェラテ”.match?(/ラテ\z/) #⇒ 出力結果はtrue
\Aをパターンに含めることで先頭が引数である文字列かどうか判定する。
例:p “カフェラテ”.match?(/\Aラテ/) #⇒ 出力結果はfalse
「captures メソッド」
captures は MatchData オブジェクトから、キャプチャグループにマッチした部分だけを取り出し、配列として返す。
Rubyでは正規表現を使って文字列にマッチさせると、結果は MatchData オブジェクトとして返されるようになっていて、その中には丸括弧で囲んだ部分(キャプチャグループ)の内容が格納されている。
(\d+)と([+-/])と(\d+)の3つが全てマッチしないとマッチしたとみなさない
s = "3+5の答えは8”
a = s.match(/(\d+)([+-/])(\d+)/)
p a[0] # => "3+5”
p a[1] # => "3" 最初のキャプチャグループ (\d+)
p a[2] # => "+" 2番目のキャプチャグループ ([+-*/])
p a[3] # => "5" 3番目のキャプチャグループ (\d+)
p a.captures # => ["3", "+", "5"]
※残りの "の答えは8" はマッチ対象外
「sub メソッド」
対象の文字列から最初に見つかった指定部位だけを置換 するメソッド。
1つ目の引数に置換元となる文字列や正規表現を入れ、2つ目に置換先の文字列をかくことで、引数1と引数2に置き換えることができる。
str = "apple banana apple”
puts str.sub("apple", "orange") #⇒ orange banana apple
置換対象 は そのままの文字列 か 正規表現 で指定できる。
最初にマッチした部分 だけが 置換後の文字列 に置き換えられる
「gsubメソッド」
対象の文字列から見つかった指定部位を全て置換するメソッド。
1つ目の引数に置換元となる文字列や正規表現を入れ、2つ目に置換先の文字列をかくことで、引数1を引数2に置き換えることができる。
subと違い文字列中に複数該当箇所があるときは全て置換する。
例:p “カフェラテ”.gsub(”カフェ”, “ティー”) #⇒ 出力結果は”ティーラテ”
p “カフェラテ”.gsub(”カフェ”, “”) #⇒ 出力結果は”ラテ”
p “カフェラテ”.gsub(”/\Aラテ/”, “ラテ”) #⇒ 出力結果は”ラテラテ”
※破壊的にオブジェクトを変更できるgsub!メソッドもある。
「その他正規表現のメタ文字」
・「[文字群]」
[]で囲むと中の文字群のどれか1文字とマッチする。例えば/[abc]/はaまたはbまたはcとマッチする。
abかcdにマッチするとかの時には使えない。この場合/ab|cd/となる。
/[A-Za-z0-9]/と範囲指定で書くと、アルファベット大文字小文字と数字のいずれか1文字にマッチする。
※文字の中でも「/」や「-」はそのままだと正規リテラルの記号として特別な意味に解釈されてしまうのでエスケープ文字「\」を用いて普通の文字としての「/」を表現する必要がある。
つまり「/」や「-」を指定するなら/[/-]/としなくてはいけない。
[+-*/] は +, -, *, / の計算記号を指定している。
・「.」:任意の何でもいい1文字とマッチする。
例えば/a.c/はabcやadc,azcなどなんでも良い1文字を表現する。
・「」:前の文字が0回以上繰り返すときにマッチする。
例えば/abc/はabcやabbbc、acにもマッチする。
・「.*」:何でも良い文字が0回以上繰り返すときにマッチする。
どんなバラバラな文字がいくつ入っても良い。
例えば/a.*c/はabcやabosjdc、agc、avvvsecなどマッチする。
・「+」:前の文字が1回以上繰り返すときにマッチする。
例えば/ab+c/はabbbcやabcにマッチする。acにはマッチしない。
・「.+」:何でも良い文字が1回以上繰り返すときにマッチする。
どんなバラバラな文字が入っても良いが空はダメ。
・「?」:前の文字が0回か1回だけ繰り返される時にマッチする。
例えば/ab?c/はabcやacにマッチする。abbcにはマッチしない。
・「\d」:任意の数字(0〜9)にマッチする。
範囲指定するなら/\d[1-5]/で 1~5 の数字にマッチする。変数を入れるなら /\d[1-#{num}]/。
・「\w」:単語構成文字(アルファベット、数字、アンダースコア)にマッチする。
・「\W」:単語構成文字ではないもの(スペース, 句読点, 記号, 日本語)にマッチする。
・「\b」:単語の境界を示し、単語の始まりや終わりを判定するために使う。
単語の境界とは「\b」の位置で対象の単語構成文字と非単語構成文字が隣り合っているかどうか。
"abc123 def”.scan(/\b\d+\b/) の場合、123の後ろはスペースなので「\b」がマッチしているが、123の前がabcとなっており、「\b」がマッチしないためscanが返すのは空配列となる。
また「\b」は「英数字」にしか適用されないので、非単語構成文字の前後に単語構成文字があるかどうかの判定はできない。
・「\s」:空白文字(スペース、タブ、改行など)にマッチする。
match = "今日は 日本語 を勉強する。".match(/(^|\s)日本語(\s|$)/).to_s
p match " 日本語 “
※行頭または空白で始まり、空白または行末で終わる」
・「^」:「行の先頭にマッチする」
^, +の組み合わせ:s = "0010123” puts s.sub(/^0+/, "+++") #⇒ +++10123
・「$」:「行の末尾」にマッチする
例えば/bc$/はabcやmbcにマッチする。
【「^」や「$」と「\A」「\z」の違い】
「^」と「$」は改行があると各行それぞれでマッチ確認してくれるが、
「\A」「\z」は改行を意識せず、ただ対象の文字列の先頭と末尾だけマッチ確認する。
s = "hello world\nruby”
puts s.sub(/^ruby/, "Python") #⇒ hello world
Python
puts s.sub(/\Aruby/, "Python") #⇒ hello world
ruby
※\Aでは改行後のrubyは先頭であると認識していないためPythonに置き換わらない。
・「+$」:
s = "Hello!!!” puts s.gsub(/!+$/, "") #⇒ Hello
・「|」:いずれかのパターンにマッチする。
例えば/ab|bc|xe/はabcやaobc、yabxc、axeoなどにマッチする。
・「()」:グループ化
例えば/(abc|def)xyz/は括弧内のabcまたはdefのどちらかに一致し、その後にxzが続く部分を探す。そして () 内にマッチした部分だけを取り出し、() の外にある部分はマッチの条件として使われるだけでキャプチャはされない。
・「//m」:マルチラインモード
マルチラインモードを有効にするオプションで、正規表現における ^ と $ の動作を変更する。
「貪欲と非貪欲」
正規表現では、* や + などの量指定子を使うときに 「できるだけ長くマッチする(貪欲)」 か 「できるだけ短くマッチする(非貪欲)」 の違いがある。
貪欲(Greedy)の例
match = "hello world bye".match(/(.*)<\/tag>/)
puts match[1] #⇒ "hello world bye"
※キャプチャグループが「.*」なのでとの間にあるすべての文字を取得する正規表現になっていて、この場合は貪欲なので最初にを見つけたらその次のを見つけるまでの間ではなく、とりあえず最後まで確認して一番最後のまでの間を出力する。
非貪欲(Lazy)の例
matches = "hello world bye".scan(/.*?<\/tag>/)
puts matches #⇒ ["hello", "bye"]
※この例では「()」をつけていないので部分も出力されるが、ちゃんと個別に取得ができている。
上記の貪欲のように通常、* や + を使うと、マッチ可能な限り長く取ろうとする。
しかし *? や +? のように ? をつけて非貪欲にすると、「最小限でマッチしよう!」という動きになる。
「先読み/後読み」:正規表現の条件設定
「ある部分の後や前に何かがあるかどうかを確認する方法」のこと。
例えば、/りんご(?=を)/ という肯定的先読みの場合、
「りんご」の後ろに「を」があるかを確認する。
text = "私はりんごを食べました。”
result = text.match(/りんご(?= を)/)
puts result # => "りんご”
※りんごを取得しているだけで「を」は取得されておらず条件として使われているだけ。
もし「りんご」の後ろに「で」があるかを確認した場合。
result = text.match(/りんご(?= で)/)
p result # => nil
※肯定的先読みの条件に合致していないためりんごすら取得できていない。
つまり先読みや後読みは特定のパターンがあるかどうかという条件を確認するためだけに使われ、一致結果には含まれない。
この条件は match や =~ 演算子 を使うことで true や false として判定できる。
match = text.match(/りんご(?=を)/)
puts "マッチ!” if match
・「=~ 演算子」
「=~」を使っても文字列が正規表現にマッチするかどうかを確認できる。
puts "マッチ!” if text =~ /りんご(?=を)/
・「?=」:肯定的先読み
(?=pattern) と記述することで、「後ろにpatternが続いているか?」というチェックをしている。
そして続いている時にtrueのような挙動をする。
text = "apple banana apple orange"
result = text.gsub(/apple(?= banana)/, 'fruit')
puts result # => "fruit banana apple orange"
※「apple の後ろに “ banana” が続く場合」に apple を fruit に置換している。
置換しているのはgsubの機能だが、それを機能させるか判定するのが先読みの役割
複数の先読みを組み合わせるなら「|」を使う
result = text.gsub(/apple(?= banana)|apple(?= orange)/, 'fruit')
puts result # => "fruit banana fruit orange"
・「?!」:否定的先読み
(?=pattern) と記述することで、「後ろにpatternが続いているか?」というチェックをしている。
そして続いていない時にtrueのような挙動をする。
text = "apple banana apple orange"
result = text.gsub(/apple(?! banana)/, 'fruit')
puts result # => "apple banana fruit orange"
※「apple の後ろに “ banana” が続かない場合」に apple を fruit に置換している。
・「?<=」:肯定的後読み
(?=pattern) と記述することで、「前にpatternが続いているか?」というチェックをしている。
そして続いている時にtrueのような挙動をする。
text = "apple banana apple orange"
result = text.gsub(/(?<=banana )apple/, 'fruit')
puts result # => "apple banana fruit orange"
※「apple の前に “banana ” が続く場合」の apple を fruit に置換している。
・「?<!」:否定的後読み
(?=pattern) と記述することで、「前にpatternが続いているか?」というチェックをしている。
そして続いていない時にtrueのような挙動をする。
text = "apple banana apple orange"
result = text.gsub(/(?<!banana )apple/, 'fruit')
puts result # => "fruit banana apple orange"
※「apple の前に “banana ” が続かない場合」の apple を fruit に置換している。
「scan メソッド」
文字列に対して正規表現を使ってパターンにマッチする部分を抽出するために使用する。
基本的な使い方
文字列から指定した正規表現にマッチする部分をすべて見つけて配列として返す。
例:"abc123def456ghi".scan(/\d+/) #⇒ ["123", "456"]
正規表現でグループ化した場合 各グループ が 連続的に マッチする 必要がある。
text = "abc 123"
p text.scan(/([a-z]+) (\d+) (\w+)/) #⇒ []
⬇︎ 以下のようにすればマッチした判定される。
text = "abc 123 def"
p text.scan(/([a-z]+) (\d+) (\w+)/) #⇒ [["abc", "123", "def"]]
※abcが[a-z]+に、123が\d+に、\w+がdefにマッチしている
※ちなみに空白も含めて判定している。
一度マッチしたものはそのペアで使われるので、他の判定で使うことはできない。
text = "abc 123 def 456 ghq"
p text.scan(/([a-z]+) (\d+) (\w+)/) #⇒ [["abc", "123", "def"]]
⬇︎ 以下のようにすれば2つがマッチしたと判定される。
text = "abc 123 def ghi 456 jkl"
p text.scan(/([a-z]+) (\d+) (\w+)/) #⇒ [["abc", "123", "def"], ["ghi", "456", "jkl"]]
以下のように「|」(パイプ)で区切ると各パターンのいずれかにマッチする部分を探す。
text = "abc 123 def 456 ghq"
p text.scan(/([a-z]+)|(\d+)|(\w+)/)
#⇒ [["abc", nil, nil], [nil, "123", nil], ["def", nil, nil], [nil, "456", nil], ["ghq", nil, nil]]
※([a-z]+)と(\d+)と(\w+)でマッチするか判定するが、最初にマッチしたグループだけが格納でき、そのほかはnilになる。
左から判定していくので(\w+)でマッチするものはすでに他2つでマッチされてしまっているので、毎回(\w+)の部分はnilになっている。
「Regexp.escape メソッド」
Regexp.escape は、特殊な意味を持つ文字(メタ文字)を「普通の文字」として扱うためのメソッド。
シンプルなケースであればメタ文字の前に「\」をつけて対応しても良いが、Regexp.escapeを使った方がミスも減り、変数にも対応できる。
「start_with? メソッド」
文字列が指定されたどれかの文字列で始まるかどうかを確認するメソッド。
str = "apple”
puts str.start_with?("a", "b”) # => true
puts str.start_with?("b", "bo") # => false
※引数として配列を渡すことも可能で、配列内のどれか一つでも一致する場合に true を返す。
※空文字列 "" については、常に true を返す。
「upcase / downcase メソッド」
文字列の 大文字化 と 小文字化 を行うメソッド。
「swapcase メソッド」
文字列の 大文字 と 小文字 を入れ替えるメソッド。
「casecmp? メソッド」
大文字・小文字を区別せずに2つの文字列を比較しtrueかfalseを返すメソッド。
"apple".casecmp?("APPLE") # => true(大文字・小文字を無視)
"banana".casecmp?("APPLE") # => false(異なる単語)
"あいう".casecmp?("あいう") # => true
"あいう".casecmp?("アイウ") # => false(非ASCII文字は無視)
※ただの casecmp とは異なり、比較できない場合でも nil ではなく false を返す。
「大文字・小文字を区別しない正規表現」
"Hello".match?(/\A[a-z]+\z/i) #=> true (i
フラグで大小の区別をなくす)
「英大文字判定」
"A".match?(/\A[A-Z]\z/) #=> true
"a".match?(/\A[A-Z]\z/) #=> false(小文字なのでNG)
"AB".match?(/\A[A-Z]\z/) #=> false (2文字なのでNG)
「文字列全体が大文字か?」を判定する
"HELLO".match?(/\A[A-Z]+\z/) #=> true
"Hello".match?(/\A[A-Z]+\z/) #=> false (小文字が含まれるのでNG)
str = "HELLO”
str == str.upcase #=> true (元々すべて小文字ならtrueになる)
「英小文字判定」
"a".match?(/\A[a-z]\z/) #=> true
"A".match?(/\A[a-z]\z/) #=> false(大文字なのでNG)
"ab".match?(/\A[a-z]\z/) #=> false (2文字なのでNG)
※小文字も文字列全体の判定は大文字と同じ要領。
「漢字判定」
"漢".match?(/\p{Han}/) #=> true (漢字なのでOK)
"ひら".match?(/\p{Han}/) #=> false (ひらがななのでNG)
※ \p{Han} は漢字のUnicodeプロパティを表しているので**「漢字が含まれるか」** を判定できる。
「すべての文字が漢字か?」 を判定する
"漢字".match?(/\A\p{Han}+\z/) #=> true (全て漢字なのでOK)
"漢字A".match?(/\A\p{Han}+\z/) #=> false (英字が含まれているのでNG)
"漢字".chars.all? { |c| c.match?(/\p{Han}/) } #=> true
"漢字A".chars.all? { |c| c.match?(/\p{Han}/) } #=> false
「ひらがな判定」
"ひらがな".match?(/\p{Hiragana}/) #⇒ true
"カタカナ".match?(/\p{Hiragana}/) #⇒ false
「カタカナ判定」
"カタカナ".match?(/\p{Katakana}/) #⇒ true
"ひらがな".match?(/\p{Katakana}/) #⇒ false
※ひらがなもカタカナも文字列全体の判定は漢字と同じ要領。
日本語か数字を抜き出す
text = "今日は3があるけど、12は違うし、5もあるよ!"
regex = /[\p{Han}\p{Hiragana}\p{Katakana}、。]+|\d+/
p text.scan(regex) ⇒ ["今日は", "3", "が", "あるけど", "12", "は", "違うし", "5", "も", "あるよ"]
「句読点判定」
\p{P} は Unicodeのプロパティ を利用して、句読点 や 記号 にマッチする正規表現。
"こんにちは、世界。".match?(/\p{P}/) #⇒ true
"abc".match?(/\p{P}/) #⇒ false
一般的な句読点:、。!?
括弧:(){}[] など
引用符:"' など
ダッシュ:-—(エムダッシュ)など
その他の記号:#$& など
text = "今日は3があるけど、12は違うし、5もあるよ!"
p text.scan(/[\p{P}]+/) #⇒ ["、", "、", "!"]
p.text.scan(/([\p{Han}\p{Hiragana}\p{Katakana}]+)|([\p{P}]+)|(\d+)/)
#⇒ ["今日は", "3", "があるけど", "、", "12", "は違うし", "、", "5", "もあるよ", "!"]
「フィルタアクション」
特定のアクションの前に共通の処理を実行し、その中でインスタンス変数を定義することができる。
「その他」
「多重代入構文」
複数の変数にそれぞれオブジェクトを代入する場合、以下の例のようにすることで記述を簡略化する方法。
例:v1, v2, v3 = "東京都", "大阪府", "福岡県”
puts v2 #⇒ 出力結果は”大阪府”
多重代入の右辺には配列オブジェクトを指定することもできる。
変数1, 変数2, 変数3, ... = [要素1, 要素2, 要素3, ...]
※左辺に記述した変数の数より右辺に記述したオブジェクトの数が少ない場合、代入されるオブジェクトが無い変数には nil が代入される。
「injectメソッド」※reduceメソッドと同じ
配列等の要素を一つずつ繰り返してブロック内で要素を注入し処理をするメソッド。
配列オブジェクト.inject {|初期値, 要素| ブロック処理 }
例:array = 1..6
array.inject(:+) #⇒21 配列の要素をすべて足す
array.inject(3,:+) #⇒24 初期値3に対して、配列の要素をすべて足す
(1..10).inject{|acc, n| acc * n }
#⇒初期値12=2→23=6→64=24→245=120…と10まで繰り返し、最終的に(1 * 2 * 3 * ... * 10) の結果が出る。
「replace メソッド」
文字列や配列などの要素をその場で変更するメソッド
配列を置き換えるなら引数には配列を、文字列を置き換えるなら文字列を引数に渡す必要がある。
数値を置き換えることは不可。
list = [["Yamada", "30"]]
list.each do | _, i |
i.replace((i.to_i + 1).to_s)
end
※上記のように文字列の数字を一旦数値にして1を足してから文字列に戻せばOK
そして普通なら以下の❌のようにしても意味がないが、replaceを使うことで配列内の要素を書き換えることができている。(⭕️でも可)
❌:list = [["Yamada", "30"]] ⭕️:list = [["Yamada", "30"], ["Hayashi", "20"]]
list.each do | _, i | list.each do | i |
i = i.to_i + 1 i[1] = i[1].to_i + 1
end end
簡単な使用例を使った解説
arr = [1, 2, 3]
arr = [4, 5, 6]
p arr # => [4, 5, 6] 新しい配列を代入
arr = [1, 2, 3]
arr.replace([4, 5, 6])
p arr # => [4, 5, 6] 元の配列の中身を更新
結果的に見た目の差はないものの、replace は、既存の配列をその場で変更している。
つまり、変数 arr は 同じ配列を指し続けたまま、その内容だけが変わる という違いがある。
「combination()メソッド」
配列からn個を選択した時の組み合わせを得ることができるメソッド。
組み合わせは、順序なし・重複無しで返される。
n = [1, 2, 3, 4]
p n.combination(2) #⇒ [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
配列内に重複する値がある場合
n = [1, 1, 2, 3, 4]
p n.combination(2) #⇒ [[1, 1], [1, 2], [1, 3], [1, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
nに事前にuniqを使うと[1, 1]が作られないので、それが必要ならcombinationの後にuniqを使う。
n = [1, 1, 2, 3, 4]
🔺p n.combination(2).uniq #⇒ [[1, 1], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
⚠️しかし上記の🔺のようにすると以下の配列の場合、やはり[1, 2]と[2, 1]が作られてしまう。。
n = [1, 2, 1, 2]
p n.combination(2).uniq #⇒ [[1, 2], [1, 1], [2, 1], [2, 2]]
★そこで!!map(&:sort)を挟んで[2, 1]だったものも[1, 2]としてからuniqすることでどんな配列からでも同じ組み合わせの要素を持つ配列を取り除くことができる!!★
n = [1, 2, 1, 2]
p n.combination(2).map(&:sort).uniq #⇒ [[1, 2], [1, 1], [2, 2]]
「2重ループを使った組み合わせ」
x + y < 100 という条件を満たす組み合わせの配列を作るには、2重のループを使ってすべての組み合わせを列挙する。
pairs = []
(1...98).each do |x|
y の範囲を 1 から (100 - x - 1) までに制限
(1...(99 - x)).each do |y|
pairs << [x, y]
end
end
p pairs #⇒ [[1, 1], [1, 2], ..., [98, 1]]
「product メソッド」
複数の配列から直積(デカルト積)を計算するために使われるメソッド。
array1 = [1, 2] array2 = [3, 4]
result = array1.product(array2)
p result #⇒ [[1, 3], [1, 4], [2, 3], [2, 4]]
複数の配列の直積を作る場合
array1 = [1, 2] array2 = [3, 4] array3 = [5, 6]
result = array1.product(array2, array3)
p result #⇒ [[1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6]]
重複する配列の場合
p [1, 1, 3].product([3, 1]) #⇒ [[1, 3], [1, 1], [1, 3], [1, 1], [3, 3], [3, 1]]
uniqで重複を除く
p [1, 1, 3].product([3, 1]).uniq #⇒ [[1, 3], [1, 1], [3, 3], [3, 1]]
もし上記の[1, 3]と[3, 1]を同じ組み合わせとみなしたいならcombinationを使う。
「index メソッド」
配列や文字列の中で指定された要素や文字列(正規表現含む)が最初に現れるインデックスを返すメソッド。
arr = ["apple", "banana", "cherry"]
result = arr.index("banana")
puts result # => 1
result = arr.index("grape")
puts result # => nil
※見つからなければ nil を返す。
第2引数を取ることで探し始める位置を指定できる。
text = "Ruby is Fun!”
puts text.index(/[A-Z]/, 5) # => 8
※大文字を探していて、isの「i」から探し始めるためRubyの「R」は検知せず、Fun!の「F」の位置が返される。
「rindex メソッド」
文字列や配列の中で、指定された要素や文字列(正規表現含む)の「最後の出現位置」を探す メソッド。
⚠️ ただ厳密には右から左に探して、最初に見つけた位置を返している。
(これを意識しないと第2引数を扱う際に混乱する)
text = "hello world, hello again"
puts text.rindex("hello") #⇒ 13
arr = [1, 2, 3, 4, 3, 2, 1]
puts arr.rindex(3) #⇒ 4
※こちらも指定されたものが見つからなければ nil が返される。
rindexでも第2引数を取ることで探し始める位置を指定できる。
text = "hello world, hello again hello"
puts text.rindex("hello", 20) #⇒ 13
※20という開始位置は通常通り左から数えたindexの位置なので、この場合againの「g」から探し始めるが、rindexは右から左に探して見つけた最初の位置を返すのでインデックス13を返す。
「each_with_indexメソッド」
各配列等の要素とそのインデックスのペアを生成する。
例:array = [’h’, ‘e’, ‘l’, ‘l’, ‘o’]
array.each_with_index #⇒[['h', 0], ['e', 1], ['l', 2], ['l', 3], ['o', 4]]
応用として指定された文字列の偶数番目だけで新たに文字列を作成する方法
例:word = ‘お奇は奇よ奇う奇’
even_word = word.chars.each_with_index.select { |, index| index.even? }.map(&:first).join
even_word #⇒ ‘おはよう’
※word.charsで一文字ずつの配列にして、each_with_indexでそれぞれに番号を割り振る。
次にselectで割り当てた番号が偶数の配列だけ選ぶ。ここの「(アンダーバー)」は要素そのものを無視するためのプレースホルダー。
番号が偶数の配列に絞れたら、map(&:first)で各配列の最初の要素(文字)だけの新しい配列を作る。(indexを抜く)
最後にjoinで新しい配列の文字を連結して一つの文字列にする。
「find メソッド」
Enumerable に含まれるメソッドで、ブロック内の条件を満たす最初の要素を返す。
もし条件を満たす要素が見つからなかった場合はnil を返す。
(繰り返し処理が続く)
array = [1, 2, 3, 4, 5]
result = array.find { |x| x > 3 }
puts result # => 4
※find は条件に一致する最初の要素を返すのに対し、select/filter は条件に一致するすべての要素を配列で返す。
「each_char メソッド」
文字列の各文字を 1 つずつ順番に処理するためのメソッド。
"hello".reverse.each_char { |char| print char } #⇒ "olleh"
「each_slice メソッド」
配列やEnumerableオブジェクトを n 個ずつのグループに分けて処理するメソッド
(1..10).each_slice(3) { |slice| p slice } #⇒ [1, 2, 3][4, 5, 6][7, 8, 9][10]
「each_cons メソッド」
配列や Enumerable オブジェクトの 連続する n 個の要素をグループにして処理 するメソッド。
arr = [1, 2, 3, 4, 5]
arr.each_cons(2) { |a| print a } # => [1, 2][2, 3][3, 4][4, 5]
3 つの連続した要素を取得
arr = [1, 2, 3, 4, 5]
arr.each_cons(3) { |a| print a } # => [1, 2, 3][2, 3, 4][3, 4, 5]
「partition メソッド」
配列や範囲 (Range) を、条件に基づいて 2 つの配列に分割するメソッド 。
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even, odd = arr.partition { |n| n.even? }
p even # => [2, 4, 6, 8, 10]
p odd # => [1, 3, 5, 7, 9]
「dupメソッド / cloneメソッド」
深いコピーと浅いコピー
配列やハッシュを上記2つでコピーした場合、そのオブジェクト自体は異なるobuject_idでコピーされるものの、そこから参照される中身の要素要素は同じobject_idのままコピーされてしまう。
そのためコピー先かコピー元のどちらかで中身の要素に対して破壊的変更を加えると、もう一方にもその変更が反映されてしまう。
つまり
例えば 配列 A を dup/clone して B を作ったとする。
そこで A.shift(1) をしても 配列の構造(要素の数)を変更するだけで、要素の中身を直接変更していないので B に影響を与えない。(逆も同じ)
しかし A[0].upcase! をした場合、A[0] は B[0] と 同じオブジェクト なので、upcase! を適用すると b[0] も影響を受けてしまう。
※配列自体の構造(要素の追加・削除)を変更する場合は影響しない という点が重要
深いコピーにするなら
①map(&:dup) を使う(浅いネストならOK)
a = ["hello", "world"]
各要素もコピー
b = a.map(&:dup)
a[0].upcase!
p a #⇒ ["HELLO", "world"]
p b #⇒ ["hello", "world"] (影響なし)
⚠️ 配列やハッシュのネストが深いと影響を受けてしまう
②Marshal を使う
Marshal.dump と Marshal.load を使うと、オブジェクトをバイナリ化して完全にコピーできる。
a = ["hello", "world"]
b = Marshal.load(Marshal.dump(a))
a[0].upcase!
p a #⇒ ["HELLO", "world"]
p b #⇒ ["hello", "world"] (影響なし)
※ 配列だけでなくハッシュなどのネストした構造にも対応!
⚠️ クラス情報を含むオブジェクト(例: Struct や Class)には適さない。
③deep_dup メソッドを使う
ActiveSupport から以下のように require が必要。
require 'active_support/core_ext/object/deep_dup’
original = { a: { x: 1, y: 2 }, b: 2 }
copy = original.deep_dup
copy[:a][:x] = 100
puts original #⇒ {:a=>{:x=>1, :y=>2}, :b=>2} (元のデータは変更されない)
puts copy #⇒ {:a=>{:x=>100, :y=>2}, :b=>2}
deep_dup ではクラスが持つメソッドやクラス変数も複製されるため、オブジェクトのメソッドが正常に動作する。
「include? メソッド」
指定した要素が含まれているかをチェックするためのメソッド。
配列や文字列、ハッシュのキー、範囲(Range) などに include? メソッドを使うことができる。
例(配列 / 文字列 / ハッシュのキー / 範囲)
arr = [1, 2, 3, 4]
puts arr.include?(3) #⇒ true
str = "Hello, world!"
puts str.include?("Hello") #⇒ true
hash = {a: 1, b: 2, c: 3}
puts hash.include?(:b) #⇒ true
range = (1..10)
puts range.include?(5) #⇒ true
配列の要素のペアも確認できる。
array = [[0, 1], [2, 3]]
p array.include?([0, 1]) #⇒ true ([0, 1] が含まれている)
p array.include?([2, 1]) #⇒ false ([2, 1] は含まれていない)
p array.include?([1, 0]) #⇒ false ([1, 0] も含まれていない)
「slice/slice!メソッド」
文字列や配列から特定の位置、範囲を指定して要素を取り出すことができる。
例:array = [1, 2, 3, 4, 5] array = [1, 2, 3, 4, 5]
array.slice(1) #⇒[2] array.slice!(0, 3) #⇒[1, 2, 3]
array #⇒[1, 2, 3, 4, 5] array #⇒[4, 5]
範囲指定
array.slice(1..2) #⇒[2, 3]
「flattenメソッド」
多次元配列を1次元の配列に変換するためのメソッド。
例:arr = [1, [2, 3], [4, 5], 6]
puts arr.flatten #⇒ [1, 2, 3, 4, 5, 6]
※flatten!にすると破壊的メソッド
flatten(1)というように引数を取ることで、「どの深さまでネストを解除するか」を指定する。
「concatメソッド」
呼び出し元の配列に他の配列を結合するメソッド。
例:arr1 = [1, 2, 3] arr2 = [4, 5, 6]
puts arr1.concat(arr2) #⇒ [1, 2, 3, 4, 5, 6]
※concat!にすると破壊的メソッド
「insert メソッド」
文字列や配列の特定の位置に要素を挿入するメソッド。
s = "hello”
s.insert(2, "X") # インデックス 2 に "X" を挿入
puts s # => "heXllo”
「flat_map メソッド」
各要素をマッピング(変換)しつつ、配列をフラット(平坦)にするメソッド 。
map + flatten(1) と同じ動作を1ステップで行う。
arr = ["A", "B", "C"]
result = arr.flat_map { |x| [x, "X"] }
p result # => ["A", "X", "B", "X", "C", "X"]
map の場合は [ ["A", "X"], ["B", "X"], ["C", "X"] ] となるが、flat_map によってフラット化される。
flat_mapの応用
特定の要素の後に指定の要素を追加する
arr = [1, 2, 3, 4]
target = 2 new_element = 99
arr = arr.flat_map { |x| x == target ? [new_element, x] : x }
p arr # => [1, 99, 2, 3, 4]
nil を含む配列の処理
flat_map は nil をうまく処理できるので、要素をフィルタリングしながら変換したいときに便利。
arr = [1, 2, 3, nil, 4]
result = arr.flat_map { |x| x.nil? ? [] : [x, x * 2] }
p result # => [1, 2, 2, 4, 3, 6, 4, 8]
※ただ繋げるだけならjoinでもnilを排除してくれる。
「format / sprintf メソッド」
文字列をフォーマットするためのメソッド。
データを指定された形式で整形し、文字列として出力する。
n = 3.14159
puts format(”%.3f”, N) #=> 3.142
puts sprintf(”%.3f”, N) #=> 3.142
puts "%.3f" % n #=> 3.142
⚫︎主な書式指定子(フォーマット文字列)
%d 整数 format('%d', 42) → 42
%f 浮動小数点数 format('%.2f', 3.14159) → 3.14
%s 文字列 format('%s', 'Ruby') → Ruby
%x 整数を16進数表記に変換 format('%x', 255) → ff
%o 整数を8進数表記に変換 format('%o', 8) → 10
⚫︎浮動小数点数の細かい設定
%.nf 小数点以下 n 桁まで表示
→ format('%.3f', 3.14159) → 3.142
%10.3f 小数点以下3桁で全体の幅を10文字にする
→ format('%10.3f', 3.14159) → 3.142
%-10.3f 小数点以下3桁で全体の幅を10文字にして左寄せ
→ format('%-10.3f', 3.14159) → 3.142
%.*f 桁数を動的に渡す
m = 3 n = 3.14159
→ format("%.*f", m, n) → 3.142
「round / ceil / floor メソッド」
round:四捨五入するメソッド
puts 3.5.round #⇒ 4
puts (-3.2).round #⇒ -3(切り上げ)
puts (-3.5).round #⇒ -4(切り捨て)
小数点以下の桁数を指定する場合
puts 3.14159.round(2) #⇒ 3.14
ceil:切り上げするメソッド
puts 3.2.ceil #⇒ 4 puts (-3.2).ceil #⇒ -4
floor:切り捨てするメソッド
puts 3.7.floor #⇒ 3 puts (-3.7).floor #⇒ -4
n = 3.1445
puts n.round(2) #=> 3.14 になってしまう。
3.15にしたい場合
puts (n * 100).ceil / 100.0 #=> 3.15
※3.1445 を100倍(314.45)して切り上げ、その後100で割って小数第2位で繰り上げた値 3.15 を取得
3.20にしたい場合
puts (n * 100).floor / 100.0 + 0.05 #=> 3.20
「to_f メソッド」
オブジェクトを浮動小数点数(Float)に変換するためのメソッド。
主に文字列から数値に変換する際に使われる。
⚠️to_fは浮動小数点数(IEEE 754 Double) に変換するため、約 17 桁の精度 しか保持できない。⚠️
"3.14".to_f # => 3.14
"123".to_f # => 123.0
"0.0100".to_f # => 0.01
数値として解釈できない文字列は 0.0 に変換されるものの、文字列の先頭部分が数値として有効であればその部分だけが変換される
”abc”.to_f # => 0.0
"12abc".to_f # => 12.0
・to_fによる指数表記(科学的記数法)
文字列が指数表記になっている場合も正しく変換される。
"1.0e-06".to_f # => 1.0e-06
⚠️数値の絶対値が一定の閾値よりも小さい場合に、自動的にこの表記になる場合がある⚠️
puts "0.00001".to_f # => 1.0e-05
「BigDecimal」
数値の精度を保持しつつ、文字列表現を科学的記数法を通常の少数表記に変換する。
to_fの⚠️の内容は BigDecimal を使うことで解決できる。
例:”0.000001”が入力される場合
require 'bigdecimal’ ⬅︎ 以下の例では不要
require 'bigdecimal/util’ ⬅︎ to_dを使うのに必要
num = gets.chomp # numは1.0e-06
puts num.to_d.to_s("F")
※num.to_dで BigDecimal に変換して、"F" オプションでBigDecimal を通常の小数表記に変換。
※bigdecimal/util を require して、to_d メソッドを使い、to_s("F") で通常表記に変換。
BigDecimal #to_s のフォーマットオプション
"F” 通常の小数表記
"E” 指数表記(科学的記数法)
"G” 適切な形式("F" または "E")
「rjust メソッド」※対になるのが ljust メソッド
文字列の左側を指定した文字で埋めることで、指定された長さに調整するためのメソッド。右揃えにするために使われ、足りない部分が埋められる。
n = 12
puts n.rjust(4, “大”) #=> “大大12”
半角スペースや0であればformatでも対応可能
n = gets.chomp.to_i
puts format("%4d", n) #=> “ 12”
puts format("%04d", n) #=> “0012”
※rjustもformatも足りない時のデフォルトは半角スペース
「send メソッド」
Ruby のすべてのオブジェクトが持つメソッドで、オブジェクトに対して動的にメソッドを呼び出すためのメソッド。
puts 5.send(:+, 3) # => 8
「eval メソッド」
文字列として与えられた内容を Ruby のコードとして評価・実行するためのメソッド。
code = "1 + 2 * 3"
result = eval(code)
puts result # => 7
「zip メソッド」
複数の配列を「横並び」にして、それぞれの対応する要素を組み合わせて1つの配列にするメソッド
a = [1, 2, 3] b = [4, 5, 6]
result = a.zip(b)
p result #⇒ [[1, 4], [2, 5], [3, 6]]
配列の長さが異なる場合
a = [1, 2] b = [4, 5, 6]
result = a.zip(b)
p result #⇒ [[1, 4], [2, 5]]
a = [4, 5, 6] b = [1, 2]
result = a.zip(b)
p result #⇒ [[4, 1], [5, 2], [6, nil]]
※メソッドを所有するオブジェクトに合わせてペアを作成する。
引数の要素が足りない場合はnilが追加される。
3つ以上の配列を組み合わせる
a = [1, 2, 3] b = [”a”, “b”, “c”] c = [”x”, “y”, “z”]
result = a.zip(b, c)
p result #⇒ [[1, "a", "x"], [2, "b", "y"], [3, "c", "z"]]
「転置行列」
ある行列の行と列を入れ替えた行列のこと。
1 2 3 1 4 7
4 5 6 ➡︎ 2 5 8
7 8 9 3 6 9
この転置行列を作る方法
以下のコードは n行 k列 を取得して k 行 n列 で出力する。
n, k = gets.chomp.split.map(&:to_i)
a = n.times.map { gets.chomp.split.map(&:to_i) }
k.times do |i|
puts n.times.map { |j| a[j][i] }.join(" ")
end
「二次元配列」
空の二次元配列を用意
array = Array.new(3) { Array.new }
p array #⇒ [[], [], []]
array = Array.new(3) { Array.new(1) }
p array #⇒ [[nil], [nil], [nil]]
二次元配列の初期値に異なる値を設定
rows = 3
initial_values = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
array = Array.new(rows) { |i| initial_values[i] }
p array #=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
計算で動的に値を設定
cols = 3 cols = 3
array = Array.new(rows) { |i| (1..cols).map { |j| i + j } }
p array #=> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
「三次元配列」
depth = 2 # 奥行き rows = 3 # 行数 cols = 4 # 列数
initial_value = 0 # 初期値
array_3d = Array.new(depth) { Array.new(rows) { Array.new(cols, initial_value) } }
p array_3d #=> [[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]]
計算で動的に値を設定
depth = 2 rows = 3 cols = 3
array_3d = Array.new(depth) do |d|
Array.new(rows) do |r|
Array.new(cols) do |c|
d + r + c # 計算結果を要素に設定
end
end
end
p array_3d #=> [[[0, 1, 2], [1, 2, 3], [2, 3, 4]], [[1, 2, 3], [2, 3, 4], [3, 4, 5]]]
「Unicordコードポイント」
文字を一意に識別するために使用される整数の値のこと。
例:'A' の Unicodeコードポイントは 65 など
・ord メソッド
例:puts 'B'.ord #⇒ 66
・chr メソッド
例:puts 67.chr #⇒ "C”
「Float::INFINITY」
Float::INFINITY は正の無限大を表す定数。
任意の実数よりも必ず大きい値として扱われるので、最小値を求めるときなどに使われる。
例えばAよりBが小さいならという条件式の時にAを Float::INFINITY にしておくことで、最初にどんな値が来てもその条件を満たすことができる。
最初に条件を満たすことができれば、その条件内でAにBを代入すればいいのであとは通常の条件式として扱うことができる。
min_diff = Float::INFINITY
A = [170, 180, 175, 160, 165].sort
A.each_cons(2) do |a, b|
diff = b - a
if diff < min_diff
初めての比較では min_diff が確実に更新される
min_diff = diff
end
end
「-Float::INFINITY」
-Float::INFINITY は負の無限大を表す定数。
最大値を求めるときに -Float::INFINITY を使う。
※Float::INFINITY の逆の用途で使うだけなので解説は省略
「関数」
配列のmemberを関数内で使って、関数終了後も中の値を保持したい場合には関数外で定義して関数の引数に渡す。
例:paiza クラスの作成(クラス・構造体メニュー)
def employee(order, member)
if order[0] == "make"
member << [order[1], "#{order[2]}"]
elsif order[0] == "getnum"
puts member[order[1].to_i - 1][0]
else
puts member[order[1].to_i - 1][1]
end
end
n = gets.chomp.to_i
order = n.times.map { gets.chomp.split }
member = []
order.each do |o|
employee(o, member)
end
「再帰関数とは」
自分自身を呼び出す関数のこと。
例:階乗
def factorial(n)
return 1 if n == 0 # 終了条件(nが0になったら 1 を返して終了)
n * factorial(n - 1) # 再帰呼び出し
end
puts factorial(5) # => 120
流れの解説
ステップ1:関数の呼び出しの流れ
factorial(5) = 5 * factorial(4)
factorial(4) = 4 * factorial(3)
factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1 * factorial(0)
factorial(0) = 1 (ここで終了!)
ステップ2:逆戻りしながら計算
factorial(0) = 1 (ここから戻り始める)
factorial(1) = 1 * factorial(0) = 1 * 1 = 1
factorial(2) = 2 * factorial(1) = 2 * 1 = 2
factorial(3) = 3 * factorial(2) = 3 * 2 = 6
factorial(4) = 4 * factorial(3) = 4 * 6 = 24
factorial(5) = 5 * factorial(4) = 5 * 24 = 120 (最終的な答え!)
✅ つまり再帰関数はどんどん分解していく(呼び出しを進める)。
factorial(0) = 1 になったら戻って計算を開始。
戻りながら掛け算をすることで最終的な結果を求める。
5! = 5 × 4 × 3 × 2 × 1 = 120 になる。
★再帰関数の特徴★
自分自身を呼び出して処理を分割するところにあり、関数を呼び出すときその関数は他の関数の中にスタックされるため、一度呼び出した関数が処理を終わるまで、他の関数の処理は進まない。
階乗の例の場合、factorial(5) を最初に呼び出すとまだ結果が出ないので、次に factorial(4) を呼び出す…….ということを続ける。
最終的に factorial(0) という終了条件に到達し、return 1 が実行される。
なので最後の factorial(0) は 1 という結果になる。
そして 計算結果が返ってきた時に、スタックしていたそれぞれの呼び出し元へと順番に結果が渡される。
具体的には、最後のfactorial(0)の前の 1 * factorial(1 - 1) で、factorial(1 - 1) が factorial(0) = 1となるわけなので、1 * 1 = 1 となる。つまり factorial(1) も 1 を返す。
factorial(1) の結果も出たのでその前に戻り、
2 * factorial(2 - 1) をみて factorial(2 - 1) = factorial(1) = 1 な訳なので、
2 * 1 = 2 となる。つまり factorial(2) は 2 を返す。
これを続けていくと最後の factorial(5) は「120」を返すという仕組み。
例:フィボナッチ数列
def fibonacci(n)
return n if n <= 1 # 終了条件
fibonacci(n - 1) + fibonacci(n - 2) # 再帰呼び出し
end
puts fibonacci(6) # => 8
再帰関数のデメリット
【処理が遅くなる】
・フィボナッチ数のように同じ計算を何度も繰り返す場合は非効率になる。
・メモ化(Memoization)や動的計画法(Dynamic Programming)**を使うと高速化できる。
【スタックオーバーフローの可能性】
・再帰を深くしすぎると、メモリを使いすぎてエラーになる。
※特にRubyでは1000回くらいでエラーになることが多い
・ループ(イテレーション)で書き換えられるなら、そちらを使うのも手。
【ループ vs 再帰関数】
深い再帰はスタックオーバーフローの可能性があるのでループの方が安全。
しかし、木構造や分割統治法(マージソートなど)のように、再帰が自然にマッチする場合は再帰が便利。
「貪欲法(Greedy Algorithm)」
「常にその場で最適な選択をすることで、最終的に最適解を求める」 というアルゴリズムの考え方。
「お釣りを渡すとき、できるだけ少ない枚数のコインで渡すには?」
coins = [500, 100, 50, 10, 5, 1] amount = 876
result = {}
coins.each do |coin|
result[coin] = amount / coin
amount %= coin
end
puts result #⇒ {500=>1, 100=>3, 50=>1, 10=>2, 5=>1, 1=>1}
⚠️貪欲法が最適解を導けないケース
「大きい硬貨が小さい硬貨の倍数ではない」場合、貪欲法では 最適な組み合わせが選べないことがある。
前述の問題で例えると coins = [9, 6, 1]、amount = 12 が与えられて、最小枚数のコインでお釣りを渡したい場合、貪欲法の結果は {9=>1, 1=>3} になるものの、本来は {6=>2}だけで済むので貪欲法では不適格。
こう言った場合に使えるのは以下の動的計画法。
「動的計画法(DP)」
問題を小さな部分問題に分解し、それらの解を再利用することで計算量を削減する手法。
どういうときに使うのか
①最適部分構造がある場合
「大きな問題を小さな問題の解で構成できる」場合DPが使える。
②重複する部分問題がある場合
同じ計算を何度もすることになる場合、計算結果を記録して再利用できる。
③選択肢が複数あり、その中から最適なものを選ぶ場合
④すべての可能性を試すと計算量が爆発する場合
(貪欲法では解けない、指数時間を削減)
⑤「部分和問題」や「経路探索」など、サブタスクの累積によって解が決まる場合
「前の状態を1つだけ見ればよいか?」がDPが必要かどうかの判断基準。
DPを使ってどこを比較すべきかを考える
①問題文の「目的」を明確にする
支払う硬貨の枚数を最小にしたり、増加部分列の長さを最大化したり。
Brian Kernighan のアルゴリズム
整数の 2 進数表現における 1 の個数を効率的に数えるアルゴリズム。
数のビットを 1 つずつ調べて 1 の個数をカウントする方法(ビットシフトを使う方法)よりも高速に処理できる。
n = 13
count = 0
while n > 0
最下位の1を削除
n &= (n - 1)
count += 1
end
puts count #⇒ 3