HackerRank: Text Alignment Notes
Background
This article is about a Python challenge on HackerRank: Text Alignment. We can definitely keep trying until we get the correct answer for this one. But I think that is kind of boring, and I really want to understand the thoughts behind the author's code. So this article will try to think like the author and come up with the necessary steps one by one.
Problem
The problem requires you to input an odd number named thickness where \( 0 \lt thickness \lt 50\). For example, when thickness = 5, we should output an ASCII art like this:
H
HHH
HHHHH
HHHHHHH
HHHHHHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHHHHHH
HHHHHHH
HHHHH
HHH
H
The problem also provides you with a code template:
#Replace all ______ with rjust, ljust or center.
thickness = int(input()) #This must be an odd number
c = 'H'
#Top Cone
for i in range(thickness):
print((c*i).______(thickness-1)+c+(c*i).______(thickness-1))
#Top Pillars
for i in range(thickness+1):
print((c*thickness).______(thickness*2)+(c*thickness).______(thickness*6))
#Middle Belt
for i in range((thickness+1)//2):
print((c*thickness*5).______(thickness*6))
#Bottom Pillars
for i in range(thickness+1):
print((c*thickness).______(thickness*2)+(c*thickness).______(thickness*6))
#Bottom Cone
for i in range(thickness):
print(((c*(thickness-i-1)).______(thickness)+c+(c*(thickness-i-1)).______(thickness)).______(thickness*6))
It asks you to replace the "____" with ljust, center, or rjust.
How to use ljust(), center(), rjust()
ljust()
and rjust()
are actually very simple. They are just aligning your text left or right. But center() is a bit tricky. There are several things you need to be careful about.
ljust() and rjust()
Let's take a look at the ljust()
and rjust()
first:
width = 10
print("12345".ljust(width,'-'))
print("12345".rjust(width,'-'))
Output:
12345-----
-----12345
As we can see, "12345" is the text that I want to print. ljust()
is aligning the text left, and the "width" here decides the total length. "-" is the character I set as the fillchar, which will be used to reach the given width.
rjust()
is also similar. If we did not pass fillchar to the second argument here, the default fillchar would be space.
center()
It is a bit tricky when it comes to center()
. We need to remember two rules:
- If the given width is an even number, the padding will start from the right.
- If the given width is an odd number, the padding will start from the left.
For example, if we want to center the "12345" and set the given width as 6, then:
s = "12345"
width = 6
print(s.center(width,'-'))
# Output:12345-
However, if we want to center "1234" and set the given width as 5, then:
s = "1234"
width = 5
print(s.center(width,'-'))
#输出:-1234
There is also another situation. If "width - len(s)" is an odd number, it means that the padding for both sides will not have an equal number of characters. The side that is padding first will have one more character. We will need to check whether the given width is odd or even.
For example, if s
= "1234", width
= 7 and the method is center()
, by calculating \(7- 4 = 3\), we know that the padding should be "-" for one side, and "--" for the other side. As width
is an odd number, the padding will start from the left:
width = 7
print("1234".center(width,'-'))
# Output:--1234-
Similarly, if s
= "12345", width
= 8, and the method is still center()
, the right side will have one more "-" because the given width is even.
width = 8
print("12345".center(width,'-'))
#输出:-12345--
How to solve the problem (rebuild the author's logic)
After we understand the three methods above, we can start to discuss the author's logic in the code template. I will try to think like the author and come up with the necessary parts step by step.
The following will discuss the situation where \(thickness = 5 \).
Top Cone
First, we can look at the Top Cone part, which is the top-left triangle in the big "H":
H
HHH
HHHHH
HHHHHHH
HHHHHHHHH
for i in range(thickness):
print((c*i).______(thickness-1)+c+(c*i).______(thickness-1))
It is easy to see that thickness decides how many lines the cone will have. For this type of triangle, the general pattern for the number of characters in each line is 1, 3, 5, 7...
The logic here is to print the middle "H" first and then pad even number of "H"s on both sides. Because it has 5 lines and we know the 5th line will be full of characters, the number of "H"s in the 5th line ranging from the start to the middle will be 5.
Therefore, every line should be aligned with width
= \( (thickness -1)\) for both sides in each line (4 in this case).
For the "H"s on the left side, we need to use rjust()
, and for the "H"s on the right side, we need to use ljust()
.
Therefore, the code will be:
for i in range(thickness):
print((c*i).rjust(thickness-1)+c+(c*i).ljust(thickness-1))
Top Pillars, Middle Belt, and Bottom Pillars
Then let's take a look at the middle part. I also keep the last line of the top cone for reference.
HHHHHHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHH HHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
We should try to think like the author before we look at the code template. The first thing to decide is the middle belt should span \(5 \times thickness\):
HHHHHHHHHHHHHHHHHHHHHHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
HHHHHHHHHHHHHHHHHHHHHHHHH
After this, there are two limited conditions here:
- Two pillars must be on two sides of the middle belt.
- The first pillar needs to be centered on the bottom line of the cone.
For ease of reading, we let \(t = thickness \).
The structure will look like this after simplification:
(The \(3t\) here is obtained by \((5t-t\times2)\).)
If we also want to use center()
for the right pillar, we need to consider the same size of blue parts on the right side:
Therefore, we know the code will be:
#Top Pillars
for i in range(thickness + 1):
print((c*thickness).center(thickness*2-1)+(c*thickness).center(thickness*6+1))
If you change the "Top Pillars" and "Bottom Pillars" to my code here, you will find that it can also pass the test.
However, we observed that the left pillar uses \( 2t-1 \) while the right pillar uses \( 6t+1 \), and they are next to each other. Is it possible that we can change them to \( 2t \) and \( 6t \)?
The yellow part is the difference, but it creates a new problem. The two sides of the pillars have different widths:
We can see that both pillars' right side has one more character than the left. This involves a point about the center() we mentioned earlier:
- If the given width is an even number, the padding will start from the right.
Because .center(2t)
and .center(6t)
are receiving two even number parameters, the padding will start from the right.
This will solve our problem automatically. Since for the left pillar, the one more right-padding character will fill the yellow difference, and for the right pillar, the one more right-padding character will not affect the output since it is a space.
Thus you can understand why the code template will be:
#Top Pillars
for i in range(thickness+1):
print((c*thickness).center(thickness*2)+(c*thickness).center(thickness*6))
#Bottom Pillars
for i in range(thickness+1):
print((c*thickness).center(thickness*2)+(c*thickness).center(thickness*6))
After we figure out the situations for the two pillars, it is clearer to see why the middle belt was written like that.
As you see here, we want the middle belt to stay in the 5t
range, which is to be centered within \( 5t+2\times \frac{(t-1)}{2} = 6t-1 \) (i.e. centered(6t-1)). Since the same rule applies, the right side will have one more character if we change to center(6t)
. It does not affect our ASCII art, so we change it to 6t
.
#Middle Belt
for i in range((thickness+1)//2):
print((c*thickness*5).center(thickness*6))
The result will look like this:
Bottom Cone
Finally, let's look at the bottom cone. In fact, the situation is very similar to the top cone. We also divide the output into three parts: left "H"s, middle "H," and right "H"s.
The left and right number of "H"s can be considered as \( ((thickness - 1) - i)\). When \( thickness = 5\), every iteration will be \( ((thickness - 1) - i) = 4,3,2,1,0 \).
We can still use the same alignment with width
= \((thickness - 1)\) as the top cone's:
for i in range(thickness):
print(((c*(thickness-i-1)).rjust(thickness-1)+c+(c*(thickness-i-1)).ljust(thickness-1)))
Output:
HHHHHHHHH
HHHHHHH
HHHHH
HHH
H
The only problem left is how to right align it. According to the picture above, we should use 6t-1
for the right()
method's width.
The code will be:
#Bottom Cone
for i in range(thickness):
print(((c*(thickness-i-1)).rjust(thickness-1)+c+(c*(thickness-i-1)).ljust(thickness-1)).rjust(thickness*6-1))
You can also pass the test by using this code, while you will notice that this is not the same as the author's code template.
The author's way is like this:
#Bottom Cone
for i in range(thickness):
print(((c*(thickness-i-1)).rjust(thickness)+c+(c*(thickness-i-1)).ljust(thickness)).rjust(thickness*6))
There are two differences, one is that for the left and right "H"s, the ljust()
and rjust()
are using thickness
as the width instead of thickness-1
in my code. The other difference is that the rjust()
for the whole triangle is using 6t
.
The author is actually trying to extend one more space at the right. This will align with the rightmost green space so that the center()
can use 6t
as the width.
Conclusion
This article is my guess for the author's logic based on the code template. I tried to simulate how the author came up with the code step by step. Although it may not be accurate, I think the explanation for the alignment logic should be correct.
It took me some time to figure out the logic and draw these pictures, but it was worth it after I finished it. I feel that the logic is much clearer for me, and I have a deeper understanding of this type of code.
I hope this article helps you have similar feelings and understand more about the author's code!