Blog

On DJing, music, productivity, professional growth, and personal journey

Ask Me Anything 002

Discovering New Music, Gig Pricing, Beatport Improvement Ideas, and My Top Productivity Tip

The second instalment of the AMA series is here! In this episode, I answer the following four questions:

Whenever I look at your tracklists, I’m always amazed at how many new artists I discover thanks to your show, and it makes me wonder how you discover all those names and tracks in the first place. What does your process of finding new music look like?
I am a beginner DJ and I want to ask how much I should charge for my set to make it fair.
If you were given the opportunity to make any changes Beatport, what would you change? (You can make several). What from this list is already in the backlog?
What would be your number one tip for someone who wants to become more productive?

Preview:

Rave Podcast 150

After a short delay, Rave Podcast is back for you to enjoy!

This episode is all about proper Techno: groovy, slamming, heads-down, and sometimes melodic too. I just love listening to this kind of stuff when I’m walking, running or in the gym, so any kind of sports body activity, and of course in the clubs too (though I guess dancing can be put in the same category, haha).

I feel that nowadays many people use the word ‘techno’ to describe something entirely different, sometimes they call progressive house tunes ‘techno’, sometimes trance tracks are mislabeled as ‘techno’, and there is, of course, a whole ‘melodic techno’ thing which rarely does anything to do with techno. So enjoy this proper, real Techno mix (with a couple of trancey tracks).

A quick note for those who subscribed to the podcast on Apple Podcasts or any other podcast app: I’ve moved the podcast to another podcast-hosting service. Hopefully, you won’t notice it as this is something ‘under the hood’, but if anything is wrong, just let me know. As a cool little bonus, new episodes now have chapters (in those podcast apps that support it), allowing you to navigate through the tracklist. Here is a direct link to the RSS feed in case you want to re-subscribe.

Tracklisting:

00:00 Fabio Florido — Dawn Devotion (Original Mix) [Runa]
04:34 Kineta — Cosmic Flux (Original Mix) [UTE]
08:41 Introversion — Hopeless Dreaming (Original Mix) [Makatao]
12:20 Rudosa — The Procession (Obscure Shape Dub Mix) [Mood Supplier Records]
15:12 Gary Beck — Ghost (Original Mix) [Mutual Rytm]
18:10 Axel Karakasis — Stunned (Original Mix) [Jeton Record]
21:48 Cri Du Coeur — Astro (Hertz Remix) [Sway]
26:07 Paul Ritch — Inner Keeper (Original Mix) [Quartz]
29:35 Sigvard — Neural (Original Mix) [Anaoh]
33:13 Mark Williams — Sometimes (Original Mix) [Hardgroove]
36:51 Tom Hades — Analog Bliss (Original Mix) [Fundaments LTD]
40:29 Keith Carnal — Exorcism (Original Mix) [Second Degree]
44:20 Raul Young — Affright (Original Mix) [MB Elektronics]
47:16 Clercq — Circuits (Original Mix) [MB Elektronics]
51:22 Axel Karakasis — Inner Voice (Original Mix) [Remain Records]
55:00 Blue Hour — True (Original Mix) [Blue Hour]
 No comments    51   25 d   Daniel Lesden   Music   Rave Podcast

Psytrance Guide 1.2

I just rolled out a new version of the Psytrance Guide with a couple of exciting changes:

  • Hi-Tech and Psycore are now split into two standalone subgenres (previously, they were grouped together). This was long overdue!
  • New subgenre: Hypnotic Psychedelic. It probably hasn’t formed as an official subgenre yet, but this sound is currently emerging and trending and definitely deserves its recognition.
  • New subgenre: Raw Trance. It is debatable whether even to consider this Raw Trance a part of the Psy ’tribe’, but nevertheless, it has many similarities and shares the same rebellious, underground ethos.
  • All “notable artists and labels” links are now leading to Beatport, so anyone can easily find the tracks they like and support the scene.
  • Many audio examples have been updated.

Enjoy!

 No comments    77   1 mo   Psytrance Guide

Underground Trance Essentials Vol.9

As a Beatport curator, every once in a while, I create DJ charts within the ongoing series titled Underground Trance Essentials to highlight some of the best tracks from the newly added Trance (Raw / Deep / Hypnotic) genre. Whether they were released yesterday or a decade ago, these tracks unquestionably deserve appreciation.

In this instalment, I featured music from Monophonik, Riva, DJ Ali, Shawn Cartier, Ryan James Ford, Komet99, Gabriola, Exotek, Frond, Maara, and Art Of Trance.

Don’t miss out on this selection – take a listen: beatport.com/chart/underground-trance-essentials-vol9/802783

I’m also curating a Spotify playlist featuring tracks from these charts, making it convenient to enjoy them all in one consolidated place.

Super Mario pyramids in Python

Starting this week in the CS50 course, I am now transitioning from C to Python, learning syntax and all the fundamentals of this new language. To kick things off, I have rewritten my very first program, Super Mario pyramids in C. Feels quite nostalgic!

My first attempt in Python looked like this:

def main():
    height = get_height()
    for i in range(1, height + 1):
        print_row(height, i)


