HackerRank: Text Alignment 笔记和题解

HackerRank Text Alignment 笔记和题解,以及我对原题目代码的思路解释,图片皆为原创。

然

Table of Contents

简介

这篇笔记是我在做到 HackerRank 的 Text Alignment 时写下的。这道题当然可以直接试出答案,但我觉得那样没啥意思,希望搞清楚这个代码的逻辑是咋样的。

题目

题目的输入只有一个奇数,称为 thickness,0 < thickness < 50。比如 thickness = 5时,你应该输出一张这样的用 ‘H’ 组成的文字图片(ASCII art):

    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 

题目有给你一个代码模板:

#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))

题目也提醒你要将 "______" 部分给替换成 ljust(),center(),还有 rjust()。

ljust(),center(),rjust() 用法

ljust 和 rjust 的用法很简单,就是将你的文字居左或者居右。不过 center 就比较 tricky,有几个点是我们需要去注意的。

ljust() 和 rjust()

我们先看 ljust()rjust()

width = 10

print("12345".ljust(width,'-'))

print("12345".rjust(width,'-'))

输出:

12345-----
-----12345

可以看到,"12345"是我要打出来的文字,用 ljust() 就是居左,width 决定了文字的总长度,ljust() 的第二个 argument 决定了你剩下的位置用什么来补齐,第二个 argument 只可以输入一个 character。rjust() 也是类似的道理,如果第二个 argument 不填的话,默认是补空格。

center()

center() 的情况稍微有点特殊,首先记住两个规则:

  • 如果定义的 width 是偶数,则 padding 会从右边开始。
  • 如果定义的 width 是奇数,则 padding 会从左边开始。

比如说,我们要让 “12345” center,但假设我们设置 width 为 6,则:

s = "12345"
width = 6

print(s.center(width,'-'))

#输出:12345-

而如果我们要让 “1234” center,且设置 width 为 5,则:

s = "1234"
width = 5

print(s.center(width,'-'))

#输出:-1234

还有一种情况就是,如果 width – len(s) 是一个奇数的话,意味着两边所补的字符不是同等数量,这个时候就是看设定的 width 是奇数还是偶数,先 padding 的那边会多出一个。

举个例子,假如 s = "1234",width = 7,我们知道 7 – 4 = 3,也就是居中后,应该一边是 "-",一边是 "- -",而此时 width 是奇数,则左边会是 "- -",右边是 "-",因为 padding 会从左边开始:

width = 7

print("1234".center(width,'-'))

#输出:--1234-

解题思路

Top Cone

搞懂了三个 methods 怎么用之后,我们来讨论一下解题思路,这里我们讨论 thickness = 5 的情况。

首先看代码模板中的 "Top Cone" 部分,也就是指大 "H" 中的左上箭头:

    H    
   HHH   
  HHHHH  
 HHHHHHH 
HHHHHHHHH
for i in range(thickness):
	print((c*i).______(thickness-1)+c+(c*i).______(thickness-1))

可以看到 thickness 决定了它有多少行,而这种三角形一般就是,每一行个数递增为:1,3,5,7…。

而这个代码模板的打印方式,是将中间的 "H" 单独打印,然后在左右两边补上偶数个 "H"。而因为有5行,我们也知道第5行肯定是满的,那么第5行从开始到中间的 "H" 就一定是5个,因此推出每行的左右两边要补 (thickness – 1)个位置,也就是4个。

那么对于左边的"H"就是 rjust,而右边的"H"就是ljust。

因此代码就会是:

for i in range(thickness):
	print((c*i).rjust(thickness-1)+c+(c*i).ljust(thickness-1))

Top Pillars, Middle Belt 和 Bottom Pillars

接下来我们来看中间的部分,两根柱子和中间的横线,这里我顺便保留上边三角形的最后一行:

HHHHHHHHH
  HHHHH               HHHHH             
  HHHHH               HHHHH             
  HHHHH               HHHHH             
  HHHHH               HHHHH             
  HHHHH               HHHHH             
  HHHHH               HHHHH             
  HHHHHHHHHHHHHHHHHHHHHHHHH   
  HHHHHHHHHHHHHHHHHHHHHHHHH   
  HHHHHHHHHHHHHHHHHHHHHHHHH

