PACER: Structured process to solve programming problems

Solve programming problems in a structured and deliberate way

When you are new to programming, you face different challenges that, if simplified, could be reduced to three layers:

  • Logic layer
  • Syntax layer
  • Productive layer

The logic layer refers to your capability and skills to understand a programming problem and suggest one or more ways to solve it. The syntax layer refers to your ability to write code in a given language, using its syntax and specifications, and the productive layer points to the set of tools -frameworks, libraries, technologies...- and best practices, technical and soft, that you have worked with and have experience in, so that you can be productive in a given job.

I'm going to share with you a process I have named PACER (Problem - Algorithm - Code - Execution - Refactor), that will help you create a mental model to solve algorithmic problems in a structured and deliberate way, so that you improve your logic layer skills.

This process is the result of studying and testing different approaches to solve programming logic problems for over 10 years.

The PACER Process

Problem

  • Define the problem in your own words: This helps you go through a cognitive process to validate that you understand what needs to get solved.
  • Input/Output: Identify the expected input and output data types. This step allows you to state what data types/structures you get as an input and what data types/structures you need to return from your solution.
  • Examples: Write some examples of the expected results based on examples of the input data. Use a desk check (manual computing) to understand what output you should get from a solution, given a specific input.
  • Edge cases: Ask or suggest (when not in an interview) the edge cases that the solution should cover. This allows you to identify cases you might cover that are not explicitly shown in the problem statement.
  • Restrictions: Write down the restrictions/rules that are given to the problem in order to set the boundaries of your solution.
/* 
PROBLEM

Write a function that shortens a string of characters fro a - z as follows:
  If the same character repeats consecutively twice, eliminate it from the string.
  Return the shortened string.

- Input: string
- Output: string
- Examples:
  "aaabccddd" -> "abd"
- Edge cases: 
   * What should I return  if I get an empty string
   * What should I return if I get a data type or structure different from a String
- Restrictions: 
   The string characters are a-z
*/

Algorithm

In English, write down the step by step you plan to follow so that you transform the input data into the expected output.

/*
ALGORITHM

Option 1

Define a function superReducedString( ) that receives a string  - str - as a parameter 
  declare a variable newString and assign an empty string as its value
  iterate over every character of the str
    if the character is different from the last character of newString, add it to newString
    otherwise eliminate the last character from newString
  return newString

Option 2   - Regex + Recursion - 

Define a function  superReducedString( ) that receives a string  - str -  as a parameter
 declare a regular expression that matches two equal consecutive characters
 call the string match method to see if there is still duplicates
   if null and str length is 0, return "Empty String"
   if null return str
   otherwise, call the replace method to replace/eliminate the two equal consecutive 
   characters and pass the returned value as the string parameter to call the function 
    recursively
*/

Code

Translate the steps of the previous algorithm into a programming language of your choice.

// CODE

// Code for algorithm - option 1 - 

function superReducedString(str) {
  let newString = "";
  for(let i = 0; i < str.length; i += 1) {
    if (str[i] === newString[newString.length -1]) {
      newString = newString.slice(0,-1);
    } else {
      newString += str[i];
    }
  };
  return newString[0]  !== newString[1] ? newString : "Empty String";
}

// Code for algorithm - option 2 -  // Using regex and recursion

function superReducedString(str) {
  let regex = new RegExp(/([a-z])\1/, "g");
  if (str.match(regex) === null && str.length === 0) {
    return "Empty String";
  } else if (str.match(regex) === null) {
    return str;
  } else {
    return superReducedString(str.replace(regex, ""));
  }
}

Execution

Execute the code to validate that the test cases are returning the expected result.

You are normally given the test cases as part of the problem. In our example, the following are the test cases and their corresponding expected result:

console.log(superReducedString("aaabccddd")); // "abc"
console.log(superReducedString("cccxllyyy" )); // "cxy"
console.log(superReducedString("aa")); // "Empty String"
console.log(superReducedString("baab")); // "Empty String"
console.log(superReducedString("fghiiijkllmnnno")); // "fghijkmno"
console.log(superReducedString("chklssstt")); // "chkls"

Refactor

Think about the following:

  • How could I make this code more readable?
  • How could I make this code more efficient in terms of time complexity (takes less iterations/"time" - and space complexity - uses less memory - Big O Notation )?
  • Think about edge cases that you might not have covered yet and apply the PACER process from the Algorithm step to change what is necessary to cover the new cases.
/* 
REFACTOR
This example shortens the lines of code written
*/

const regex = /([a-z])\1/g;
const superReducedString = str => regex.test(str) ? superReducedString(str.replace(regex, "")) : str || "Empty String";

console.log(superReducedString("aaabccddd")); // "abc"
console.log(superReducedString("cccxllyyy" )); // "cxy"
console.log(superReducedString("aa")); // "Empty String"
console.log(superReducedString("baab")); // "Empty String"
console.log(superReducedString("fghiiijkllmnnno")); // "fghijkmno"
console.log(superReducedString("chklssstt")); // "chkls"

You can check out the complete example here

You can watch this video with the process applied in real time - Audio in Spanish -

If you want a simpler problem, feel free to pick one form the repositories below or follow through this one - in Spanish -

Open repositories with examples

Problems you are going to avoid with PACER

  • Start writing code without really understanding the problem you have to solve.
  • Write a solution to a problem without considering alternative solutions.
  • Waste time writing a solution that does not consider edge cases.
  • Writing invalid or incomplete solutions to algorithmic problems.
  • Writing solutions that return the wrong data type or data structure.

Platforms you can use to practice your programming logic skills

If you have any doubts, send me a direct message via LinkedIn and I will get back to you ASAP.


Big O notation: It’s a way to describe how the runtime of an algorithm grows as its inputs grow. In other words, it is a way to describe how the number of operations to execute an algorithm increases as the input of data to the algorithm increases. Some resources you may want to check out Big O Notation 101, Big O notation calculator

Note: The programming problem used in this blog post to showcase the PACER process was taken from the Edabit

Special thanks to all those who have helped me become a better programmer.