# Prompt user for input
def get_height():
    MIN_HEIGHT = 1
    MAX_HEIGHT = 8
    while True:
        try:
            n = int(input("Height: "))
            if MIN_HEIGHT <= n <= MAX_HEIGHT:
                return n
        except ValueError:
            print("Enter a number from 1 to 8")


# Printing columns of the pyramid
def print_row(height, length):
    # Printing the spaces on the left
    spaces = height - length
    print(" " * spaces, end="")

    # Printing hashes for the left side of the pyramid
    print("#" * length, end="")

    # Printing the gap between the sides of the pyramid
    print("  ", end="")

    # Printing hashes for the right side of the pyramid
    print("#" * length)


main()

Then I decided to shrink the print_row function into a single print:

# Printing columns of the pyramid
def print_row(height, length):
    spaces = height - length
    print(" " * spaces + "#" * length + "  " + "#" * length)

But looking at that piece of code, I realised that I don’t need a separate function for this at all. The concatenation of strings within a single print function in Python is amazing!

I’ve also added a condition to check whether the user input is numeric, which might be an overkill for this case, but still good for practice.

So here is my final code:

def main():

    height = get_height()

    # Print pyramid
    for i in range(1, height + 1):
        spaces = height - i
        print(" " * spaces + "#" * i + "  " + "#" * i)


# Prompt user for input
def get_height():

    MIN_HEIGHT = 1
    MAX_HEIGHT = 8

    while True:
        height_str = input("Height: ")

        if height_str.isnumeric():
            height = int(height_str)
            if MIN_HEIGHT <= height <= MAX_HEIGHT:
                return height
            else:
                print("Number must be between 1 and 8, inclusive.")
        else:
            print("Invalid input. Please enter a number.")


if __name__ == '__main__':
    main()

That ending felt a bit weird, but as I’ve learned from Google, it seems like a rule of thumb to finish the program with that line to ensure it can be executed only when the file runs as a script, but not through importing as a module.

 No comments    148   3 mo   CS50   Programming   Python

Image filters in C

Another task in the CS50’s problem set 4 was to apply filters to BMP images. Luckily, the core functionality was provided by the staff, so I had to only implement four functions: grayscale, sepia, reflect, and blur.

Let me share my walkthrough for each function.

Grayscale

First, I need to loop through each pixel in the image. I do this by using two nested loops, one for the height and another for the width, to access each pixel individually:

for (int i = 0; i < height; i++)
{
    for (int j = 0; j < width; j++)
    {
        // Pixel processing will be done here
    }
}

For each pixel, I calculate the average value of its red, green, and blue components. I add up the red, green, and blue values and divide the sum by 3.0 to get the average.

int average =
    round((image[i][j].rgbtRed + image[i][j].rgbtGreen + image[i][j].rgbtBlue) / 3.0);

This new average value represents the intensity of the grayscale colour for that pixel.

Why do I divide by 3.0 instead of 3, you may ask? It’s a decision related to data types and precision. When you divide by an integer (e. g., 3), the result will also be an integer, potentially causing a loss of precision in the calculation.

By using 3.0, we explicitly specify that we want floating-point division. In C, if at least one of the operands in a division operation is a floating-point number (like 3.0), the result will be a floating-point number. This is important for obtaining a more accurate average, especially when dealing with colour values that might have fractional parts after the division.

Consider this example:

int result_int = 5 / 3;    // Result is 1, as it's integer division
float result_float = 5 / 3.0;  // Result is 1.66667, as it's floating-point division

Now, I update the red, green, and blue values of the pixel with the calculated average. This makes all three colour components of the pixel equal, resulting in a grayscale effect:

image[i][j].rgbtRed = average;
image[i][j].rgbtGreen = average;
image[i][j].rgbtBlue = average;

And since these steps are inside the nested loop, the entire image is converted to grayscale.

Here is my full code and the result:

// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Take average of red, green, and blue
            // Take average of red, green, and blue
            int average =
                round((image[i][j].rgbtRed + image[i][j].rgbtGreen + image[i][j].rgbtBlue) / 3.0);

            // Update pixel values
            image[i][j].rgbtRed = average;
            image[i][j].rgbtGreen = average;
            image[i][j].rgbtBlue = average;
        }
    }

    return;
}
Grayscale filter Original image

Sepia

Similar to the grayscale function, I start by looping through each pixel in the image using nested loops for height and width:

for (int i = 0; i < height; i++)
{
    for (int j = 0; j < width; j++)
    {
        // Pixel processing will be done here
    }
}

I extract the original red, green, and blue values of the current pixel to variables for easier manipulation:

int original_red = image[i][j].rgbtRed;
int original_green = image[i][j].rgbtGreen;
int original_blue = image[i][j].rgbtBlue;

Using specific weighted averages, I calculate new values for red, green, and blue to achieve the sepia tone effect. The formula involves multiplying each original colour component by a specific constant and summing them up:

int sepia_red = (int)fmin(
    255, round(0.393 * original_red + 0.769 * original_green + 0.189 * original_blue));
int sepia_green = (int)fmin(
    255, round(0.349 * original_red + 0.686 * original_green + 0.168 * original_blue));
int sepia_blue = (int)fmin(
    255, round(0.272 * original_red + 0.534 * original_green + 0.131 * original_blue));

Note that I use fmin(255, ...) to cap the values at 255 to ensure they don’t exceed the maximum intensity.

