Preface
Goal: A practical case to collect unique record fields using Ruby.
After Python, why not Ruby?
Reference Reading
Most of the time, I just google things rather than, reading official documentation.
Source Examples
You can obtain source examples here:
Common Use Case
Task: Get the unique tag string
Please read overview for more detail.
Prepopulated Data
Songs and Poetry
module Songs
SONGS = [
{ 'title' => 'Cantaloupe Island',
'tags' => ['60s', 'jazz'] },
{ 'title' => 'Let It Be',
'tags' => ['60s', 'rock'] },
{ 'title' => 'Knockin\' on Heaven\'s Door',
'tags' => ['70s', 'rock'] },
{ 'title' => 'Emotion',
'tags' => ['70s', 'pop'] },
{ 'title' => 'The River' },
]
end
Ruby Solution
The Answer
There are different waty actually. One of them is this oneliner as below:
require_relative 'MySongs'
include Songs
tags = SONGS
.map { |song| song['tags'] }
.compact
.flatten
.uniq
puts "#{tags}"
Enough with introduction, at this point we should go straight to coding.
Environment
No need any special setup. Just run and voila..!
1: Data Structure Using Hash
We are going to use array
and hash
,
throught out this article.
Simple Array
Consider begin with simple array
.
tags = ["rock", "jazz", "rock", "pop", "pop"]
# expression substitution
puts "#{tags}"
To dump array in ruby
,
you should using expression substitution
,
instead of just plain puts
.
The result is similar as below list
:
❯ ruby 01-tags.rb
["rock", "jazz", "rock", "pop", "pop"]
Hash
Writing record in hash
is straightforward.
song = { 'title' => 'Cantaloupe Island',
'tags' => ['60s', 'jazz'] }
puts song
With the result as below:
❯ ruby 02-song.rb
{"title"=>"Cantaloupe Island", "tags"=>["60s", "jazz"]}
The Songs Structure
We can continue our journey to records just using hash
.
No need any complex structure.
songs = [
{ 'title' => 'Cantaloupe Island',
'tags' => ['60s', 'jazz'] },
{ 'title' => 'Let It Be',
'tags' => ['60s', 'rock'] },
{ 'title' => 'Knockin\' on Heaven\'s Door',
'tags' => ['70s', 'rock'] },
{ 'title' => 'Emotion',
'tags' => ['70s', 'pop'] },
{ 'title' => 'The River' },
]
songs.each do |song| puts song end
With the result similar as below record:
❯ ruby 03-songs.rb
{"title"=>"Cantaloupe Island", "tags"=>["60s", "jazz"]}
{"title"=>"Let It Be", "tags"=>["60s", "rock"]}
{"title"=>"Knockin' on Heaven's Door", "tags"=>["70s", "rock"]}
{"title"=>"Emotion", "tags"=>["70s", "pop"]}
{"title"=>"The River"}
2: Separating Module
Since we need to reuse the songs record multiple times, it is a good idea to separate the record structure from logic.
Songs Module
The code can be shown as below:
module Songs
SONGS = [
{ 'title' => 'Cantaloupe Island',
'tags' => ['60s', 'jazz'] },
{ 'title' => 'Let It Be',
'tags' => ['60s', 'rock'] },
{ 'title' => 'Knockin\' on Heaven\'s Door',
'tags' => ['70s', 'rock'] },
{ 'title' => 'Emotion',
'tags' => ['70s', 'pop'] },
{ 'title' => 'The River' },
]
end
Using Songs Module
Now we can have a very short code.
require_relative 'MySongs'
include Songs
SONGS.each do |song| puts song end
With the result exactly the same as above array
of hash
.
❯ ruby 04-module.rb
{"title"=>"Cantaloupe Island", "tags"=>["60s", "jazz"]}
{"title"=>"Let It Be", "tags"=>["60s", "rock"]}
{"title"=>"Knockin' on Heaven's Door", "tags"=>["70s", "rock"]}
{"title"=>"Emotion", "tags"=>["70s", "pop"]}
{"title"=>"The River"}
3: Finishing The Task
Map, Compact, Flatten, Unique
There are at least three ways to do this.
- Filter Map
- Compact
- Select
All with the same result,
as list
of list
shown below.
❯ ruby 05-filter-map.rb
[["60s", "jazz"], ["60s", "rock"], ["70s", "rock"], ["70s", "pop"]]
❯ ruby 05-map-select.rb
[["60s", "jazz"], ["60s", "rock"], ["70s", "rock"], ["70s", "pop"]]
❯ ruby 05-compact.rb
[["60s", "jazz"], ["60s", "rock"], ["70s", "rock"], ["70s", "pop"]]
However, we need to examine map
first.
Map
Consider this map
example below:
require_relative 'MySongs'
include Songs
tagss = SONGS.map { |song|
song['tags'] if song['tags'] != nil }
puts "#{tagss}"
With the result of list
of list
, as shown below.
❯ ruby 05-comprehension.rb
[["60s", "jazz"], ["60s", "rock"], ["70s", "rock"], ["70s", "pop"], nil]
Wait!
Why is there still nil
?
Because, if the conditional false, it return nil.
It is the same as writing this:
tagss = SONGS.map { |song| song['tags'] }
Now what is our option?
Filter Map
One combined function
tagss = SONGS.filter_map do |song|
song['tags'] if song['tags'] != nil
end
Compact
Map, then compact
tagss = SONGS.map do |song|
song['tags']
end.compact
Select
Map, then select
tagss = SONGS
.map { |song| song['tags'] }
.select { |tags| tags != nil }
Unique
Built in flatten and uniq.
To solve unique
list,
we can utilize built in method.
require_relative 'MySongs'
include Songs
tags = SONGS
.map { |song| song['tags'] }
.compact
.flatten
.uniq
puts "#{tags}"
The code is tidy.
What is Next 🤔?
We have alternative way to build the record structure.
Consider continue reading [ Ruby - Playing with Records - Part Two ].