group_by と partition
CoddyのRubyジャーニー「ロジックとフロー」セクションの一部 — レッスン 25/56。
時には、コレクションをフィルタリングするのではなく、分割したいことがあります。2つのメソッドがほとんどのケースをカバーしています。
group_by はブロックを受け取り、ハッシュを返します。ブロックの各ユニークな実行結果がキーとなり、その結果を生成した要素の配列へとマッピングされます:
words = ["hi", "hello", "yo", "hey", "bonjour"]
by_length = words.group_by { |w| w.length }
puts by_length.inspect
# {2=>["hi", "yo"], 5=>["hello"], 3=>["hey"], 7=>["bonjour"]}1回のパスで項目をバケット化する、最もクリーンな方法です。
partition はバイナリ版です。ブロックが真を返した要素の配列と、それ以外の要素の配列のペアを返します:
evens, odds = [1, 2, 3, 4, 5].partition { |n| n.even? }
puts evens.inspect # [2, 4]
puts odds.inspect # [1, 3, 5]バケットがちょうど2つの場合は partition を、多数になる可能性がある場合は group_by を使用します。
チャレンジ
中級name:score 形式のカンマ区切りリスト(例:Alice:88,Bob:42,Charlie:75)を読み込みます。group_by とそれに続く partition を使用して、小さなレポートを作成してください。
ステップ:
- 各エントリを
[name, score]のペアにパースします(score は整数です) nameの最初の文字(大文字)でgroup_byを行います- 各文字グループについて、アルファベット順に、エントリを合格(
score >= 60)と不合格にpartitionし、グループごとに1行出力します:
<LETTER>: pass=<names_passing_joined_by_comma> fail=<names_failing_joined_by_comma>各グループ内では、名前は元の順序で表示されます。片方に名前がない場合は、空のままにします(例:fail=)。
入力が Alice:88,Adam:42,Bob:75,Beth:55,Cara:90 の場合、出力は以下のようになります:
A: pass=Alice fail=Adam
B: pass=Bob fail=Beth
C: pass=Cara fail=チートシート
group_by は、ブロックの結果をキーとするハッシュにコレクションを分割します:
words = ["hi", "hello", "yo"]
by_length = words.group_by { |w| w.length }
# {2=>["hi", "yo"], 5=>["hello"]}partition は、正確に2つの配列(条件に一致するものと一致しないもの)に分割します:
evens, odds = [1, 2, 3, 4, 5].partition { |n| n.even? }
# evens => [2, 4], odds => [1, 3, 5]2つのグループに分ける場合は partition を、多数のグループに分ける場合は group_by を使用します。
自分で試してみよう
entries = gets.chomp.split(",").map do |s|
name, score = s.split(":")
[name, score.to_i]
end
# TODO: 最初の文字(大文字)でグループ化し、各グループをスコア60以上で分割し、
# 'X: pass=... fail=...' をアルファベット順に表示する
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
ロジックとフローのすべてのレッスン
5Enumerable の強力な機能
Select と RejectMap のメソッドチェーンReduce / Injectcount, all?, any?, none?group_by と partitionsort_by, min_by, max_byまとめ:データパイプライン