Finally, I update the red, green, and blue values of the pixel with the calculated sepia values:

image[i][j].rgbtRed = sepia_red;
image[i][j].rgbtGreen = sepia_green;
image[i][j].rgbtBlue = sepia_blue;

Here is my full code and the result:

// Convert an image to sepia
void sepia(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Compute sepia values
            int original_red = image[i][j].rgbtRed;
            int original_green = image[i][j].rgbtGreen;
            int original_blue = image[i][j].rgbtBlue;

            // Calculate sepia values, rounding to the nearest integer and capping at 255
            int sepia_red = (int) fmin(
                255, round(0.393 * original_red + 0.769 * original_green + 0.189 * original_blue));
            int sepia_green = (int) fmin(
                255, round(0.349 * original_red + 0.686 * original_green + 0.168 * original_blue));
            int sepia_blue = (int) fmin(
                255, round(0.272 * original_red + 0.534 * original_green + 0.131 * original_blue));

            // Update pixel with sepia values
            image[i][j].rgbtRed = sepia_red;
            image[i][j].rgbtGreen = sepia_green;
            image[i][j].rgbtBlue = sepia_blue;
        }
    }
}
Sepia filter Original image

Reflect

For this function, I start by looping through each row of the image using a single loop for the height:

for (int i = 0; i < height; i++)
{
    // Row processing will be done here
}

Within each row, I use two pointers, start and end, to swap pixels from the outer edges towards the centre. The start pointer begins at the first pixel (index 0), and the end pointer begins at the last pixel:

int start = 0;
int end = width - 1;

I use a while loop to continue swapping pixels until the start pointer is no longer less than the end pointer. Within the loop, I use a temporary variable to do the swapping:

while (start < end)
{
    // Swap pixels using a temporary variable
    RGBTRIPLE temp = image[i][start];
    image[i][start] = image[i][end];
    image[i][end] = temp;

    // Move the pointers towards the centre
    start++;
    end--;
}

The entire process is repeated for each row of the image, making the reflection apply to the whole image.

Here is my full code and the result:

// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width])
{
    // Loop over all rows
    for (int i = 0; i < height; i++)
    {
        // Use two pointers to swap pixels from the outer edges towards the centre
        int start = 0;
        int end = width - 1;

        while (start < end)
        {
            // Swap pixels using a temporary variable
            RGBTRIPLE temp = image[i][start];
            image[i][start] = image[i][end];
            image[i][end] = temp;

            // Move the pointers towards the centre
            start++;
            end--;
        }
    }
}
Reflected image Original image

Blur

So, first, I create a temporary image to store the blurred result. This temporary image has the same dimensions as the original image:

RGBTRIPLE temp[height][width];

I then loop through each pixel in the original image using nested loops for height and width:

for (int i = 0; i < height; i++)
{
    for (int j = 0; j < width; j++)
    {
        // Pixel processing will be done here
    }
}

For each pixel, I initialize variables to calculate the average colour values: red_sum, green_sum, blue_sum, and count. These variables will be used to accumulate the colour values of neighbouring pixels:

int red_sum = 0;
int green_sum = 0;
int blue_sum = 0;
int count = 0;

Next, I use nested loops to iterate over a 3x3 grid centred around the current pixel. I use row and col to determine the position of the neighbouring pixels relative to the current pixel:

for (int row = -1; row <= 1; row++)
{
    for (int col = -1; col <= 1; col++)
    {
        // Neighboring pixel calculation will be done here
    }
}

For each neighbouring pixel, I calculate its position using newRow and newCol variables. I then check if it is within the bounds of the image. If it is, I accumulate the colour values (red_sum, green_sum, and blue_sum) and increment the count variable:

int newRow = i + row;
int newCol = j + col;

if (newRow >= 0 && newRow < height && newCol >= 0 && newCol < width)
{
    red_sum += image[newRow][newCol].rgbtRed;
    green_sum += image[newRow][newCol].rgbtGreen;
    blue_sum += image[newRow][newCol].rgbtBlue;
    count++;
}

After iterating over the neighbouring pixels, I calculate the average colour values for the current pixel using the accumulated sums and the count of valid neighbouring pixels and use the round function to round the result to the nearest integer:

temp[i][j].rgbtRed = round((float)red_sum / count);
temp[i][j].rgbtGreen = round((float)green_sum / count);
temp[i][j].rgbtBlue = round((float)blue_sum / count);

Once all pixels have been processed and the temporary image contains the blurred result, I copy the contents of the temporary image back to the original image:

for (int i = 0; i < height; i++)
{
    for (int j = 0; j < width; j++)
    {
        image[i][j] = temp[i][j];
    }
}

I must admit, this one was a bit tough, and it took me some time to figure out!

Here is my full code and the result:

// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
    // Create a temporary image to store the blurred result
    RGBTRIPLE temp[height][width];

    // Loop over all pixels
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            // Initialize variables to calculate the average colour values
            int red_sum = 0;
            int green_sum = 0;
            int blue_sum = 0;
            int count = 0;

            // Loop over neighboring pixels (3x3 grid centered around the current pixel)
            for (int row = -1; row <= 1; row++)
            {
                for (int col = -1; col <= 1; col++)
                {
                    int newRow = i + row;
                    int newCol = j + col;

                    // Check if the neighboring pixel is within the image bounds
                    if (newRow >= 0 && newRow < height && newCol >= 0 && newCol < width)
                    {
                        // Accumulate colour values
                        red_sum += image[newRow][newCol].rgbtRed;
                        green_sum += image[newRow][newCol].rgbtGreen;
                        blue_sum += image[newRow][newCol].rgbtBlue;
                        count++;
                    }
                }
            }

            // Calculate average colour values
            temp[i][j].rgbtRed = round((float) red_sum / count);
            temp[i][j].rgbtGreen = round((float) green_sum / count);
            temp[i][j].rgbtBlue = round((float) blue_sum / count);
        }
    }

    // Copy the blurred image back to the original image
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            image[i][j] = temp[i][j];
        }
    }
}
Blurred image Original image
 No comments    141   3 mo   C   CS50   Programming

Volume changing program in C

As I continue learning computer science, here is my walkthrough of week 4’s problem:

Complete the implementation of volume.c, such that it changes the volume of a sound file by a given factor.

The program should accept three command-line arguments. The first is input, which represents the name of the original audio file. The second is output, which represents the name of the new audio file that should be generated. The third is factor, which is the amount by which the volume of the original audio file should be scaled. For example, if factor is 2.0, then your program should double the volume of the audio file in input and save the newly generated audio file in output.

Your program should first read the header from the input file and write the header to the output file.

Your program should then read the rest of the data from the WAV file, one 16-bit (2-byte) sample at a time. Your program should multiply each sample by the factor and write the new sample to the output file. You may assume that the WAV file will use 16-bit signed values as samples. In practice, WAV files can have varying numbers of bits per sample, but we’ll assume 16-bit samples for this problem.

Your program, if it uses malloc, must not leak any memory.

Let me explain what this program does. It’s a simple C program that modifies the volume of an audio file in the WAV format. The user needs to provide three command-line arguments when running the program: the input audio file name, the output audio file name, and a scaling factor for adjusting the volume.

First, I check if the user provided the correct number of command-line arguments. If not, I print a message explaining the correct usage and exit the program with an error code:

// Check command-line arguments:
if (argc != 4)
{
    printf("Usage: ./volume input.wav output.wav factor\n");
    return 1;
}

Next, I open the input (in the read mode) and output (in the write mode) audio files specified by the user. If the input file cannot be opened, an error message is displayed, and the program exits. Similarly, if the output file cannot be opened, I print an error message and exit the program. The third command-line argument is converted from a string to a floating-point number, representing the scaling factor:

// Open files and determine scaling factor:
FILE *input = fopen(argv[1], "r");
if (input == NULL)
{
    printf("Could not open input file.\n");
    return 1;
}

FILE *output = fopen(argv[2], "w");
if (output == NULL)
{
    printf("Could not open output file.\n");
    return 1;
}

float factor = atof(argv[3]);

Here, I copy the 44-byte WAV header from the input file to the output file:

// Copy WAV Header:
uint8_t header[HEADER_SIZE];
fread(header, HEADER_SIZE, 1, input);
fwrite(header, HEADER_SIZE, 1, output);

A me from a week ago wouldn’t know what this is, so let me elaborate a bit more. I use the data type uint8_t to create an array called header that is 44 elements in size. Each element in this array represents one byte of data. The reason I chose uint8_t is because it is an unsigned (meaning it can only represent non-negative values) 8-bit integer data type.

The WAV file format uses bytes to store information in its header. Using an 8-bit data type ensures that each element of the header array corresponds to one byte of data. This is essential for accurately copying the header, as the header size is 44 bytes, so I’ll assign the value of 44 to this global constant HEADER_SIZE.

Next, I declare a variable buffer to hold a single audio sample:

// Create buffer for a single sample:
int16_t buffer;

Notice the int16_t data type here. In digital audio processing, an audio sample represents the amplitude of a sound wave at a specific point in time. The int16_t data type is a 16-bit signed integer, which means it can represent both positive and negative values using 16 bits (or 2 bytes) of storage. And the choice of int16_t corresponds to the standard bit depth used in many audio files, including WAV files.

Now, I need to read audio samples one by one from the input file, multiply each sample by the scaling factor to adjust the volume, and then modify the sample to write it to the output file. For that task, I use a while loop that continues until the fread function returns 0, indicating that there are no more samples to read:

// Modify Volume and Write to Output:
while (fread(&buffer, sizeof(int16_t), 1, input) != 0)
{
    buffer *= factor;
    fwrite(&buffer, sizeof(int16_t), 1, output);
}

Finally, I close both the input and output files to ensure that all changes are saved. The program has now processed the audio file, adjusted its volume, and saved the modified version to a new file.

Here is my final code:

// Modifies the volume of an audio file

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

// Number of bytes in .wav header
const int HEADER_SIZE = 44;

