JavaScript 列印星號三角形

icyhat發表於2018-11-24

列印由星號組成的x層三角形,如下圖:

JavaScript 列印星號三角形

看規律, 設三角層數為x,每層星星數為y,每層開頭空格數為z

那麼,對於第i層:

  1. y 為1,3,5,7,9...奇數。光知道奇數你並不知道印象中的公式是2n-1還是2n+1更哪個更適合這裡, 那我們一步步來, 這裡隔離變與不變, 1是最開始的基準, 3和1的關係是+2, 5和3的關係是+2... 因此每次加2,即1, 1+2, 1+2+2, 1+2+2+2...注意這裡隔離變與不變, 數量不變的1, 和數量變化的2, 那麼就看這個變化的2的數量和i的關係, 即0, 1, 2, 3...和1, 2, 3...的關係,這樣才由題意得y = 1+(i-1)*2 = 2i-1;
  2. z 在最後一層為0,朝上依次增加1,共有x層,故最上層為x-1,第二層x-2, 第三層x-3, 因此z = x-i;

雖然很多時候這兩個簡單的變數之間的數量關係可以一眼看出, 但通常都是碰運氣的不斷嘗試拼湊, 本著九年義務教育提供的數學素養, 我們應該可以從頭推出這些式子, 並且有理有據, 而不是背下來.

個人最討厭背了,除了3.1415926, 九九乘法表之類的, 爸媽的手機號... 其他時候是能不背就不背.

我們如果能掌握如何產生公式, 是不是就比直接背公式更容易記憶呢?短期看背的收益是高回報的, 無需思考, 提取就可以. 而理解就像計算機中的函式, 他們多了之後可以互相利用, 組合成更復雜的功能. 而背除了詩詞能假以時日身臨其境,有所感慨可以陶冶情操外, 很多時候他們真的除了提取資料, 毫無發展和其他有趣的事情聯絡起來從而有更大的用處.

當然理解是要付出代價的, 即理解的時間, 和從原資料推演計算的時間, 因此重要的或者有趣的才要去理解, 如果是一次性使用的資訊, 比如學校旁的小麵館說13號擔擔麵好了~13號在哪~, 這樣的資訊還是直接背下來比較好, 當然你也可以想象背叛上帝的猶大, 藉此把13記下來

而手機驗證碼的六位, 原先還要不斷重複口唸450893, 生怕有人跟我說話我又要切換應用重新記, 哈利路亞, 這種純粹沒有邏輯的死板的資訊, 現在的輸入法自帶的資訊提示, 不用切換到簡訊, 直接點一下就Ok了.

看, 越是死的東西, 越是會被機器+軟體替換掉.

簡易程式碼實現

const printTriangle = x => {
    for(let i=1; i<=x; i++) {
        console.log(" ".repeat(x-i)+ "*".repeat(2*i-1))
    }
}
printTriangle(7)
複製程式碼

效果:

VM189:3       *
VM189:3      ***
VM189:3     *****
VM189:3    *******
VM189:3   *********
VM189:3  ***********
VM189:3 *************
複製程式碼

遐想

程式碼邏輯很簡單,這裡有一點引起我的遐想。

*.repeat()為例,如果

  1. 需要1個*,是我們程式設計師手動輸入的,無法精簡
  2. 需要2個*,可能我們更願意手動輸入,也無需精簡
  3. 需要3個*,可能我們會想相關重複字元的API是不是比三個星星簡單,於是想到str.repeat(), 但很明顯,還是手動輸入***更簡單
  4. 需要4,5,6,7個*呢?很明顯,這時候我們理性一點,在這種事上不能夠選擇相信我們的眼睛真的每次都能數對數目,儘管小學算術經常拿滿分 :)
  5. 而如果我們不是一次只需要幾個*,而是同時需要1個,2個,3個,4個*呢?這時候我們會選擇自動生成。即有str.repeat()那就用它,沒有的話呢?

手動實現str.repeat()

第一步,先保證基本能用最簡單的

const repeat = (str,n) => {
    let result = "";
    for(let i=0; i<n; i++) {
        result = result + str
    }
    return result
}
複製程式碼

複雜度:O(n)

第二步: 優化

