Making A Program To Pick Blog Topics For Me
Friday, October 15, 2021
One of the biggest troubles I have while running this blog is choosing a topic to write about. My goal is to share everything that I find intriguing, but the issue is I find way too many things interesting! So to help me pick I decided to turn to Twitter to help. I ran a poll with 4 writing topics and the results were.... interesting.
It resulted in a three way tie. It would appear as either I have a diverse viewer base, or those who saw the poll are trolls. What I could conclude for sure is that no one wanted to read about digital privacy and security, which is a bummer in my opinion. Anyway, since Twitter couldn't help me decide a topic, I decided to make a program that would!
Since I run Linux these days, I decided it was easiest to just write a small command line program. What language to write it in? Well I picked rust. I like rust and wanted some more practice so it just seemed like a no brainier for this project.
Getting Setup
So the easiest method to installing rust is to follow the instructions on the rust language website. You can write the code in any text editor you like, and then compile and run using the included cargo utility. I highly recommend reading through the Generating a new project section of the linked rust language page if you've never coded in rust before. This will get you used to making a program, so you can follow along with this post.
The first step when starting a project is to break it down into simple steps that we can use to focus our development process. Our topic picking program will have three basic steps:
- Determine the number of topics
- Gather the topics
- Select a random topic
We will make each step a dedicated function in our project, so that it's easy to read the program.
Determining The Number Of Topics
The first function is the one that will determine the number of topics. We will do this by just asking the user how many we want. Here's the function, with an explanation below:
fn get_count() -> u32 {
let mut x = String::new();
println!("How many topics will you need to chose from?");
io::stdin()
.read_line(&mut x)
.expect("Error reading from stdin.");
let x: u32 = x.trim().parse().expect("Error parsing input.");
x
}
The function is rather simple. The first line is the function declaration, which is telling us that it will return a variable of type u32
, aka an unsigned 32 bit integer. The next line initializes a variable named x
, and assigns it to an empty string of characters. The reason we make it a string instead of an integer is so we can take in user input, and then convert into an integer. We add the term mut
after the let
statement to tell cargo that this variable is mutable, meaning we plan to change it's value later on. Then next little section of code will print the string "How many topics will you need to chose from?"
to the console window. Next it uses the stdin()
function to read the user input. The .read_line(&mut x)
function will take the actual input and append it to the x
variable, while the .expect()
function will handle if there is an issue while taking the user input.
It's important to note that to use the io::stdin()
function that we will need to tell the program to import the io function library. To do that, we will add the line using std::io;
at the top of our program, before the fn main()
declaration.
Next we reassign the x variable, this time explicitly telling the program we want it to be of type u32. The functions that we tack onto the x variable after the =
sign will turn the string we have stored in the x variable into the type we want. It's important to have the .expect()
here as well as trying to turn a character such a E into a number will result in errors. Lastly we tell the function to return the value of x. We leave off the semicolon on this line to keep it as an expression with a return value, instead of a simple statement.
Gathering The Topics
The next function is pretty similar to the last, but it introduces a little spin to allow some expansiveness. Here's the code:
fn get_topics(count: u32) -> Vec<String> {
let mut topics: Vec<String> = Vec::new();
for _i in 0..count {
let mut input = String::new();
println!("Please input a topic:");
io::stdin()
.read_line(&mut input)
.expect("Error reading from stdin.");
topics.push(input.to_string());
}
topics
}
What's mostly new here is the for loop. What it's doing is repeating the code inside of its curly brackets from 0 until a variable called count. We get the variable count from the function decoration, which is shown by having the variable in the parenthesis. The for loop will ask the user for a topic, and then add it to a vector of strings. A vector is one of two variable types that can hold multiple variables. We use a vector instead of the other type, an array, as arrays can't change size once they are initialized, unlike vectors. We reset the input variable to a new string each time the loop runs, as the method of input we use only adds to the string, not overwrites it.
Side Note: We very well could make the topics variable an array instead of a vector. I originally had the whole program in one function when writing it so the vector resizing made sense then. If the array method is preferred for optimizing system usage, please let me know and I'll update the program and this guide.
Selecting A Random Topic
The last function we will write is the one that will be selecting our topic.
fn pick_random(x: u32) -> usize {
let rand_int = rand::thread_rng().gen_range(0..x);
usize::try_from(rand_int).expect("Error converting u32 to usize.")
}
This function is a lot more simple. The first line will generate a random number from 0 to x, an input variable for the function. Then it converts the random number to the type usize
, which we will need to point out the selected topic for the program. The second line lacks a semicolon, meaning the function will return the converted variable. To make this work will need to import two libraries at the top of our program, use rand::Rng;
for the random number generator to work, and use std::convert::TryFrom;
for the conversion to usize
to work.
The Full Program
Once we put all of our pieces together, we can use the three functions in the main()
function as so:
use std::io;
use std::convert::TryFrom;
use rand::Rng;
fn main() {
let count = get_count();
let topics = get_topics(count);
let selected_index: usize = pick_random(count);
println!("\nYou should pick the topic {}", topics[selected_index]);
}
fn get_count() -> u32 {
let mut x = String::new();
println!("How many topics will you need to chose from?");
io::stdin()
.read_line(&mut x)
.expect("Error reading from stdin.");
let x: u32 = x.trim().parse().expect("Error parsing input.");
x
}
fn get_topics(count: u32) -> Vec<String> {
let mut topics: Vec<String> = Vec::new();
for _i in 0..count {
let mut input = String::new();
println!("Please input a topic:");
io::stdin()
.read_line(&mut input)
.expect("Error reading from stdin.");
topics.push(input.to_string());
}
topics
}
fn pick_random(x: u32) -> usize {
let rand_int = rand::thread_rng().gen_range(0..x);
usize::try_from(rand_int).expect("Error converting u32 to usize.")
}
As promised, it will gather the number of topics, gather the topics, then select a random one and print it to the console. You can see so in my output window below.
And what do you know, it picked programming!
It's important that technical articles like this one stays updated and is peer reviewed. If you want to help out and keep it updated and reviewed, you can find the program source code on GitLab, and the source of this article on GitLab as well.
Thanks, Jacob