int main(int argc, char *argv[])
{
    // Check command-line arguments
    if (argc != 4)
    {
        printf("Usage: ./volume input.wav output.wav factor\n");
        return 1;
    }

    // Open files and determine scaling factor
    FILE *input = fopen(argv[1], "r");
    if (input == NULL)
    {
        printf("Could not open file.\n");
        return 1;
    }

    FILE *output = fopen(argv[2], "w");
    if (output == NULL)
    {
        printf("Could not open file.\n");
        return 1;
    }

    float factor = atof(argv[3]);

    // Copy header from input file to output file
    uint8_t header[HEADER_SIZE];
    fread(header, HEADER_SIZE, 1, input);
    fwrite(header, HEADER_SIZE, 1, output);

    // Create a buffer for a single sample
    int16_t buffer;

    // Read single sample from input into buffer while there are samples left to read
    while (fread(&buffer, sizeof(int16_t), 1, input) != 0)
    {
        // Update volume of sample
        buffer *= factor;

        // Write updated sample to new file
        fwrite(&buffer, sizeof(int16_t), 1, output);
    }

    // Close files
    fclose(input);
    fclose(output);
}
 No comments    115   3 mo   C   CS50   Programming

Rave Podcast 149

The March episode of Rave Podcast is here, and this time we start with banging 140 BPM straight away! A lot of hard grooves in the first half and a cosmic trance towards the end.

Tracklisting:

00:00 David Moleon — Back To Moleon (Original Mix) [Transfiguration Recordings]
05:14 Introversion — Nostalgia (Original Mix) [Arts]
07:56 Name_Less, JBILO — Blaze (Original Mix) [Tronic]
10:04 Sera J — Rainbow (Original Mix) [Warg]
12:45 Faster Horses — 21 (Original Mix) [Dancefloor Impact Research]
16:57 Moes — Aftermath (Original Mix) [DistroKid]
20:02 Regent — Encoder (Original Mix) [Mutual Rytm]
23:48 Alan Backdrop — Dysfunctional Reality (Original Mix) [Not On Label]
29:12 Raffaele Attanasio — Aogiri Tree (Original Mix) [Mutual Rytm]
31:38 Sko Raw — Moe’s Dream (Original Mix) [Note Recordings]
34:16 Miju — Metatron (Bliss Inc Remix) [Headroom Rec]
37:50 Dusty Tech, Parsa Jafari — Surafil (Original Mix) [The Acid Mind]
40:55 Hypnocoustics — Satori In Pokhara (Original Mix) [Nano Records]
45:20 Kineta — Scorpius (Original Mix) [UTE]
49:41 Sioc — Eternity (Original Mix) [Proxima]
53:33 Oprofessionell — Psy-TB (Original Mix) [UTE]
 No comments    69   3 mo   Daniel Lesden   Music   Rave Podcast

Runoff, votes program in C

By the end of my second week of learning programming, there was a problem to solve which admittedly took me some time to figure out as the level of complexity from the previous problem jumped quite significantly.

The problem description is pretty lengthy, so instead of writing it all here, let me just put a video walkthrough that explains what needs to be done. They also provided the distribution code that has the main function already pre-written. If you’d like to dive deeper into the problem or perhaps even try solving it yourself, definitely start with that video and the source code.

The task was to complete the six functions named vote, tabulate, print_winner, find_mind, is_tie, and eliminate.

As always, if you are an experienced programmer, your feedback would be appreciated. The whole point of this post is for me to get a better understanding of what I’ve learnt for the past week or so.

Vote

So, the first function, vote, should update the global preferences array to indicate that the voter has that candidate as their preference.

I’ll start with writing a pseudocode:

// Record preference if vote is valid
bool vote(int voter, int rank, string name)
{
    // If candidate name is found and not eliminated, update the voter preference and return true
    // If candidate name not found or eliminated, return false
}

To check all votes, I need to iterate through all candidates using the global candidate_count variable:

// Record preference if vote is valid
bool vote(int voter, int rank, string name)
{
    for (int i = 0; i < candidate_count; i++)
    {
    // If candidate name is found and not eliminated, update the voter preference and return true
    }
    
    // If candidate name not found or eliminated, return false
}

To check whether the candidate’s name matches, I’ll use the strcmp function to compare the string name (which is the input parameter of the vote function) with the name field of the ith candidate struct. And I’ll check whether the candidate is eliminated in the same if statement, like so:

// Record preference if vote is valid
bool vote(int voter, int rank, string name)
{
    for (int i = 0; i < candidate_count; i++)
    {
        if (strcmp(candidates[i].name, name) == 0 && !candidates[i].eliminated)
        {
            // Record the vote preference

            return true;
        }
    }

    // Candidate name not found or eliminated, vote is invalid
    return false;
}

The most challenging part for me was to figure out how to record the vote preference, the actual action inside this loop and the sole purpose of this function.

Let me share my thought process. What does ‘record (or update) the vote preference’ actually mean? Well, preferences is a two-dimensional array used to store the voting preferences of each voter. It represents a grid where each row corresponds to a voter, and each column corresponds to a rank of preference for a candidate. The vote function also takes two inputs: voter, which represents the index of the current voter in the array of voters, and ranks, which represents the index of the preference rank for the current candidate.

Putting it all together, it means that for the current voter at index voter, at the given preference rank rank, the candidate at index i in the array of candidates is chosen as the preference. So the final code of this function is the following:

// Record preference if vote is valid
bool vote(int voter, int rank, string name)
{
    for (int i = 0; i < candidate_count; i++)
    {
        if (strcmp(candidates[i].name, name) == 0 && !candidates[i].eliminated)
        {
            // Record the vote preference
            preferences[voter][rank] = i;
            return true;
        }
    }

    // Candidate name not found or eliminated, vote is invalid
    return false;
}

