Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

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"]

Ruby: A very simple array

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"]}

Ruby: Two tales of dictionary

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

Ruby: The Songs Module Containing List of Record

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: Using Songs Module

❯ 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 }

Ruby: Extracting Records

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}"

Ruby: Solving Unique Song

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 ].