Ruby 入門

Ruby 配列とハッシュのイテレーション

1. 配列(アレイ)のイテレーション

配列(Array)は、要素(Element)の順序付けられたコレクションです。Rubyはこれらをイテレーション(Iteration)するための、極めてエレガントなアプローチを多数提供しています。

1.1 each メソッド

each メソッドは、配列をイテレーションするための最もベーシックかつ頻繁に使用されるメソッドです。これは配列内の各要素に対して、提供されたブロック(Block)を順次実行します。

# 例:each を使用して配列の各要素をプリントする
numbers = [1, 2, 3, 4, 5] 

numbers.each do |number|
  puts number
end 

# 出力:
# 1
# 2
# 3
# 4
# 5

この例では、each メソッドが numbers 配列をイテレーションしています。配列内の各 number に対して、ブロック do |number| ... end が1回ずつ実行され、puts number ステートメント(Statement)が現在の数値をコンソール(Console)にプリントします。

アドバンスドな例:イテレーション中の要素の変更(強い警告)

each を使用したイテレーション中に元の配列を直接変更することは(予期せぬビヘイビアを引き起こすことが多いため)強く非推奨とされていますが、デモンストレーションの目的で以下に一つのアプローチを示します:

# 例:イテレーション中に配列を変更する(通常は非推奨!)
numbers = [1, 2, 3, 4, 5]
new_numbers = [] # 推奨されるプラクティス:元の配列への変更によるイシューを防ぐため、新しい配列を作成する

numbers.each do |number|
  new_numbers << number * 2 # 現在の数値を2倍にし、新しい配列にアペンド(Append)する
end 

puts new_numbers.inspect # => [2, 4, 6, 8, 10]
puts numbers.inspect     # => [1, 2, 3, 4, 5]

警告: each の使用中に現在イテレーションされている配列を変更すると、要素のスキップや無限ループ(Infinite loop)など、予測不可能な結果を招く可能性があります。より優れたアプローチは、上記のように新しい配列を作成するか、配列のトランスフォーム(Transform)に特化して設計された map メソッドを使用することです。

1.2 each_with_index メソッド

each_with_index メソッドは each と非常に似ていますが、現在の要素を提供するだけでなく、同時にその要素の配列内におけるインデックス(Index)も提供します。

# 例:each_with_index を使用して各要素とそのインデックスをプリントする
fruits = ["apple", "banana", "orange"] 

fruits.each_with_index do |fruit, index|
  puts "#{index + 1}: #{fruit}"
end 

# 出力:
# 1: apple
# 2: banana
# 3: orange

この例では、each_with_indexfruits 配列をイテレーションしています。各 fruit とそれに対応する index に対してブロックが実行されます。ヒューマンリーダブルなフォーマットにするため、プリント時にインデックス index に 1 を加算しています。

アドバンスドな例:each_with_index を使用した条件付きの要素変更

# 例:each_with_index を使用して条件付きで要素を変更する
words = ["hello", "world", "ruby", "programming"] 

words.each_with_index do |word, index|
  words[index] = word.upcase if index.even? # インデックスが偶数の場合、それを大文字(Uppercase)に変換する
end 

puts words.inspect # 出力: ["HELLO", "world", "RUBY", "programming"]

1.3 map(または collect)メソッド

map メソッド(エイリアスは collect です)は配列をイテレーションし、ブロックを各要素に適用し、すべてのブロックの実行結果を含む完全に新しい配列をリターン(Return)します。これは配列データのフォーマット変換を行うための強力なツールです。

# 例:map を使用して、数値が2倍になった新しい配列を作成する
numbers = [1, 2, 3, 4, 5] 

doubled_numbers = numbers.map do |number|
  number * 2
end 

puts doubled_numbers.inspect # 出力: [2, 4, 6, 8, 10]
puts numbers.inspect         # 出力: [1, 2, 3, 4, 5] (元の配列は変更されません)

アドバンスドな例:map を使用したハッシュの配列の変換

プロダクト(Product)のハッシュ(Hash)で構成される配列があり、すべての「プロダクト名」のみを含む新しい配列を作成したいと仮定します:

