group_by and partition
Part of the Logic & Flow section of Coddy's Ruby journey — lesson 25 of 56.
Sometimes you want to split a collection rather than filter it. Two methods cover most cases.
group_by takes a block and returns a hash. Each unique block result becomes a key, mapping to an array of the elements that produced it:
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"]}It's the cleanest way to bucket items in one pass.
partition is the binary version: it returns a pair of arrays, the elements where the block was true, and the rest:
evens, odds = [1, 2, 3, 4, 5].partition { |n| n.even? }
puts evens.inspect # [2, 4]
puts odds.inspect # [1, 3, 5]Use partition when there are exactly two buckets, group_by when there can be many.
Challenge
MediumRead a comma-separated list of name:score entries (e.g. Alice:88,Bob:42,Charlie:75). Use group_by followed by partition to produce a small report.
Steps:
- Parse each entry into a
[name, score]pair (score is an integer) group_bythe pair's first letter ofname(uppercase)- For each letter group, in alphabetical order,
partitionthe entries into passing (score >= 60) and failing, then print one line per group:
<LETTER>: pass=<names_passing_joined_by_comma> fail=<names_failing_joined_by_comma>Within each group, names appear in their original order. If a side has no names, leave it empty (e.g. fail=).
For input Alice:88,Adam:42,Bob:75,Beth:55,Cara:90, the output is:
A: pass=Alice fail=Adam
B: pass=Bob fail=Beth
C: pass=Cara fail=Cheat sheet
group_by splits a collection into a hash keyed by block result:
words = ["hi", "hello", "yo"]
by_length = words.group_by { |w| w.length }
# {2=>["hi", "yo"], 5=>["hello"]}partition splits into exactly two arrays (matching and non-matching):
evens, odds = [1, 2, 3, 4, 5].partition { |n| n.even? }
# evens => [2, 4], odds => [1, 3, 5]Use partition for two buckets, group_by for many.
Try it yourself
entries = gets.chomp.split(",").map do |s|
name, score = s.split(":")
[name, score.to_i]
end
# TODO: group by first letter (upper), partition each group by score >= 60,
# print 'X: pass=... fail=...' in alphabetical letter order
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Logic & Flow
1Strings In Depth
String Methods OverviewString InterpolationIterating Over StringsSplit and JoinRecap - String Weaver4Blocks, Procs & Lambdas
What is a Block?do..end vs BracesThe yield KeywordBlock ParametersProcs and LambdasRecap - Custom Iterator7Hashes Part 2
Hash.new with DefaultsIterating HashesNested HashesMerging and TransformingRecap - Frequency Counter10Project - Student Records
Project OverviewAdd Student5Enumerable Powerhouse
Select and RejectChaining MapReduce / Injectcount, all?, any?, none?group_by and partitionsort_by, min_by, max_byRecap - Data Pipeline8Advanced Decision Making
Case with Classes & RegexMulti-value whenTernary OperatorInline if / unlessRecap - Grade Classifier