I am wrecking my brain trying to solve this python exercise for beginners: Rearrange sorted list in max / min form. I have a confession to make. I don’t know what I’m doing. I sometimes struggle when I am solving coding exercises. But you know what so does everyone else. The sooner I can get over the feeling of inadequacy in my coding knowledge, the sooner I will get better at solving coding problems. Now that I got the elephant out of the room, I can start focusing on the coding exercise by dissecting the requirement for the problem.
In this case, I want to implement a function sort_min_max which will rearrange the elements of a sorted list. The output of the list will be rearranged so that the 0th index will have the max number, the 1st index will have the min number, the 2nd index will have the 2nd max and etc … Let’s visualize what that will look like:
Table: Sample Input and Output
Input (Sorted List) | Output (Sorted list in min / max form) |
[6,7,8,9,10] | [10,6,9,7,8] |
[11,12,13,14,15] | [15,11,14,12,13] |
[16,17,18,19,20] | [20,16,19,17,18] |
There is one observation from the table above that I find interesting here. The min numbers are always located in indexes where the index is an odd number. For example, in the first row of the table. The min number 6 is located at index 1 and the next min number is located at index 3. Whereas the max numbers are always located in indexes where the index is an even number. I can exploit this to my advantage when coding my solution.
As always, there are many ways to tackle a python exercise. Today, I am choosing to tackle this exercise by using two concepts of coding in python. I will use extra space to store the output list and negative indexing to insert the last element from the input list into my output list. Often, for this kind of problem there is usually a way to optimize for space and do it in place. In other words, you can do this by rearranging the original list without creating a new list. However, that will be a topic for another day. Here is what a solution to this exercise could look like:
Input (Sorted List) | Output (Sorted list in min / max form) |
[6,7,8,9,10] | [10,6,9,7,8] |
[11,12,13,14,15] | [15,11,14,12,13] |
[16,17,18,19,20] | [20,16,19,17,18] |
There is one observation from the table above that I find interesting here. The min numbers are always located in indexes where the index is an odd number. For example, in the first row of the table. The min number 6 is located at index 1 and the next min number is located at index 3. Whereas the max numbers are always located in indexes where the index is an even number. I can exploit this to my advantage when coding my solution.
As always, there are many ways to tackle a python exercise. Today, I am choosing to tackle this exercise by using two concepts of coding in python. I will use extra space to store the output list and negative indexing to insert the last element from the input list into my output list. Often, for this kind of problem there is usually a way to optimize for space and do it in place. In other words, you can do this by rearranging the original list without creating a new list. However, that will be a topic for another day. Here is what a solution to this exercise could look like:
def sort_min_max(lst):
output = []
# I only need to go through half of the list to sort everything
for i in range(len(lst)//2):
# The last element from the input list will be the first element in the output
# So, append the corresponding last element from lst into output
output.append(lst[-(i+1)])
# The first element from the input list will be the next element in the output list
# So, get the current element of lst and insert it into output
output.append(lst[i])
if len(lst) % 2 == 1:
# if middle value then append
output.append(lst[len(lst)//2])
return output
print(sort_min_max([6,7,8,9,10])
The concept of negative indexing used to baffle me as a beginner coder in python. When I started coding in Python, I had experience coding in a lot of programming languages already. I have experience with Assembly, Verilog, Java, C, C++, etc. However, I had never come across negative indexing in coding experience before Python. It took me a while to get comfortable with using negative indexing. Now, I take it for granted and I find it intuitive when I need to access elements from the end of a list rather than the beginning.
This experience of encountering something outside my domain of knowledge happens quite often in programming. I always need to remind myself that it’s okay to be uncertain and it’s okay to fail from time to time. One caveat to this is failure is only ok if I’m willing to learn and improve from the failure. Uncertainty and failure are doors of opportunity to learn something new. There’s no point in beating myself up over my failures. The sooner I can get comfortable with uncertainty and failure, the sooner I can open those doors of learning opportunity. Ultimately, the progress I make from encountering uncertainty and failure are what pushes me to become a better software engineer. The constant learning is one of the things that attracts me to programming.
An easy way to verify that my code solution correctly is to simply check that the output is correct. I can do this by comparing the output from my function equals the output I expect to get as described in the table of sample input and output. So, a simple test case could look like below:
def test_sort_min_max():
input_list1 = [6,7,8,9,10]
input_list2 = [11,12,13,14,15]
input_list3 = [16,17,18,19,20]
function_output1 = sort_min_max(input_list1)
function_output2 = sort_min_max(input_list2)
function_output3 = sort_min_max(input_list3)
assert function_output1 == [10,6,9,7,8]
assert function_output2 == [15,11,14,12,13]
assert function_output3 == [20,16,19,17,18]
If I am writing this to be used in a production environment, I could test against several more input and output samples to ensure that I catch as many bugs as I possibly can. Some other test cases I can think of are: empty list, a list containing a large number of elements in it, a list containing negative numbers, a list containing duplicate numbers etc. Robust testing is especially important if I am making code that will be relied on by other people in a real-world scenario. However, this will be good enough for today to show how I can check that my code is functionally correct.
I’ll end this post with an analogy that I heard from Thomas Frank, a productivity advocate on https://thomasjfrank.com/. In his words, “The analogy I like to use is of a gold miner. If you’re somebody who mines for gold for a living, then you’re not always going to strike gold whenever you dig somewhere. Hopefully, you’ve got some crazy technology to figure out if there’s probably gold in the ground. But either way, you have to dig through a bunch of dirt and rock to potentially find gold. That process of digging through nothing of value is required to get you to the point where you’re ever going to find gold if you are going to find it.”
Digging through dirt of uncertainty and failures so that I can potentially find gold is what I use to remind myself to be comfortable with not knowing everything. Often, I find that it is way more satisfying when I am able to successfully write code on something that I was not familiar with before than when I am simply coding something I am familiar with.