[TDD磕算法] 排排隊,吃果果(一)失敗嘗試

本文是[TDD磕算法] 我為什么嘗試用TDD解算法題系列的一篇。

題目

原諒我標(biāo)題黨了,其實是這次解的題目很難一言兩語概括出來。這里我嘗試解釋解釋。

假定一隊人排成一列
每個人向前看,如果看到前面有1個比自己高、或者和自己同樣高的人。就舉個牌子報數(shù)1。同理如果有3個,就報3。
如果把每個人的身高和報數(shù)列出來,就形成一個二元組的隊列,比如:
[(172, 0), (160, 1), (182, 0), (170, 2)]
就表示第一個人172cm,前面沒有高于或等于他高度的人;第二個160cm,前面有一個;……

這道題目是,給定打亂順序的一個序列,把他們排回應(yīng)有的順序。
比如:
輸入:[ (7,2), (4,3), (8,0), (7,1), (6,0)]
輸出:[ (6,0), (8,0), (7,1), (4,3), (7,2)]

這道題可以在刷題網(wǎng)站Leetcode上看到,編號406
https://leetcode.com/problems/queue-reconstruction-by-height

初步分析

初看起來題目非常適合TDD解決。排序的邏輯似乎有點復(fù)雜,但是應(yīng)該比較容易一步步從簡單到復(fù)雜演進(jìn)。比如先從最簡單的情況開始,隊列里只有一個人,可以直接返回。兩個人的話有兩種情況……
另外這道題目不禁讓我想起了Combined Number這道Kata。同樣也是實現(xiàn)排序,排序的邏輯有些繞。最終可以很漂亮的把問題的幾個方面分離開解決掉。

關(guān)于Combined Number,可以在這里看到更詳細(xì)的練習(xí)和討論。
http://cyber-dojo.org/kata/edit/FCDDF1?avatar=zebra
https://groups.google.com/forum/#!topic/agileshanghai/d6cy6tCXHA8

失敗嘗試

大致想想之后就開始信心滿滿的TDD起來了,果不其然失敗了……
全過程見 http://cyber-dojo.org/review/show/8922CE128B?avatar=ray&was_tag=1&now_tag=1
大體歷程如下

第一個測試

只有一個人的話,必然前面沒有更高的人。

reconstruct_should_be([[3,0]], [[3,0]]);

reconstruct_should_be的第一個參數(shù)是輸入,第二個是預(yù)期的輸出。

實現(xiàn)代碼,輸入什么就返回什么。

function reconstrutQueue(people) {
  return people;
}
第二個測試

兩個人,有一個前面有更高的人,另一個沒有。

reconstruct_should_be([[2,1],[3,0]], [[3,0],[2,1]]);

實現(xiàn)代碼,按前面人數(shù)排序。

function reconstructQueue(people) {
  return people.sort( (p1, p2) => p1[1] -p2[1]);
}

稍作重構(gòu),抽取常量

return people.sort( (p1, p2) => p1[PRE] -p2[PRE]);
第三個測試

兩個人,前面都沒有更高的人。也就是說是低的在前。

reconstruct_should_be([[2,0],[3,0]], [[2,0],[3,0]]);

直接通過了。

第四個測試

三個人,從低到高排。

reconstruct_should_be([[2,0],[3,0],[1,0]], [[1,0],[2,0],[3,0]]);

實現(xiàn)代碼,如果前面人數(shù)一樣,按高度排序。

  return people.sort( (p1, p2) => {
    if (p1[PRE] === p2[PRE]) return p1[0] - p2[0]
    return p1[PRE] -p2[PRE]
  });
第五個測試

三個人,中等高度的排在最高的后面。

reconstruct_should_be([[2,1],[3,0],[1,0]], [[1,0],[3,0],[2,1]]);

又一次直接通過了。

第六個測試

三個人,最矮的排在中間。

reconstruct_should_be([[2,0],[3,0],[1,1]], [[2,0],[1,1],[3,0]]);

實現(xiàn)代碼,排序方式改為先按高度排列,相同高度的按前面人數(shù)排列。這樣三個人就會排成[1,1],[2,0],[3,0]。

  let result = people.sort( (p1, p2) => {
    if (p1[VAL] === p2[VAL]) { 
      return p1[PRE] - p2[PRE];
    }
    return p1[VAL] - p2[VAL];
  });

