# Ruby - Playing with Records - Part Three

Article Series

Playing with Records Related Articles.

Table of Content
Where to Discuss?

Local Group

### Preface

Goal: A practical case to collect unique record fields using Ruby.

### Preface

Goal: Continue Part Two

### 7: Approach in Solving Unique

A custom example, that you can rewrite.

#### An Algorithm Case

Why reinvent the wheel?

My intention is writing a custom pattern matching example, so later I can rewrite other algorithm based on this example. You can read the detail on the first article. The record overview.

Consider going back to simple data. We need to discuss about solving unique list.

#### x:xs Pattern Matching

The original `x:xs` pattern matching is made of two functions:

1. Exclude
2. Unique

Pattern matching in `Ruby`, is not as fancy as `haskell`, or even `OCaml`. But what we have in `Ruby` is enough, to solve our little problem.

#### Exclude Function

The `exclude` function is just a `filter` with below details:

``````tags = ['60s', 'jazz', '60s', 'rock',
'70s', 'rock', '70s', 'pop']

def exclude(val, tags)
tags.map{ |tag| tag unless tag == val }
.compact
end

puts "#{exclude('rock', tags)}"``````

With the result as below `array`:

``````❯ ruby 21-exclude.rb
["60s", "jazz", "60s", "70s", "70s", "pop"]``````

#### Exclude Using Delete

Ruby has `delete` function, to make our life simpler.

``````def exclude(val, tags)
tags.delete(val)
tags
end``````

#### Recursive Unique Function

With `exclude` function above, we can build `unique` function recursively, as below code:

``````tags = ['60s', 'jazz', '60s', 'rock',
'70s', 'rock', '70s', 'pop']

def exclude(val, tags)
tags.map{ |tag| tag unless tag == val }
.compact
end

def unique(tags)
if tags.length() <= 1 then
tags
else
end
end

puts "#{unique(tags)}"``````
• Although the syntax looks is, not exactly the same as functional programming in haskell, the algorithm is the same.

With the result as below `array`:

``````❯ ruby 22-unique-a.rb
["60s", "jazz", "rock", "70s", "pop"]``````

#### A Simpler Unique Using Delete

With `ruby`s `delete` function, the function is even simpler.

We can also put this into its own helper module.

``````module HelperUnique
def unique(tags)
if tags.length() <= 1 then
tags
else
end
end
end``````

Now we can apply to out test array.

``````require_relative 'MyHelperUnique'
include HelperUnique

tags = ['60s', 'jazz', '60s', 'rock',
'70s', 'rock', '70s', 'pop']

puts "#{unique(tags)}"``````

#### Applying Unique to data Structure

Now we can apply the method to our unique song challenge.

``````require_relative 'MySongs'
require_relative 'MyHelperUnique'
include HelperUnique

tags = Songs::SONGS
.map { |song| song['tags'] }
.compact.flatten

puts "#{unique(tags)}"``````
• With the same result as below

``````❯ ruby 22-unique-d.rb
["60s", "jazz", "rock", "70s", "pop"]``````

### 8: Concurrency with Channel Agent

`Ruby` support `Actor Model` pattern. We can rewrite previous process through go-like `channel`. One I found in search engine is `agent`.

#### Flatten

Before we go discussing agent, we need an alternative approach to flatten `array`.

Just go straight, examine the code below:

``````require_relative 'MySongs'
include Songs

def flatten(songs)
tags = []

songs.each { |song|
unless song['tags'] == nil
song['tags'].each { |tag| tags << tag }
end
}

tags
end

puts "#{flatten(SONGS)}"``````

With the result as below `array`:

``````❯ ruby 24-flatten.rb
["60s", "jazz", "60s", "rock", "70s", "rock", "70s", "pop"]``````

#### The Skeleton

We should be ready for the real concurreny. This is only consist of one short file.

``````def sender(channel, songs)
# ...
end

# ...
end

# entry point ...``````
1. Producer: sender()
3. Program entry point

#### Producer and Consumer

We should prepare two functions:

1. One for `Producer` that `send` to `channel`,
2. And the other one for `Consumer` that `receive` from `channel`.

`Producer` that `send` to `channel`.

``````def sender(channel, songs)
songs.each do |song|
if song['tags'] != nil
song['tags'].each do |tag|
channel << tag
end
end
end
channel << nil
end``````

`Consumer` that `receive` from `channel`.

``````def receiver(channel)
tags = []

while true
if message != nil
tags << message.to_s
else
puts "#{tags}"
break
end
end
end``````

#### Running Both Routine

So much go like.

Consider gather both function in `main` entry point.

``````require_relative 'MySongs'
include Songs

require 'agent'
chan = channel!(Object)

go! do
sender(chan, SONGS)
end

With the result as below `array`:

``````❯ ruby 25-channel.rb
["60s", "jazz", "60s", "rock", "70s", "rock", "70s", "pop"]``````

### 9: Concurrency with Fiber

There is, however, another way in Ruby. Fiber using `yield` just like in `generator`, but lighter than `Channel`.

#### The Skeleton

The skeleton is similar to above.

``````def sender(channel, songs)
# ...
end

# ...
end

# entry point ...``````
1. Producer: sender()
3. Program entry point

#### Producer and Consumer

We should prepare two functions:

1. One for `Producer` that `yield` to `fiber`,
2. And the other one for `Consumer` that `resume` from `fiber`.

`Producer` that `yield` to `fiber`.

``````def sender(songs)
Fiber.new do
SONGS.each do |song|
if song['tags'] != nil
song['tags'].each do |tag|
Fiber.yield tag
end
end
end
Fiber.yield nil
end
end``````

`Consumer` that `resume` from `fiber`.

``````def receiver(fiber)
tags = []
while true
message = fiber.resume
if message != nil
tags << message
else
puts "#{tags}"
break
end
end
end``````

#### Running Both Routine

So much go like.

Consider gather both function in `main` entry point.

``````require_relative 'MyStructSongs'
include StructSongs

fiber = sender(SONGS)
With the result as below `array`:
``````❯ ruby 27-fiber.rb
This is the end of our `ruby` journey in this article. We shall meet again in other article.