Categories
Best Practices Programming Tutorial

Best Practices Part 1: Understand the Basics

Best Practices for STEM Loyola Programming Challenges

Contents:

Overview

This is a multi-part tutorial on participating in the STEM Loyola Programming Challenges. The objective of this series of tutorials is to equip you with the necessary tools to become competitive as you participate in future challenges.

Give me six hours to chop down a tree and I will spend the first four sharpening the axe

Abraham Lincoln

This series will highlight the tools that you will need such as programming languages, data structures, algorithms, and other principles/tricks. In this part, we will provide an overview of the automatic challenges grader and a good method of approaching solving the problems.

Understand the Automatic Challenges Grader

Since all programming challenges are automatically graded, it matters to understand the system. The grader works by accepting the source code you upload (e.g. file.cpp, file.java, file.c), compile the source code, execute the program, supply it with inputs, and observe if the program produces the correct output.

Suppose a problem requires you to input two positive integers less than a million and compute the sum of the two numbers. One of the correct ways to solve this problem is shown in the C++ source code below.

#include <iostream>
<iostream>using namespace std;

int main(){
    int A, B;
    cin >> A >> B;

    cout << A + B << endl;

    return 0;
}</iostream>Code language: PHP (php)

Note the following from the above source code:

  1. When inputting the two number into the program, we did not have to request the compiler to input the two numbers like cout << Enter the two numbers: ";
    This is because the compiler will automatically know to supply the two numbers and more importantly, anything you display on the standard output will be treated as your answer (resulting in the grader marking your program output as wrong).
  2. Similarly, when outputting the sum of the two numbers, we only displayed the sum without additional words like cout << "Sum is: " which will cause the grader to mark your answer as wrong. Because if the two numbers are 5 and 6, their sum is “11” and not “Sum is: 11
  3. The program ends with return 0;. Your program should run only once and return zero at the end. Ending your program with return main(); will result in time-limit error.
  4. Since the problem states that the two numbers are positive and less than a million, we chose int because we are sure a million can fit in the int data type. If the two numbers were bigger (say 5 billion), then we would have to select a bigger data type like long long int. Refer to our Programming and Data Structures in C++ guideline for an in-depth treatise of data structure.

Think of Problem’s Special Cases

Your submitted solution will be tested against five or more different test cases. Different test cases are used to ensure that your program has covered different special cases.

For instance, suppose the problem is to input two numbers and find the quotient when the first number is divided by the second. A basic solution can look like the source code below.

#include<iostream>

using namespace std;

int main(){
    float A, B;
    cin >> A >> B;

    cout << A / B << endl;

    return 0;
}
Code language: PHP (php)

However, the above program will crash when the second number is zero. This is due to the fact that in Mathematics, dividing a number by zero is undefined. What will happen exactly during your program’s execution depends on the compiler that was used, some compilers have protection against such cases and will produce inf as the answer. Some compilers will just result in run-time error and your program crashing.

To become a better programmer is to always think of such special cases that may crash your program and write your code defensively. A better solution for the above problem is as follows.

#include<iostream>
#include<cmath>

using namespace std;

int main(){
    const float INFINITESIMAL = 0.000001;
    float A, B;

    cin >> A >> B;

    if( abs(B) < INFINITESIMAL ){
        cout << "Cannot divide by zero!" << endl;
    } else {
        cout << A / B << endl;
    }

    return 0;
}Code language: PHP (php)

Note the following from the above solution:

  1. Since the numbers are assumed to be decimal numbers, line 12 does not check if the second number is zero using if( B == 0 ) because that works reliably with whole numbers (e.g. int, long, short, etc.) and not with decimal numbers (e.g. float and double). To check if a decimal number is zero, you select your precision (the smallest number that anything less than that will be assumed to be practically zero). Then compare if the magnitude of your number is less than that.
  2. In line 13, during an actual challenge, only write “Cannot divide by zero!” if the problem has requested you to do so.

Can I Do Better?

After crafting your solution, you should always ask yourself, “can I do better?

With the data structures that you have thought of using, can you do better? Is there a better data structure that you can use to make implementing the solution easier, faster, or use less memory? Do you really need to store all those numbers? Can you do better?

With the algorithm you have selected, is there a better one? Do you need to do something else (e.g. reverse the list, sort the list, find the binary representation of a number, etc.) before applying my algorithm? Can you do better?

You will be amazed by how much you can improve your solutions if you keep asking yourself this question and put in the effort to improve your solutions.

Part 2 “Speed Up Testing and Uploading” is now available.

Categories
Programming Tutorial

Welcome-Back! Challenge Tutorial

Tutorial

Contents:

Now we have the final results of the Welcome-Back Challenge, it is time to review and discuss the problems and their solutions in detail. There is always more than way to implement a solution in programming. We often speak of a better solution/algorithm as the one that requires less memory or execution time. Also, a better algorithm will perform fairly well on a small input as with the large input i.e. if it takes the algorithm 1 second to find a solution for 10X10 grid, it should not take a day to find the solution for 1,000,000X1,000,000 grid.

Click here to download the PDF of the problems.

Note: All the source code snippets will be provided in C++. The provided code snippets are examples of many ways to solve the problems.

Problem A: Decimal Part

The problem had two key challenges: how to extract the fraction part of a decimal number and how to format a number to display only four decimal places.

  1. Extracting a fraction part of a decimal number
    This can be achieved by finding the difference between the decimal number and the whole/integer part of the number.