上面的實現間接生成了任意長度的*字串

  1. *, 手動輸入
  2. **, 由* + *
  3. ***, 由** + *
  4. ****, 由*** + *
  5. *****, 由**** + *
  6. ******, 由***** + *
  7. ...

而對於本題目需要奇數個長度的*, 這裡生成的一半都是不需要的, 造成了浪費

那麼怎麼才能不浪費呢.

*, **, ****, ********, 每次以之前的字元為基本, 自我複製,達到2倍法來逼近指定長度, 達到log2(n)的複雜度(想一想二分法...)

但是對於本題而言, *, **, ****, ********, ****************是相對不夠的, 所以

  1. 對於每行的*, 從第一層的*開始, 不斷的在上一層字串 + **,
  2. 而對於每行的空格" ", 一開始就有x-1個, 所以要最大速度的增加到x-1個字串, 用2倍法, O(log2(n))複雜度, 2^40 = 10995,1162,7776, 即長度為一萬億以下的字串的重複, 不超過40次計算即可拼接成.
const printTriangle = x => {
    let blanks_max_length = x-1
    let blanks_max = ""  // empty str
    let blank = " "  // blank str
    for(;blanks_max_length!=0;){ // only stop while length became 0, 
                                // there is no (let i i...i++), because it's all about length! 
        if(blanks_max_length%2===1) {    // remember decimal 5 trans into binary 101?
                                        // 5/2 = 2...1, 2/2 = 1...0, 1/2 = 0...1, 
                                        // the first remainder 1 indicates 1 while the  last 1 indicate 2^2 which is 4
            blanks_max = blanks_max + blank  // so here if there remains 1,  blanks_max should add 1 blank
        }
        blanks_max_length = Math.floor(blanks_max_length/2)  // here is the result of /2
        blank = blank + blank   // here is where log2(n) come from, cause we double the string rather than linear increase

    }
    let star = "*"
    const addon = "**"
    // first line
    console.log(blanks_max + star)
    // other lines
    for(let i=2; i<x; i++) {
        star = star + addon
        console.log(blanks_max.slice(0, x-i) + star)
    }
    // last line
    console.log(star+addon)
}

複製程式碼
  1. 對比結果時, 我發現第一行和其他行的格式縮排不一致, 找了半天程式碼, 我預設其他是對的, 那麼一對比結果可能正確的那個就表現的錯誤, 而我怎麼也找不到這錯在哪, 這個時候要聯絡上下文, 我發現其實這只是因為兩個邏輯, 第一個邏輯產生了1個例項即第一行, 第二個邏輯產生了更多的例項即其他行, 當錯誤的那個邏輯應用在很多行, 導致其他行一致性的表現規律, 以至於你認為其他行都格式正確了, 第一行是錯誤的, 其實這不是多對一, 本質上這是一對一, 如果不是這個邏輯的這一行錯了, 就是那個邏輯的很多行錯了!
  2. 為什麼會這麼用除2判斷餘數來標識32,16,8421碼的位是否存在的方式來組合總空格的長度以及用空格double的方式實現複雜度O(log2(n)), 首先考慮的是先生成第一層最長的空格, 然後依次遞減. 其實這是個str.repeat()從O(n)到O(log2(n))的方法,被強行套在這裡.
  3. 關於本題實際上更適合生成空字串逐個加1長度, 存入棧, 然後列印的時候出棧, 滿足倒序列印的特點

程式碼如下

const printTriangle = x => {
    let blanks_max_length = x-1
    let blanks_arr = []
    let blank_char = " "
    let tmp =""
    for (let i=0; i<blanks_max_length; i++) {
        tmp = tmp + blank_char
        blanks_arr.push(tmp)
    }
    let star = "*"
    const addon = "**"
    // first line
    console.log(blanks_arr.pop() + star)
    // other lines
    for(let i=2; i<x; i++) {
        star = star + addon
        console.log(blanks_arr.pop() + star)
    }
    // last line
    console.log(star+addon)
}
複製程式碼

噢, 醜陋的程式碼, 就是為了不產生浪費的字串.

好了, 上面給出了一些個人答案,這些答案分別面向節省程式設計師時間(str.repeat)、面向自己實現str.repeat(),面向不浪費生成的物件等角度。

最後還是好愛第一個答案,不禁讓我想到一句話

JavaScript是世界上最好的語言~

相關文章