products = [
  { name: "Laptop", price: 1200 },
  { name: "Keyboard", price: 75 },
  { name: "Mouse", price: 25 }
] 

product_names = products.map do |product|
  product[:name]
end 

puts product_names.inspect # 出力: ["Laptop", "Keyboard", "Mouse"]

1.4 select(または filter)メソッド

select メソッド(エイリアスは filter です)は配列をイテレーションし、ブロックの実行結果が true となる要素のみを含む完全に新しい配列をリターンします。

# 例:select を使用して配列から偶数をフィルタリング(Filter)する
numbers = [1, 2, 3, 4, 5, 6] 

even_numbers = numbers.select do |number|
  number.even? # 各値はブール値(Boolean)をリターンします
end 

puts even_numbers.inspect # 出力: [2, 4, 6]
puts numbers.inspect      # 出力: [1, 2, 3, 4, 5, 6] (元の配列は変更されません)

アドバンスドな例:select を使用したより複雑な条件のハンドリング

# 例:複雑な条件と組み合わせて select を使用する
words = ["apple", "banana", "kiwi", "orange", "grape"] 

long_words = words.select do |word|
  word.length > 5 # 単語の長さに基づいてブール値をリターンします
end 

puts long_words.inspect # 出力: ["banana", "orange"]

1.5 reject メソッド

reject メソッドの役割は select の正確な逆です。配列をイテレーションし、ブロックの実行結果が false となる要素のみを含む完全に新しい配列をリターンします(つまり、条件にマッチする要素を排除します)。

# 例:reject を使用して配列から偶数を排除する(奇数をキープする)
numbers = [1, 2, 3, 4, 5, 6] 

odd_numbers = numbers.reject do |number|
  number.even? # select のロジックの逆です
end 

puts odd_numbers.inspect # 出力: [1, 3, 5]
puts numbers.inspect     # 出力: [1, 2, 3, 4, 5, 6] (元の配列は変更されません)

アドバンスドな例:reject を使用した属性に基づく特定のオブジェクトのリムーブ

# 例:reject を使用して管理者(Admin)権限を持つユーザーをリムーブする
users = [
  { name: "Alice", admin: true },
  { name: "Bob", admin: false },
  { name: "Charlie", admin: true }
] 

non_admins = users.reject do |user|
  user[:admin] # user[:admin] が truthy な場合、暗黙的に true をリターンし、そのユーザーは排除されます
end 

puts non_admins.inspect # 出力: [{:name=>"Bob", :admin=>false}]
puts users.inspect      # 元の users 配列が変更されていないことを示します

1.6 find(または detect)メソッド

find メソッド(エイリアスは detect です)は配列をイテレーションし、ブロックの実行結果が最初に true となる要素をリターンします。条件を満たす要素が存在しない場合は nil をリターンします。

# 例:find を使用して配列内で最初の偶数を検索する
numbers = [1, 3, 2, 4, 5] 

first_even = numbers.find do |number|
  number.even? # この条件を true にする最初のインスタンス(Instance)をリターンします
end 

puts first_even      # 出力: 2
puts numbers.inspect # 出力: [1, 3, 2, 4, 5] (元の配列は変更されません)

アドバンスドな例:オブジェクト配列内で条件に基づいて特定のオブジェクトを検索する

# 例:find を使用して名前で特定のプロダクトを特定する
products = [
  { name: "Laptop", price: 1200 },
  { name: "Keyboard", price: 75 },
  { name: "Mouse", price: 25 }
] 

keyboard = products.find do |product|
  product[:name] == "Keyboard"
end 

puts keyboard.inspect # 出力: {:name=>"Keyboard", :price=>75}

1.7 find_all(または select)メソッド

find_allselect と完全に等価(Equivalent)であることに注意してください。これら2つのメソッドは全く同じオペレーションを実行します。

2. ハッシュ(Hash)のイテレーション

ハッシュ(ディクショナリや連想配列とも呼ばれます)はデータを Key-Valueペア(キーと値のペア)の形式でストアします。Rubyは非常に便利なハッシュのイテレーションアプローチをいくつか提供しています。

2.1 each メソッド

ハッシュに対して使用される場合、each メソッドは各 Key-Valueペア をイテレーションします。

