How to Think Like a Programmer
Before delving into the world of algorithmic thinking, let’s take a moment to demystify two terms we’ll be using constantly - “algorithmic” and “thinking”.
Firstly, what does “algorithmic” even mean? It’s intricately tied to algorithms, but what exactly are algorithms? The term “algorithm” might conjure thoughts of algebra, but interestingly, it finds its roots in the name of the influential polymath, Al-Khwarizmi, also known as the father of algebra. It amazes me how so many terms often bear little association with their meanings, and one might ponder if we can have an algorithm to craft a language that effortlessly assigns terms to entities that always represent their meaning. Of course, scientists who happily named works by their own names won’t be delighted. But that’s a topic for another day.
For now, let’s focus on understanding algorithms and their connection to thinking. Thinking, on the other hand, is a more familiar term. We engage in it constantly, contemplating various aspects, recalling memories, forming associations, and deducing conclusions - all within our minds.
(It’s intriguing to note that our thoughts are so linked to language and words. We often rely on language even in our thoughts, raising a curious question: How did we think before the advent of proper language? Perhaps again a question for another day.)
An algorithm is a systematic process for accomplishing a specific task. Therefore, algorithmic thinking involves a step-by-step approach to solving a well-defined problem. It’s like a mental roadmap guiding us toward a defined goal. Sure, you can entertain thoughts of riding unicorns on fluffy clouds, but that doesn’t qualify as algorithmic thinking. Instead, imagine expressing your desire to ride unicorns on clouds and then systematically thinking through the steps to achieve it. How do you acquire a unicorn? Could it be a unicorn-shaped machine, perhaps an airplane? If so, how does that contraption work, and what steps lead to reaching cloud-level altitudes? and so on… thinking further on these smaller tasks. There you have algorithmic thinking.
Now that we’ve scratched the surface of what algorithmic thinking means, let’s dive into the problems and questions that we try to tackle through this type of thought process. The essence lies in step-by-step thinking towards a defined goal, something our brains often do naturally when confronted with a problem. The idea of making a sequence of decisions to accomplish a task is not foreign to us. However, the key is to explore and understand the nature of this thinking more deeply. As we delve into the intricacies of algorithmic thinking, we discover a vast scope for tinkering and optimizing this inherent ability for better results and solutions.
So, exactly what kind of problems benefit from algorithmic thinking? Answering this question isn’t straightforward and often puts us in a make-or-break situation while making sense of something through algorithmic thinking. Therefore, it’s crucial to invest time in contemplating the problems we aim to solve and their interconnectedness. Rather than randomly tackling generic problems like sorting or searching without understanding why they are interesting in the first place, let’s begin with a common problem encountered daily, even by animals - finding food. Imagine a lion in the jungle employing algorithmic thinking. It observes different prey and identifies patterns to discern which ones are feasible to catch and eat. It can also consider factors like the age of animals, their locations, and how they gather in certain areas, like near water bodies or foliage, and hence decide where, who and when to hunt accordingly. Animals operate on instinct and emotion, which prompts problem-solving and pattern recognition. Humans take it a step further by identifying intricate patterns, quantifying and comparing them for better understanding. With the integration of AI, humans can enhance these processes and take better advantage of patterns in our messy world.
The algorithmic thinking process involves recognizing types of patterns, such as similarity in shape, size, or arrangement, be it in space, in time, or in a logical sequence, and using these inferences to our advantage in solving problems in a systematic manner. Sorting, for instance, is a simpler form of pattern recognition where we look at a property to compare multiple objects while the problem of searching resembles exact pattern matching with the object being sought. Expectedly, sorting aids in searching because it simplifies the detection of properties belonging to the object being sought. Our journey of algorithmic thinking involves identifying useful patterns in the problem context, implementing solutions by taking advantage of these patterns, learning from mistakes, updating strategies, and continually iterating toward more effective outcomes.
But before we can solve a problem, we must define it - and that step is far more subtle than it sounds.
Defining a problem is a crucial transformation from the messy and complex real world into a comprehensible, qualitative and quantitative framework that can be effectively communicated. It involves minimizing vagueness while highlighting crucial aspects that impact the desired outcome.
While defining a problem, it’s essential to recognize that every conceivable aspect may not need to be included. Instead, the emphasis should be on understanding the aspects that significantly influence the final result. This involves choosing inputs judiciously, distinguishing between exact values or fixed notions and variable inputs. A well-defined problem strikes a balance between specificity and vagueness, ensuring it remains meaningful. For instance, the problem of “Planning a vacation” might be too vague, while “Planning a vacation in Paris with a specific itinerary” is overly specific. A refined problem could be “Planning a vacation in X, considering a preference for visiting Y places,” where X represents the location and Y denotes places taken as input from the user.
Before delving into more complex problems, let’s examine a simpler one to illustrate the major challenges that can arise even in seemingly straightforward tasks. Imagine the task of sending a birthday card to a friend. Defining constants (friend’s name, date of birth) and variables (budget for the card), the problem becomes “Buying a birthday card for Joe on 1st Jan under budget X.” We go to an online store, search for birthday cards, and sort by price. However, challenges emerge when the lowest card price exceeds X. Rethinking the problem reveals a solution: handmaking the card, which is more cost-effective. This process underscores the importance of revisiting and refining problem definitions. Asking related questions like “If I tweak something in the problem, does it become totally unacceptable to me?” helps uncover tolerances for different versions of the problem, which might be much easier to solve. The refined problem becomes “Having a birthday card for Joe on 1st Jan under budget X.”
Moving on to the card-making process, some materials can arrive earlier than others, and understanding causal relations between steps becomes crucial. Identifying actions that can be done independently and those dependent on previous steps is essential. Drawing hearts on the card is not dependent on having glue, but sticking sequins to the card is. Even within the tasks, we might unveil that it might be more efficient to complete all the drawings using the same color marker together than changing colors after every step. Finally, in a moment of serendipity, you might remember having materials or embellishments left from a previous project, or you find a solution of buying a cheaper card that can be repurposed for a birthday, which can save you some time.
Concepts like parallel and sequential processes, efficiency considerations within steps, and recognizing potential improvisations contribute to a rich problem-solving experience. This seemingly simple problem encompassed various concepts such as sorting, searching, parallel processing, graphical relations, weighted graphs, memory, divide and conquer, and identifying similar problems. It illustrates how algorithmic thinking is inherent even in seemingly straightforward tasks - and I’m sure this example can be easily extended even further to encompass more concepts.
Read Next: