cli  
 
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: 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:

  1. Exclude
  2. 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"]

Rust: Early Example of Custom Exclude Method

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

Rust: The Utility Module Containing Generic Unique Method

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

Rust: Applying Genuine Unique Function to Songs Record


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

Rust: Function Helper to build String from &str

The Song Struct Module

Now we can build our struct in simple manners, without the burden of previously <&lt> 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.

Rust: The Alternate Songs Module Containing Get Songs Method

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

Rust: Finishing The Task: Unique Tag Elements

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 tags 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() {…}
  1. Sender
  2. Receiver
  3. 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"]

Rust: Concurrency with Channel: Sender and Receive

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