# 例:each を使用してハッシュ内の各 Key-Valueペア をプリントする
person = { name: "Alice", age: 30, city: "New York" } 

person.each do |key, value|
  puts "#{key}: #{value}"
end 

# 出力:
# name: Alice
# age: 30
# city: New York

アドバンスドな例:Key-Valueペア に基づく条件付きのアウトプット

# 例:条件ロジックと組み合わせて each を使用する
config = { timeout: 10, retries: 3, logging: true } 

config.each do |key, value|
  if value.is_a?(Numeric)
    puts "#{key} の値は数値(Numeric)です: #{value}"
  else
    puts "#{key} の値は: #{value}"
  end
end

2.2 each_key メソッド

each_key メソッドは、ハッシュのキー(Key)のみをイテレーションするために特化しています。

# 例:each_key を使用してハッシュ内の各キーをプリントする
person = { name: "Alice", age: 30, city: "New York" } 

person.each_key do |key|
  puts key
end 

# 出力:
# name
# age
# city

アドバンスドな例:キーがトランスフォームされた新しいハッシュの作成

# 例:each_key を使用してハッシュのキーを変換する(新しいハッシュを作成)
data = { "first_name" => "John", "last_name" => "Doe" }
transformed_data = {} 

data.each_key do |key|
  new_key = key.gsub("_", " ").capitalize
  transformed_data[new_key] = data[key]
end 

puts transformed_data.inspect # 出力: {"First name"=>"John", "Last name"=>"Doe"}

2.3 each_value メソッド

each_value メソッドは、ハッシュの値(Value)のみをイテレーションするために特化しています。

# 例:each_value を使用してハッシュ内の各値をプリントする
person = { name: "Alice", age: 30, city: "New York" } 

person.each_value do |value|
  puts value
end 

# 出力:
# Alice
# 30
# New York

アドバンスドな例:ハッシュの値を通じた統計データの計算

# 例:each_value を使用してトータルスコアを計算する
student_scores = { alice: 85, bob: 92, charlie: 78 }
total_score = 0 

student_scores.each_value do |score|
  total_score += score
end 

puts "トータルスコア: #{total_score}" # 出力: トータルスコア: 255

2.4 ハッシュにおける map メソッド

ハッシュに対しても map を使用することができますが、極めて重要なポイントがあります。それは、新しいハッシュではなく、ブロックの実行結果で構成される配列(Array)をリターンするという点です。

# 例:map を使用して値を配列に抽出する
person = { name: "Alice", age: 30, city: "New York" } 

values = person.map do |key, value|
  value # 各 Key-Valueペア の値のみをリターンします
end 

puts values.inspect # 出力: ["Alice", 30, "New York"]

アドバンスドな例:Key-Valueペア を完全に新しい文字列の配列に変換する

# 例:map を使用してフォーマットされた文字列(String)配列を作成する
product = { name: "Book", price: 25, quantity: 2 } 

details = product.map do |key, value|
  "#{key.to_s.capitalize}: #{value}"
end 

puts details.inspect # 出力: ["Name: Book", "Price: 25", "Quantity: 2"]

2.5 ハッシュにおける select および reject メソッド

select および reject はハッシュに対しても同様に適用可能です。map とは異なり、これらは完全に新しいハッシュをリターンします。新しいハッシュには、ブロック内で指定された条件を満たす(または reject の場合は満たさない)Key-Valueペア のみが含まれます。

# 例:select を使用して値に基づいて Key-Valueペア をフィルタリングする
ages = { alice: 30, bob: 25, charlie: 35 } 

older_than_30 = ages.select do |name, age|
  age > 30 # 値が 30 より大きいペアのみをキープします
end 

puts older_than_30.inspect # 出力: {:charlie=>35}
puts ages.inspect          # 元のハッシュが変更されていないことを示します
# 例:reject を使用してキーに基づいて Key-Valueペア をフィルタリングする
ages = { alice: 30, bob: 25, charlie: 35 } 

not_alice = ages.reject do |name, age|
  name == :alice # Alice に属する Key-Valueペア をリムーブします
end 

puts not_alice.inspect # 出力: {:bob=>25, :charlie=>35}
puts ages.inspect      # 元のハッシュが変更されていないことを示します