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:
- Exclude
- 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
head, *tail = tags
return [head] + unique(exclude(head, tail))
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
head, *tail = tags
tail.delete(head)
return [head] + unique(tail)
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
.
Reference
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
def receiver(channel)
# ...
end
# entry point ...
- Producer: sender()
- Consumer: receiver()
- Program entry point
Producer and Consumer
We should prepare two functions:
- One for
Producer
thatsend
tochannel
, - And the other one for
Consumer
thatreceive
fromchannel
.
Producer
thatsend
tochannel
.
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
thatreceive
fromchannel
.
def receiver(channel)
tags = []
while true
message = channel.receive.first
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
receiver(chan)
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
def receiver(channel)
# ...
end
# entry point ...
- Producer: sender()
- Consumer: receiver()
- Program entry point
Producer and Consumer
We should prepare two functions:
- One for
Producer
thatyield
tofiber
, - And the other one for
Consumer
thatresume
fromfiber
.
Producer
thatyield
tofiber
.
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
thatresume
fromfiber
.
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)
receiver(fiber)
With the result as below array
:
❯ ruby 27-fiber.rb
["60s", "jazz", "60s", "rock", "70s", "rock", "60s", "jazz"]
This is the end of our ruby
journey in this article.
We shall meet again in other article.
What is Next 🤔?
Consider continue reading [ PHP - Playing with Records - Part One ].