我们先试着复原作者的思路,再来看代码。作者这里首先决定的,应该是中间的横线要横跨 5*thickness:

  HHHHHHHHHHHHHHHHHHHHHHHHH   
  HHHHHHHHHHHHHHHHHHHHHHHHH   
  HHHHHHHHHHHHHHHHHHHHHHHHH

在决定了这点之后,有两个限制条件:

  • 两根柱子要分别在横线的两边
  • 第一根柱子要在左上三角形的底部居中

为了方便,以下我们简称 thickness 为 t。

稍微简化一下后,我们可以将其看作是这样的:

(这里的 3t 是通过 (5t – t*2) 得到的)

而如果我们想要右边那根柱子也居中,我们就要将蓝色的部分乘以2,变成这样:

所以我们知道代码上应该是:

#Top Pillars
for i in range(thickness + 1):
	print((c*thickness).center(thickness*2-1)+(c*thickness).center(thickness*6+1))

如果你将 Top Pillars 和 Bottom Pillars 的代码改成这样,你会发现也是过得了的。

但是此时我们观察到一件事情,一个是 2t-1,一个是 6t+1,而他们也刚好是挨在一起的,我们是不是可以将他们改成 2t 和 6t 呢?

黄色部分就是改变的那一格,但这就涉及到了一个问题,现在两个柱子的左右两边都是不对称的:

两个情况都是右边比左边多一块,这就涉及到了我们前面提到的一个知识:

  • 如果定义的 width 是偶数,则 padding 会从右边开始。

所以 , center(2t)center(6t) 因为两个传入的参数都是偶数,那么 padding 从右边开始,会自然而然地多一个,也就完美符合我们要的效果。

因为对于左边的柱子来讲,右边多出的那一个刚好填了变成 2t 的那一格。而对于右边的柱子来讲,因为我们 pad 的是空格,右边多 pad 一个并不会产生影响。

因此你就可以看到它给出的代码模板为:

#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))

完全理解后,我们再看横线的部分就很清楚了:

我们想要横线的部分,也就是 5t 的部分,在这整个范围内居中,也就是 (5t+2*(t-1)/2) = 6t-1,也就是 .center(6t-1),但跟上面同样的道理,如果我们改成 .center(6t),会从右边开始padding,也就是右边会多一个空格,这并不影响我们整体的排版,所以就改成 6t,也与其它部分更加一致。

#Middle Belt
for i in range((thickness+1)//2):
	print((c*thickness*5).center(thickness*6))

出来的效果就是这样:

Bottom Cone

最后我们再来看 Bottom Cone,也就是底部的三角形。其实这里跟 Top Cone 的情况很相似,同样是左边的 H 居右,加中间的一个 H,然后再将右边的 H 居左。每行放在左右的 H 数量可以看作是 ((thickness – 1)-i),当 thickness 为 5 时,每个循环就是 ((thickness – 1)-i) = 4, 3, 2, 1, 0。

rjust 和 ljust 我们仍可以采用 Top Cone 的想法,用 thickness – 1:

for i in range(thickness):
	print(((c*(thickness-i-1)).rjust(thickness-1)+c+(c*(thickness-i-1)).ljust(thickness-1)))

打印出来就是:

HHHHHHHHH 
 HHHHHHH  
  HHHHH   
   HHH    
    H

但我们还要考虑一个将其居右的问题,而根据我们上边的这张图,要居右的时候应该是 6t-1:

#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))

用这段代码也是可以通过测试的,但你会注意到这并不是作者所给的代码模板。

作者的代码模板是这样的:

#Bottom Cone
for i in range(thickness):
	print(((c*(thickness-i-1)).rjust(thickness)+c+(c*(thickness-i-1)).ljust(thickness)).rjust(thickness*6))

可以看到有两个区别,一个是左右的 H 的 ljust 和 rjust 都采用了 width 为 thickness,而不是我上面的 thickness – 1。另一个区别是整个三角形的 rjust 采用了 width 为 6t。

其实作者的想法也很明显,就是把 2t – 1 那一行的左右两侧多撑出来一个空格,把原本绿色的那块抵掉,这样就可以使用 6t 为 width。

总结

最后,这篇文章也只是我根据作者的代码模板猜想的作者思路,但至少对于输出的排版逻辑应该还是准确的。

搞清楚这段代码的逻辑和画这些图出来其实花了我不少时间,不过最后还是觉得很值得吧,这样理清了很多思路上的问题。

希望我这篇讲解能对你有所帮助!

HackerRankPython简单难度算法

Comments