I still wrap my head around this line preferences[voter][rank] = i;, so let me provide additional explanation for future me reading this blog.

Let’s say, we have three candidates named Alice, Bob, and Charlie, indexed as 0, 1, and 2 in the candidates’ array respectively. We also have three voters indexed as 0, 1, and 2 too. And, as an example, let’s say the first voter ranked their candidate preferences in the ballot in the following order: Charlie, Alice, Bob.

Now if we look back at that line of code again, it effectively stores the index of the chosen candidate in the preferences array, like so:

  Rank [0] Rank [1] Rank [2]
Voter [0] 2 0 1

I hope it explains!

Tabulate

The next function, tabulate, should update the number of votes each candidate has at this stage in the runoff. Recall that at each stage in the runoff, every voter effectively votes for their top-preferred candidate who has not already been eliminated.

I’ll start with a pseudocode again to outline the general logic of this function:

// Tabulate votes for non-eliminated candidates
void tabulate(void)
{
    // Iterate through each voter
        // Iterate through each rank until a valid vote is cast
            // Get the index of the preferred candidate for this rank

            // Check if the preferred candidate is not eliminated
                // Update the vote count for the preferred candidate and break out of the loop
}

So the function iterates through each voter, considering their ranked preferences. For each voter, it iterates through each rank until it finds a valid vote for a non-eliminated candidate. For that purpose, I’ll a basic loop-within-the-loop construction:

// Tabulate votes for non-eliminated candidates
void tabulate(void)
{
    // Iterate through each voter
    for (int i = 0; i < voter_count; i++)
    {
        // Iterate through each rank until a valid vote is cast
        for (int j = 0; j < candidate_count; j++)
        {
            // Get the index of the preferred candidate for this rank

            // Check if the preferred candidate is not eliminated
        }
    }
}

It seems I’m going to need a new integer variable to store the index of the candidate preferred by the current voter at the given rank, which I could later use to incrementally update the vote count.

And here is the final code for this function:

// Tabulate votes for non-eliminated candidates
void tabulate(void)
{
    // Iterate through each voter
    for (int i = 0; i < voter_count; i++)
    {
        // Iterate through each rank until a valid vote is cast
        for (int j = 0; j < candidate_count; j++)
        {
            // Get the index of the preferred candidate for this rank
            int preferred_candidate_index = preferences[i][j];

            // Check if the preferred candidate is not eliminated
            if (!candidates[preferred_candidate_index].eliminated)
            {
                // Update the vote count for the preferred candidate and break
                // out of the loop
                candidates[preferred_candidate_index].votes++;
                break;
            }
        }
    }
}

Just to clarify, preferences[i][j] retrieves the index of the candidate preferred by the voter at index i for the current rank j. This index is then stored in the preferred_candidate_index variable.

This line candidates[preferred_candidate_index].votes+ increments the votes field for the candidate with the index preferred_candidate_index. It means that the voter’s vote at the current rank has been cast for this candidate, and we update the candidate’s vote count accordingly.

Print winner

The print_winner should print the winner’s name If any candidate has more than half of the vote, and don’t print anything if nobody has won the election:

// Print the winner of the election, if there is one
bool print_winner(void)
{
    // Check each candidate's votes
    
        // If a candidate has more than half of the votes, they are the winner


    // No winner found, return false
}

So I’ll iterate through each candidate again and check if their votes exceed half of the total votes. To see whether the candidate has the majority of votes, I’ll check if their votes are greater than the half of total voters:

// Print the winner of the election, if there is one
bool print_winner(void)
{
    // Check each candidate's votes
    for (int i = 0; i < candidate_count; i++)
    {
        // If a candidate has more than half of the votes, they are the winner
        if (candidates[i].votes > voter_count / 2)
        {
            printf("%s\n", candidates[i].name);
            return true;
        }
    }

    // No winner found
    return false;
}

Find min

The function should return the minimum vote total for any candidate who is still in the election. For that, I’ll loop through the candidates to find the one who is both still in the election and has the fewest number of votes.

And my first instinct was to write this:

// Return the minimum number of votes any remaining candidate has
int find_min(void)
{
    int min = 0;

    // Loop through each candidate to find the one with the fewest votes and is
    // still in the election
    for (int i = 0; i < candidate_count; i++)
    {
        if (!candidates[i].eliminated &&
            (candidates[i].votes < min || min == 0))
        {
            min = candidates[i].votes;
        }
    }

    return min;
}

It’s only later I realised that this code has a potential flaw that might lead to a wrong outcome in certain cases. Can you guess what it is?

The potential issue in that version is related to the initialisation of the min variable with a value of 0. In some scenarios, the condition //(candidates[i].votes < min || min == 0)// will always be true for the first candidate in the loop, and //min// will be updated to the candidate's votes. This would result in the function returning 0, even though it's not the minimum number of votes any remaining candidate has.

This is pretty obvious now – the min variable should be initialised with a voter_count + 1:

// Return the minimum number of votes any remaining candidate has
int find_min(void)
{
    int min = voter_count + 1;

    // Loop through each candidate to find the one with the fewest votes and is
    // still in the election
    for (int i = 0; i < candidate_count; i++)
    {
        if (!candidates[i].eliminated && candidates[i].votes < min)
        {
            min = candidates[i].votes;
        }
    }

    return min;
}

Here’s an example to compare both versions of the function:

Suppose we have three candidates, and initially, they have the following votes: Candidate A (2 votes), Candidate B (3 votes), Candidate C (1 vote). After some eliminations, only Candidate A and Candidate B remain in the election.

  • In version 1, min is initialised to 0, and the condition (candidates[i].votes < min || min == 0) will be true for the first candidate (Candidate A) with 2 votes, updating min to 2. Therefore, the function would incorrectly return 2 as the minimum number of votes.
  • In version 2, min is initialised to a value greater than the maximum possible votes (voter_count + 1), and the correct minimum number of votes (1 in this case) will be returned. A pretty subtle, but important difference!

The rest of the functions are pretty straightforward, so I’ll just put my final code for each function:

Is tie

// Return true if the election is tied between all candidates, false otherwise
bool is_tie(int min)
{
    // Loop through each candidate
    for (int i = 0; i < candidate_count; i++)
    {
        // Check if the candidate is still in the election and has a different
        // number of votes than the minimum
        if (!candidates[i].eliminated && candidates[i].votes != min)
        {
            // Not a tie as at least one candidate has a different number of
            // votes
            return false;
        }
    }

    // All remaining candidates have the same number of votes, it's a tie
    return true;
}

Eliminate

// Eliminate the candidate (or candidates) in last place
void eliminate(int min)
{
    for (int i = 0; i < candidate_count; i++)
    {
        if (!candidates[i].eliminated && candidates[i].votes == min)
        {
            candidates[i].eliminated = true;
        }
    }
}
 No comments    162   3 mo   C   CS50   Programming

A substitution cipher in C

To finish off my first week of learning programming, I’ve completed another task which summed up pretty much everything I’ve learned so far about the basics: variables, operators, loops, functions, arrays, and such.

Here is the problem description:

In a substitution cipher, we “encrypt” (i.e., conceal in a reversible way) a message by replacing every letter with another letter. To do so, we use a key: in this case, a mapping of each of the letters of the alphabet to the letter it should correspond to when we encrypt it. To “decrypt” the message, the receiver of the message would need to know the key, so that they can reverse the process: translating the encrypt text (generally called ciphertext) back into the original message (generally called plaintext).

A key, for example, might be the string NQXPOMAFTRHLZGECYJIUWSKDVB. This 26-character key means that A (the first letter of the alphabet) should be converted into N (the first character of the key), B (the second letter of the alphabet) should be converted into Q (the second character of the key), and so forth.

A message like HELLO, then, would be encrypted as FOLLE, replacing each of the letters according to the mapping determined by the key.

Create a program that enables you to encrypt messages using a substitution cipher. At the time the user executes the program, they should decide, by providing a command-line argument, on what the key should be in the secret message they’ll provide at runtime.

Your program must accept a single command-line argument, the key to use for the substitution. The key itself should be case-insensitive, so whether any character in the key is uppercase or lowercase should not affect the behavior of your program. If your program is executed without any command-line arguments or with more than one command-line argument, your program should print an error message of your choice (with printf) and return from main a value of 1 (which tends to signify an error) immediately. If the key is invalid (as by not containing 26 characters, containing any character that is not an alphabetic character, or not containing each letter exactly once), your program should print an error message of your choice (with printf) and return from main a value of 1 immediately.

Your program must output plaintext: (without a newline) and then prompt the user for a string of plaintext (using get_string). Your program must output ciphertext: (without a newline) followed by the plaintext’s corresponding ciphertext, with each alphabetical character in the plaintext substituted for the corresponding character in the ciphertext; non-alphabetical characters should be outputted unchanged. Your program must preserve case: capitalized letters must remain capitalized letters; lowercase letters must remain lowercase letters. After outputting ciphertext, you should print a newline. Your program should then exit by returning 0 from main.

Phew, that’s a pretty bulky description! I’ll start by outlining the big steps I would need to solve the problem as follows:

  1. Get key
  2. Validate key
  3. Get plaintext
  4. Encipher
  5. Print ciphertext

As I’ve just learned, the program can receive command-line arguments, so I can use the main function with two new input parameters: integer argc and array argv. The key that the program should receive is the second word in the command line (after the file name), so keeping arrays’ zero-indexing in mind, I start with this:

#include <cs50.h>
#include <stdio.h>

int main(int argc, string argv[])
{
    // Get key
    string key = argv[1];
}

Now the key is stored in the key variable. Next, I’ll get the key length, which is pretty easy now as I know about the strlen function, and also check whether the length equals 26:

#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(int argc, string argv[])
{
    // Get key
    string key = argv[1];

  // Get key length
    int key_length = strlen(key);

    // Check key length
    if (key_length != 26)
    {
        printf("Key must contain 26 characters.\n");
        return 1;
    }
}

I don’t quite like the ‘magic number’ 26 in the if condition, so I’ll move it out as a global constant. I also feel that I need to make the key variable global (though I’m not sure whether it’s a good idea):

#include <cs50.h>
#include <stdio.h>
#include <string.h>

// Constant
const int valid_key_length = 26;

string key;