在排序之后增加一個調(diào)整順序的過程。硬編碼快速通過,把前面有1個人的項目向后挪一位。也就是[1,1]挪到[2,0]后面。

  for (let i=0; i<result.length; i++) {
    if (result[i][PRE] === 1) {
      let temp = result[i];
      result[i] = result[i+1];
      result[i+1] = temp;
      i++;
    }
  }
第七個測試

三個人,最矮的排在最后。

reconstruct_should_be([[2,0],[3,0],[1,2]], [[2,0],[3,0],[1,2]]);

實現(xiàn)代碼,增加第二個硬編碼分支,處理有2個人在前面的情況。

  for (let i=0; i<result.length; i++) {
    if (result[i][PRE] === 1) {
    ...
    }
    if (result[i][PRE] === 2) {
      let temp = result.splice(i,1)[0];
      result.splice(i+2,0,temp);
      i+=2;
    }
  }

重構(gòu),把兩個分支改寫成相同的邏輯。

    if (result[i][PRE] === 1) {
      let temp = result.splice(i,1)[0];
      result.splice(i+1,0,temp);
      i+=1;
    }
    if (result[i][PRE] === 2) {
      let temp = result.splice(i,1)[0];
      result.splice(i+2,0,temp);
      i+=2;
    }

重構(gòu),統(tǒng)一為一個分支。

    if (sortedByVal[i][PRE] > 0) {
      let nLater = sortedByVal[i][PRE];
      let temp = sortedByVal.splice(i,1)[0];
      sortedByVal.splice(i+nLater,0,temp);
      i+=nLater;
    }
第八個測試

最高的人在前面,后面的兩個按高度排。

reconstruct_should_be([[2,1],[3,0],[1,1]], [[3,0],[1,1],[2,1]]);

實現(xiàn)代碼,呃…… 就這么掉坑里了。
首先是沒有很容易的快速通過的思路。
反復(fù)幾次后發(fā)現(xiàn)在排好的數(shù)組里挪動的方式,把一個人向后挪了之后,換來的人可能也需要后移。
所以決定不在原來的數(shù)組里挪動位置,而是全部插入一個新數(shù)組。插入時比較新數(shù)組里的位置和這個人前面人的個數(shù),直到所有的人都插入新數(shù)組為止。

function reorder(sortedByVal) {
  let result = [];
  let length = sortedByVal.length;
  for (let i=0; i<length; i++) {
    for (let j=0; j<sortedByVal.length; j++) {
      if (sortedByVal[j][PRE] <= i) {
        result.push(sortedByVal[j]);
        sortedByVal.splice(j,1);
        break;
      }
    }
  }
  return result;
}

但是發(fā)現(xiàn)這時候[ [ 1, 0 ], [ 3, 0 ], [ 2, 1 ] ]這個測試又失敗了。
經(jīng)過一段思索無法繼續(xù)后,我覺得就算通過苦思冥想把這個題解出來,也已經(jīng)和TDD無關(guān)了。就此停止了這次練習(xí)。

感想

從來沒有嘗試過仔細(xì)描述一段失敗的過程。不知道你讀到這里是和感受?

  • “簡直是浪費時間嘛”?
  • “早就知道這樣做不靠譜”?
  • “看起來還有救啊,怎么就失敗了”?
  • “太菜了,讓我來”?

這里說說掉坑的心得。怎么知道自己掉坑了。

  • 最終爬不出來的點很容易判定。突然發(fā)現(xiàn)自己思考了10分鐘,還是沒有寫代碼。
  • 顧此失彼,寫了一段代碼快速通過當(dāng)前測試,結(jié)果前面的測試掛了。
  • 感覺前面重構(gòu)出的結(jié)構(gòu)在阻礙新的用例快速通過。
  • 一個早期征兆是想不清楚下一個測試是什么?比如這個過程中兩次直接通過的測試,都代表了對當(dāng)前程序的行為沒有理解清楚。
  • 測試想不清楚的背后是沒有想出逐步分解問題的思路,進(jìn)而加入測試的順序可能不是適宜代碼演進(jìn)。導(dǎo)致重構(gòu)時對實現(xiàn)進(jìn)行重大調(diào)整。比如第六步和最后一步。

下一篇文章會總結(jié)我在反思之后重新解決的過程,敬請期待。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容