Last Update: July 18, 2017
What makes someone a senior developer? Is it years of programming experience? Is it time spent mentoring less-experienced developers? Is it a knowledge of best practices and the ability to identify code that strays from them? It’s obviously some combination of the above, but it’s also the ability to think like a programmer.
This ability came naturally for me. I excelled at a Pascal course in high school and fell into a leadership role in most of my team assignments in college. Early on, I realized that the biggest struggle some students faced was simply getting into the programming mindset.
After four years of college and over ten years working on real-world applications, I’ve been thinking more about how programmers approach a problem, what makes good code, how to teach people about programming, etc. I’ve been trying to take on a mentor role as my business partner and my wife - two separate people , both designers - learn more about programming.
Recently, I’ve been working through LeetCode and Project Euler exercises with my business partner Tim. This has refreshed my ability to think through algorithms and reminded me of the problems that budding developers face. Being highly-organized, I started thinking about how these various concerns could be arranged. I’m still thinking through all of this, but what I came up with are the first three tiers of a software development hierarchy:
- Thinking like a programmer
- Encoding the algorithm
- Knowing the language
This post takes a deeper look at all three.
Thinking like a programmer
Thinking like a programmer involves reducing a problem to a set of repeatable steps that can be followed to solve the problem. This is the first step in learning to code. It’s also a key component in early computer science courses. Although, looking back, it’s not explicitly stressed as much as it should be - which is why I think many students find the subject frustrating.
Let’s look at a simple LeetCode Exercise. Here’s the description:
Given an array of 2n integers, your task is to group these integers into n pairs of integer, say (a1, b1), (a2, b2), …, (an, bn) which makes sum of min(ai, bi) for all i from 1 to n as large as possible. In simpler terms, split an array of integers into two-element subarrays such that adding the lower value in each subarray gives you the maximum sum.
How would a non-programmer even begin thinking about this problem? What if we change the problem so that the numbers are written on index cards?
Lay out group of index cards with numbers written on them. Then, organize them into groups of two such that adding the lower number in each group gives the maximum sum.
Suppose we have eight cards randomly numbered 1 through 8:
1 7 8 3 2 6 5 4
No matter what we pair it with, the 8 will always be the maximum value in the pair and will never be added to the sum. However, if we pair it with the next highest card, 7, we get to add 7 to our sum.
1 3 2 6 5 4
Now that the 7 and 8 cards are paired, what do we do with the rest of the cards? 6 is the highest remaining card. It will always be the maximum value in any pair. So, we should pair it with the next highest card, 5.
1 2 3 4
A pattern is starting to emerge. We need to keep pairing up the two highest remaining cards until all of our cards are paired up. Now that we know how to solve the problem, let’s code the solution.
Encoding the algorithm
Being able to express an idea with code is the next level of the hierarchy. Let’s do it with Ruby!
Going back to the actual coding exercise, LeetCode is expecting a method named array_pair_sum that accepts the array of numbers and returns the sum. Let’s work through that method.
def array_pair_sum(nums) # We need to code this part! end numbers = [1, 7, 8, 3, 2, 6, 5, 4] array_pair_sum(numbers) #=> This should return the sum
We’ll be adding a bunch of numbers together. So, let’s start with a sum of 0.
sum = 0
In the index card version, we had all of the cards in front of us and could easily pick out the two highest cards. How can we do that in our code? One option is to sort the array first so all of the numbers are in order. Then, the last two numbers will always be the highest. Let’s create a sorted copy of the numbers array.
nums = nums.sort nums #=> [1, 2, 3, 4, 5, 6, 7, 8]
Now, we can split the numbers array into separate arrays of two. We’ll start with an empty array of pairs and use Ruby’s pop method to remove the last two numbers and add them to the pairs array until there are no numbers left.
pairs =  while !nums.empty? pairs << nums.pop(2) end
Finally, we can go through all of the pairs and add the minimum of each pair to the sum.
pairs.each do |pair| sum += pair.min end
Here’s the code in its entirety:
def array_pair_sum(nums) nums = nums.sort sum = 0 pairs =  while !nums.empty? pairs << nums.pop(2) end pairs.each do |pair| sum += pair.min end sum end
A lot of developers, especially junior developers, might stop there. However, a more experienced developer who knows the ins-and-outs of Ruby could refactor this code into something much simpler.
Knowing the language
Having a deep knowledge of your language allows you to refactor your code into cleaner, simpler solutions. Let’s see if we can take advantage of Ruby and simplify the array_pair_sum method.
First, we can use Ruby’s map and reduce methods to calculate the sum. Map runs a block for each element in an array and returns a new array that contains the results. We can use it to turn our array of pairs into an array of the minimum values of each pair. Reduce combines all of the elements of the array into a single value. We can use it to add all of the minimum values together.
def array_pair_sum(nums) nums = nums.sort pairs =  while !nums.empty? pairs << nums.pop(2) end pairs.map(&:min).reduce(:+) end
Ruby’s each_slice method breaks an array into chunks of a given size. We can use that to break the numbers into pairs. It returns an enumerator object, but we can convert it back into an array using the to_a method. This allows us to obtain the array of pairs without using a while loop.
def array_pair_sum(nums) nums = nums.sort pairs = nums.each_slice(2).to_a pairs.map(&:min).reduce(:+) end
Notice that we’re creating the nums array, using it to create the pairs array, and then operating on that. Why not just chain them all together and turn the method into a single line?
def array_pair_sum(nums) nums.sort.each_slice(2).map(&:min).reduce(:+) end
We’re not doing anything magical here. The code still reveals our intentions: sort the numbers, break them into pairs, get the minimum value of each pair, and add them together.
That’s it. We just thought through the problem like a programmer, coded a solution to it, and refactored it using the tools Ruby provides.
I’m already thinking of expanding this hierarchy to include object-oriented (or functional) design, testing, and refactoring, but I’ll have to save that for another post.