float fraction = number - int( number );
  1. Display only four decimal places of a number
    In C++, the number of decimal places can be fixed using Input/Output Manipulation header (#include<iomanip>) tags: fixed and setprecision as shown below.
cout << fixed << setprecision(4) << fraction << endl;

Problem B: Gotcha!

This problem requires the application of a typical if-then-else block. After inputting the two numbers (say A and B), the sum and product of the two numbers can be compared as follows.

if( A+B > A*B ) cout << "SUM" << endl;
else if( A+B < A*B ) cout << "PRODUCT" << endl;
else cout << "GOTCHA!" << endl;

Problem C: Kiosk Change

This is an example of a problem that utilizes a greedy algorithm.

A greedy algorithm, as the name suggests, always makes the choice that seems to be the best at that moment. This means that it makes a locally-optimal choice in the hope that this choice will lead to a globally-optimal solution. Source

The minimum number of notes/coins can be determined by always asking the question: What is the biggest note/coin that can fit the current change?

Suppose that we need to return the change of TZS 3,500. The biggest note/coin we can use at the moment is 2000-note. After this, the remaining change will be TZS 1,500. At this point, the biggest note/coin is 1000-note. This will leave TZS 500 which we can use a 500-coin for. Hence, a TZS 3,500 change can be effectively be returned using 3 notes/coins.

Therefore, for any given change, we can walk through the given list of notes/coins (10000, 5000, 2000, 1000, 500, 200, 100, and 50) in descending order and check if the current note can be used to return the remaining change. Suppose the change is in variable change, the above algorithm can be implemented to produce the results in variable notes_needed.

const int DENOM_COUNT = 8;
const int DENOMS[] = { 10000, 5000, 2000, 1000, 500, 200, 100, 50 };

int notes_needed = 0;
while( change > 0 ){
    for( int i = 0; i < DENOM_COUNT; i++ ){ 
        if( change >= DENOMS[i] ){ 
            notes_needed += change / DENOMS[i]; 
            change %= DENOMS[i];  
        }
    }
}

Problem D: Usernames

The objective of this problem is straight forward. We have to go through the list of names and track every time a name is repeated. The challenge is how to maintain a unique count of usernames, and display the total number of unique names before printing the entire list of modified names.

It is always a good practice to ask the question: Is there any data structure that I can leverage?

For tracking a unique list of elements both a set and a map can be used in C++. Since the problem requires to track how many times a username has already been used, a map will be more desirable. Refer to Programming and Data Structure in C++ for a detailed explanation and examples on maps and sets.

Also, the problem requires us to print the total number of unique names before listing the modified names. This suggests that we will have to store the list of modified names somewhere before printing them. There are several ways to accomplish this in C++, here we shall use a string stream.

// Get the total count of usernames
int N;
cin >> N;

stringstream output;  // Stores modified names
map<string, int> uniqueNames;  // Tracks unique names and how many times they are used
string name;

// Process the list of names
while( N-- ){
    cin >> name;

    uniqueNames[name]; // Add the name with a count of zero if it's not in the list

    if( uniqueNames[name] == 0 ){  // First time to encounter the name
        output << name << endl;
        uniqueNames[name] = 1;
    }else{  // The name has already been used
        output << name << ++uniqueNames[name] << endl;
    }
}

// Display the results
cout << uniqueNames.size() << endl;
cout << output.str();Code language: PHP (php)

Problem E: Mike’s Puzzle

The objective of problem E is to trace a path from the top left corner to the bottom left corner while counting the number of visited cells and the direction changes made.

The problem makes this easier since it has guaranteed that the path has neither loops nor branches. This means there is only a single path between the top left and bottom right corner.

This is an example of a simulation problem. As Jonathan described below when answering the question “What does it mean by ‘simulation type problem’ in competitive programming?” in Quora:

It means that the main difficulty in the problem is not figuring out how to solve it conceptually, but instead writing code that follows the (usually complex) set of rules they tell you. The challenge is writing this code quickly and accurately. It is called a “simulation” problem because your code is simulating whatever situation is described in the problem. Source

Jonathan Paulson in Quora

Clearly an array will be a great data structure to use (specifically, an N by N array where N refers to the provided size of the grid). Then, we can fill the array with the ones and zeros.

Before we can start to navigate through the array, we need a way of marking the cells that we have already visited. Since we need to visit the grid only once, we can clear the cell (i.e. change the one to zero) once we visit it. This will simplify tracking the visited cells.

// Get the grid size
int N;
cin >> N;

// Construct and fill in the grid
int grid[N][N];
for(int x = 0; x < N; x++)
    for(int y = 0; y < N; y++)
        cin >> grid[x][y];

int dirChange = 0;  // Track how many times direction changes
int tiles = 1;      // Count how many tiles were touched

enum { NOT_SET, UP, DOWN, RIGHT, LEFT } dir = NOT_SET; // Define and track the direction

// Walk through the grid starting from the top left corner
int x = 0, y = 0;   // Current position

while( x != N-1 || y != N-1 ) {  // Stop when reaching the bottom right (N-1, N-1)
    if( x+1 < N && grid[x+1][y] ){   // Check if can go down
        grid[++x][y] = 0; // Clear the tile from the path
        if( dir != NOT_SET && dir != DOWN ) dirChange++; // Track if direction changed
        dir = DOWN; // Assign the current direction
    }else if( x-1 >= 0 && grid[x-1][y] ){ // Check if can go up
        grid[--x][y] = 0; // Clear the tile from the path
        if( dir != NOT_SET && dir != UP ) dirChange++; // Track if direction changed
        dir = UP; // Assign the current direction
    }else if( y+1 < N && grid[x][y+1] ){ // Check if can go right
        grid[x][++y] = 0; // Clear the tile from the path
        if( dir != NOT_SET && dir != RIGHT ) dirChange++; // Track if direction changed
        dir = RIGHT; // Assign the current direction
    }else if( y-1 >= 0 && grid[x][y-1] ){ // Check if can go left
        grid[x][--y] = 0; // Clear the tile from the path
        if( dir != NOT_SET && dir != LEFT ) dirChange++; // Track if direction changed
        dir = LEFT; // Assign the current direction
    }
    tiles++; // Increment visited tiles
}

cout << tiles - dirChange << endl;Code language: JavaScript (javascript)