int main(int argc, string argv[])
{
    // Get key
    key = argv[1];

  // Get key length
    int key_length = strlen(key);

    // Check key length
    if (key_length != valid_key_length)
    {
        printf("Key must contain 26 characters.\n");
        return 1;
    }
}

I guess I could have created a separate function for checking the key length, but would it be worth it given that the whole code is literally just one if and one printf? Let me know in the comments!

Next, I’m going to check whether the key only consists of alphabetic characters, and this is where a separate function definitely should be a good idea. So, I’ll create a function called is_key_alphabetic that can take a string as input and give me a boolean as output – whether the key consists of alphabetical is true or false.

Inside the function, I need a loop that keeps checking the string character by character until it’s not equal to nul, which in plain English means ‘until the end of the word’s length’. You may wonder, why making such an extravagant condition of the loop, and to be honest, it’s just for the sake of exercising. I find it pretty interesting that all arrays in C are ‘secretly’ end with null to indicate the end of the string.

So, here is the first function:

bool is_key_alphabetic(string s)
{
    for (int i = 0; s[i] != '\0'; i++)
    {
        if (!isalpha(s[i]))
        {
            return false;
        }
    }
    return true;
}

Now upon coming back to my program and writing this blog post, I realised that the naming here is a bit messy. I mean, the function is called is_key_alphabetic, whereas inside the loop there is an if statement checking whether a character is not alphabetic, and in that it case returns false, and then the whole function returns true. Apart from the variable name s which I admit isn’t very informative, is there any anything else I could improve here?

In the meantime, I’ll create another function to check for any repeated characters in the key. From a logical perspective, identifying a repeated character should be as easy as checking whether it’s equal to itself. However, in the code, I don’t know a better solution than doing a loop within a loop:

bool is_key_repeated(string s)
{
    for (int i = 0; s[i] != '\0'; i++)
    {
        for (int j = 0; j < i; j++)
        {
            if (s[i] == s[j])
            {
                printf("Key must not contain repeated characters.\n");
                return false;
            }
        }
    }
    return true;
}

Once again, upon writing this blog post, I already spotted some things I could’ve done better. For example, including printf in this function probably wasn’t a good idea.

Next, I’ll create another function to encipher plaintext. It seems I need to make some sort of mapping to assign each letter of the plaintext to according letter of the key. And thanks to the previous problem, Scrabble, I now know how to do it!

So, I’ll call the function encipher, which is going to take the input text, iterate through each character, replace it with the corresponding character from the provided key while preserving the case, and then print the resulting ciphertext.

void encipher(string text)
{
    int length = strlen(text);
    for (int i = 0; i < length; i++)
    {
        if (isupper(text[i]))
        {
            text[i] = toupper(key[text[i] - 'A']);
        }
        else if (islower(text[i]))
        {
            text[i] = tolower(key[text[i] - 'a']);
        }
    }
    // Print ciphertext
    printf("ciphertext: %s\n", text);
}

All I need from now is just to get a plaintext input using the get_string function, and then call all three functions I’ve created above in the main function. And so here is my final code:

// A program in C that enables you to encrypt messages using a substitution cipher.

#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>

// Constant
const int valid_key_length = 26;

// Function prototypes
bool is_key_alphabetic(string key);
bool is_key_repeated(string key);
void encipher(string text);

string key;

// Main
int main(int argc, string argv[])
{

    // Check if there's a key provided
    if (argc != 2)
    {
        printf("Usage: ./substitution KEY\n");
        return 1;
    }

    // Get key
    key = argv[1];

    // Get key length
    int key_length = strlen(key);

    // Check key length
    if (key_length != valid_key_length)
    {
        printf("Key must contain 26 characters.\n");
        return 1;
    }

    // Check key contains only alphabetic characters
    if (!is_key_alphabetic(key))
    {
        printf("Key must only contain alphabetical characters.\n");
        return 1;
    }

    // Check key doesn't contain repeated characters
    if (!is_key_repeated(key))
    {
        printf("Key must not contain repeated characters.\n");
        return 1;
    }

    // Get plaintext
    string plaintext = get_string("plaintext ");

    // Encipher
    encipher(plaintext);

    // Print ciphertext
    printf("\n");
    return 0;
}

bool is_key_alphabetic(string s)
{
    for (int i = 0; s[i] != '\0'; i++)
    {
        if (!isalpha(s[i]))
        {
            return false;
        }
    }
    return true;
}

bool is_key_repeated(string s)
{
    for (int i = 0; s[i] != '\0'; i++)
    {
        for (int j = 0; j < i; j++)
        {
            if (s[i] == s[j])
            {
                printf("Key must not contain repeated characters.\n");
                return false;
            }
        }
    }
    return true;
}

void encipher(string text)
{
    int length = strlen(text);
    for (int i = 0; i < length; i++)
    {
        if (isupper(text[i]))
        {
            text[i] = toupper(key[text[i] - 'A']);
        }
        else if (islower(text[i]))
        {
            text[i] = tolower(key[text[i] - 'a']);
        }
    }
    // Print ciphertext
    printf("ciphertext: %s\n", text);
}
 1 comment    399   3 mo   C   CS50   Programming
Earlier Ctrl + ↓
© Daniel Sokolovskiy, 2024
Powered by Aegea