Preface
Goal: Continue Part One
5: Approach in Solving Unique
A custom pattern matching 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
Exclude Function
You can use filter method instead.
The example of custom exclude
function is,
just a filter
with below details:
fn exclude<'lt>(
value: &'lt str, tags: Vec<&'lt str>
) -> Vec<&'lt str> {
let mut new_tags: Vec<&str> = Vec::new();
for tag in tags.iter() {
if tag != &value {
new_tags.push(tag);
}
}
new_tags
}
fn main() {
let tags: Vec<&str> =
vec!["rock", "jazz", "rock", "pop", "pop"];
println!("{:?}", exclude("rock", tags));
}
With the result similar as below vector
:
$ rustc 07-tags-exclude.rs
$ ./07-tags-exclude
["jazz", "pop", "pop"]
In real world you can use filter
,
instead of using this example.
Recursive Unique Function
With exclude
function above,
we can build unique
function recursively, as below code:
fn unique<'lt>(
tags: Vec<&'lt str>
) -> Vec<&'lt str> {
if tags.len() <= 1 {
tags
} else {
let head = &tags[0];
let tail = tags[1..].to_vec();
let mut new_tail = unique(exclude(head, tail));
new_tail.insert(0, head);
new_tail
}
}
fn main() {
let tags: Vec<&str> =
vec!["rock", "jazz", "rock", "pop", "pop"];
println!("{:?}", unique(tags));
}
With the result similar as below vector
:
$ rustc 07-tags-unique.rs
$ ./07-tags-unique
["rock", "jazz", "pop"]
Both functions can be applied to any type, not just string, so we can apply generic.
Generic Unique Function
Consider to apply generics to unique
function,
as below code:
fn exclude<T: PartialEq>(
value: T, tags: Vec<T>
) -> Vec<T> {
let mut new_tags: Vec<T> = Vec::new();
for tag in tags.into_iter() {
if tag != value {
new_tags.push(tag);
}
}
new_tags
}
fn unique<T: Clone + PartialEq>(
tags: Vec<T>
) -> Vec<T> {
if tags.len() <= 1 {
tags
} else {
let head: T = tags[0].clone();
let tail: Vec<T> = tags[1..].to_vec();
let mut new_tail = unique(
exclude(head.clone(), tail));
new_tail.insert(0, head);
new_tail
}
}
fn main() {
let tags: Vec<&str> =
vec!["rock", "jazz", "rock", "pop", "pop"];
println!("{:?}", unique(tags));
}
With the result similar to previous code.
$ rustc 07-tags-generic.rs
$ ./07-tags-generic
["rock", "jazz", "pop"]
There are a lot of details about these two functions with generics, that would be too long to be explained here.
Custom Utility Module
This would be great if we can reuse, the generic unique function above into its own module.
Instead of two functions, we can merge both into one, to reduce complexity.
pub fn unique<T: Clone + PartialEq>(
tags: Vec<T>
) -> Vec<T> {
if tags.len() <= 1 {
tags
} else {
let head: T = tags[0].clone();
let tail: Vec<T> = tags[1..].to_vec();
let mut excluded: Vec<T> = Vec::new();
for tag in tail.into_iter() {
if tag != head {
excluded.push(tag);
}
}
let mut new_tail = unique(excluded);
new_tail.insert(0, head);
new_tail
}
}
Applying to Songs Record
Finally we got our custom code shorter. Of course with our genuine unique function detail stay hidden in module.
mod my_songs;
mod my_utils;
fn main() {
let songs: Vec<my_songs::Song> =
my_songs::get_songs();
let tags: Vec<&str> = songs
.into_iter()
.filter_map(|song| song.tags)
.flat_map(|tag_s| tag_s)
.collect();
println!("{:?}", my_utils::unique(tags))
}
With the result similar as below vector
:
$ rustc 07-songs-unique.rs
$ ./07-songs-unique
["60s", "jazz", "rock", "70s", "pop"]
6: A Simpler Struct without Lifetime
String Type
Instead of the primitive &str
string slice,
we can utilize String
type.
A Cumbersome Declaration
The reason I use &str
in the first place is,
the length of the declaration of String
vector.
fn main() {
let tags: Vec<String> = vec![
String::from("rock"),
String::from("jazz"),
String::from("rock"),
String::from("pop"),
String::from("pop")
];
println!("{:?}", tags);
}
With the result similar as below vector
:
$ rustc 11-tags-string.rs
$ ./11-tags-string
["rock", "jazz", "rock", "pop", "pop"]
This is simply too long for simple declaration.
Custom Helper
We are programmers right?
This can be simplified using custom function.
There is no reason to worry about declaration complexity.
We can utilize map
method,
as a function helper to build String
from &str
.
fn to_strings(vec_str: Vec<&str>) -> Vec<String> {
let new_strings: Vec<String>;
new_strings = vec_str
.into_iter()
.map(|tag| tag.to_string())
.collect();
new_strings
}
fn main() {
let tags: Vec<String> = to_strings(
vec!["rock", "jazz", "rock", "pop", "pop"]);
println!("{:?}", tags);
}
With the result exactly the same a previous vector
:
$ rustc 11-tags-str-map.rs
$ ./11-tags-str-map
["rock", "jazz", "rock", "pop", "pop"]
The Song Struct Module
Now we can build our struct in simple manners,
without the burden of previously <<>
lifetime complexity.
use std::fmt;
struct Song {
title: String,
tags : Vec<String>
}
impl fmt::Display for Song {
fn fmt(&self, f: &mut fmt::Formatter)-> fmt::Result {
write!(f, "({}, {:?})", self.title, self.tags)
}
}
And fill our record with the given structure.
fn main() {
let song = Song {
title: "Cantaloupe Island".to_string(),
tags: to_strings(vec!["60s", "jazz"])
};
println!("{}", &song);
}
With the result exactly the same a previous vector
:
$ rustc 12-song-struct.rs
$ ./12-song-struct
(Cantaloupe Island, ["60s", "jazz"])
Alternative Songs Module
A remake of our songs module.
I have difficulties to find a right identifier for this module.
You know, thinking about a proper name can take hours.
I have to decide fast so I give it a name ma_songs
,
instead of the original my_songs
.
This begin from a struct with pub
access.
use std::fmt;
pub struct Song {
pub title: String,
pub tags : Option<Vec<String>>
}
impl fmt::Display for Song {
fn fmt(&self, f: &mut fmt::Formatter)-> fmt::Result {
write!(f, "({}, {:?})", self.title, self.tags)
}
}
Then helper with additional Some
as Option
in return
.
We need to use pub
for public function,
for use with dynamic record building.
pub fn to_strings(vec_str: Vec<&str>) -> Option<Vec<String>> {
let new_strings: Vec<String>;
new_strings = vec_str
.into_iter()
.map(|tag| tag.to_string())
.collect();
Some(new_strings)
}
And finally prepopulate the records vector
.
#[allow(dead_code)]
pub fn get_songs() -> Vec<Song> {
vec![
Song { title: "Cantaloupe Island".to_string(),
tags: to_strings(vec!["60s", "jazz"])},
Song { title: "Let It Be".to_string(),
tags: to_strings(vec!["60s", "rock"])},
Song { title: "Knockin' on Heaven's Door".to_string(),
tags: to_strings(vec!["70s", "rock"])},
Song { title: "Emotion".to_string(),
tags: to_strings(vec!["70s", "pop"])},
Song { title: "The River".to_string(), tags: None }
]
}
I appended an additional lint #[allow(dead_code)]
,
because we are not going to use this function in every executable.
We will face dynamic record building without this prepopulated function.
This is simple right.
Using Alternative Songs Module
It is a good time to use our newly created ma_songs
module.
mod ma_songs;
fn main() {
let songs: Vec<ma_songs::Song> =
ma_songs::get_songs();
for song in songs.iter() {
println!("{}", song)
}
}
With the result similar as below vector
.
Each lines containing a record:
$ rustc 13-songs-vector.rs
$ ./13-songs-vector
(Cantaloupe Island, Some(["60s", "jazz"]))
(Let It Be, Some(["60s", "rock"]))
(Knockin' on Heaven's Door, Some(["70s", "rock"]))
(Emotion, Some(["70s", "pop"]))
(The River, None)
Unique Tags Using HashSet
Now we can solve our previous task with String
instead of &str
.
mod ma_songs;
use std::collections::HashSet;
use std::iter::FromIterator;
fn unique(tags: Vec<String>) -> Vec<String> {
let hashtags: HashSet<String> =
HashSet::from_iter(tags.iter().cloned());
Vec::from_iter(hashtags.iter().cloned())
}
fn main() {
let songs: Vec<ma_songs::Song> =
ma_songs::get_songs();
let tags: Vec<String> = songs
.into_iter()
.filter_map(|song| song.tags)
.flat_map(|tag_s| tag_s)
.collect();
println!("{:?}", unique(tags))
}
With the result similar as below vector
:
$ rustc 14-songs-unique.rs
$ ./14-songs-unique
["jazz", "60s", "rock", "pop", "70s"]
Unique Tag Using Custom Utility Module
Finishing The Task
Or we can utilize our custom unique functions.
mod ma_songs;
mod my_utils;
fn main() {
let songs: Vec<ma_songs::Song> =
ma_songs::get_songs();
let tags: Vec<String> = songs
.into_iter()
.filter_map(|song| song.tags)
.flat_map(|tag_s| tag_s)
.collect();
println!("{:?}", my_utils::unique(tags))
}
With the result similar as below vector
:
$ rustc 15-songs-unique.rs
$ ./15-songs-unique
["60s", "jazz", "rock", "70s", "pop"]
Dynamic Record Building
Since the next article part, require data passing, we require an example of dynamic record building.
To avoid too long code,
we can directly add the scope of ma_song
,
to the code by using use
.
mod ma_songs;
use ma_songs::{ Song, to_strings };
mod ma_songs;
use ma_songs::{ Song, to_strings };
mod my_utils;
fn main() {
let mut songs: Vec<Song> = vec![];
songs.push(Song {
title: "Cantaloupe Island".to_string(),
tags: to_strings(vec!["60s", "jazz"])
});
songs.push(Song {
title: "Let It Be".to_string(),
tags: to_strings(vec!["60s", "rock"])
});
let tags: Vec<String> = songs
.into_iter()
.filter_map(|song| song.tags)
.flat_map(|tag_s| tag_s)
.collect();
println!("{:?}", my_utils::unique(tags))
}
With the result similar as below vector
:
$ rustc 16-songs-dynamic.rs
$ ./16-songs-dynamic
["60s", "jazz", "rock"]
We are done with alternate example.
7: Concurrency with Channel
Concurrency in rust
, is surprisingly easy.
We have a show case to pass data between thread
.
Reference
Custom Flatten Function
In order to make a Sender
demo,
I need to make custom handmade flatten function.
mod ma_songs;
use ma_songs::{ Song, get_songs };
fn flatten(
songs: Vec<Song>
) -> Vec<String> {
let mut new_tags: Vec<String> = Vec::new();
for song in songs.iter() {
if let Some(tags) = &song.tags {
for tag in tags.iter() {
new_tags.push(tag.clone());
}
}
}
new_tags
}
fn main() {
let songs: Vec<Song> = get_songs();
let tags: Vec<String> = flatten(songs);
println!("{:?}", tags)
}
With the result similar as below vector
:
$ rustc 17-songs-flatten.rs
$ ./17-songs-flatten
["60s", "jazz", "60s", "rock", "70s", "rock", "70s", "pop"]
This is simply flatten using all tag
s from all songs records,
using nested for loop
.
This way we can send the result through a Sender
channel,
instead of directly pushing the result to vector
.
The Skeleton
Now we should be ready for the real demo. This is only consist of one short file.
First we have a few modules dependencies.
use std::sync::mpsc;
use std::thread;
use mpsc::Sender;
use mpsc::Receiver;
mod ma_songs;
mod my_utils;
use ma_songs::{ Song, get_songs };
use my_utils::{ unique };
Then we have these three functions below:
fn flatten(tx: Sender<String>) {…}
fn walk(rx: Receiver<String>) -> Vec<String> {…}
fn main() {…}
- Sender
- Receiver
- Main (program entry point)
Sender and Receiver
We should prepare two functions,
one for Sender
, and the other one for Receiver
.
The first one sending each results to Sender
channel.
fn flatten(tx: Sender<String>) {
let songs: Vec<Song> = get_songs();
for song in songs.iter() {
if let Some(tags) = &song.tags {
for tag in tags.iter() {
tx.send(tag.clone()).unwrap();
}
}
}
}
And from the Receiver
channel,
all the result would be gathered as vector
.
fn walk(rx: Receiver<String>) -> Vec<String> {
let mut new_tags: Vec<String> = Vec::new();
for received in rx { new_tags.push(received); }
new_tags
}
Running Both Thread
Pretty short right!
Consider gather both functions in main
entry point.
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || flatten(tx));
let tags: Vec<String> = walk(rx);
println!("{:?}", unique(tags))
}
With the result similar as below vector
:
$ rustc 18-songs-mspc.rs
$ ./18-songs-mspc
["60s", "jazz", "rock", "70s", "pop"]
We are done with simple concurreny example. Concurency is not so complex after all.
What is Next 🤔?
Apart from this article series,
there is a rust feature that catch my attention.
It is the Rust
ability to work well as native NodeJS
using N-API.
Consider continue reading [ Rust - Playing with Records - N-API ].