Murano Stones

  1. 有三位學姐放假的時候結伴去 Venice 逛市集。 她們想要買著名的 Murano Stones 當作紀念品帶回家。 市集上販賣紀念品的攤販很多,所以她們可以一家一家詢問比價。 可是由於遊客實在太多(Venice 每年約有三千萬名觀光客造訪;Taiwan每年約731萬造訪;Malaysia每年約2500萬。 如果你考慮一下這幾個地方的面積,你就會發現這數字實在驚人), 當天市集上只開放單向通行。 所以問完一家的價格後,如果當下決定不買,要再去問別家,之後是無法回頭再到這家購買的(所謂「好馬不吃回頭草」也)。
  2. 她們都想用最低的價格買到紀念品,但由於她們對於當地的市場行情沒有任何概念,所以她們就根據在資工系所學到的演算法 (algorithm) 知識,各自設計了一套詢價及採購的策略。
  3. Hestia 學姐最學術派,尤其她最近讀了暨大圖書館的一本書 The Mathematics of Love,書中提到,連真命天子 (soulmate) 的尋找都有演算法,她相信類似的策略可以幫助她在此地以最低價購得紀念品。 她打算在市集(一條長長的街)的前三分之一先收集資訊,看問到的最低價格是多少。過了三分之一以後,如果遇到任何一家攤販的售價,小於等於之前問到的最低價格,她就出手買下。
  4. Christina 學姐書讀得最多,所以腦袋裡剩下的工作記憶體 ( working memory) 也少。她沒有耐性問那麼多家,所以只打算先問三家就好(古人所謂「貨比三家不吃虧」呀),然後遇到小於等於那三家中最低價的,就成交。
  5. Trista 學姐最務實,她擔心如果堅持要比前面的價格低才成交,萬一後面的售價越來越貴,她豈不是要空手而回? 所以她打算先看完前二分之一,接下來只要小於等於第二低價她就買。但如果走完四分之三還沒遇到,就改為只要小於等於至目前為止的第四低價就成交。要是走了八分之七還沒有,就再放寬為小於等於到目前為止的第八低價就成交。
  6. 試撰寫一個程式,模擬上述三種策略,以供我們比較其優劣,作為之後學弟妹們逛街的參考。
  7. 讓我們用上學期學到的 Structure Chart 來協助設計這個程式。
  8. 首先你需要一個 CPP2x.cpp 檔,裡面包含有 main() 函式。它會在程式執行時由命令列接收四個參數 (見 P.202 Arguments to main): 最高價、最低價、攤位數、模擬次數。
  9. main() 函式會呼叫一個 printIntro() 函式,說明本程式之目的。
  10. 其次 main() 會呼叫 simNshopping() 函式,用 pass-by-value 傳進去最高價 (max),最低價 (min),攤位數 (nBooth),模擬次數 (nIteration)。傳出來的參數有兩個:成交次數 (nDeal)、以最低價成交次數 (nOptimal)。我們應可確定 nIteration >= nDeal >= nOptimal. 由於C語言中函式只能回傳一個值,不像 Python 可以 return 多個數值,因此簡單起見,這兩個參數以 pass-by-reference 的方式傳遞。
  11. simNshopping() 中用一個 for 迴圈,執行 nIteration 次, 每次呼叫 simOneShopping() 函式。 simOneShopping() 模擬單趟逛街行程中的採購情形,以 pass-by-value 方式傳入的參數為 最高價 (max),最低價 (min),攤位數 (nBooth); 以 pass-by-reference 傳出的參數為 成交的價格 (boughtPrice) 以及 此行所問到的最低價 (lowestPrice)。boughtPrice 傳入的初始為0,若傳出值亦為零,則代表未成交。
  12. simOneShopping() 中,以 for 迴圈執行 nBooth 次,每次產生一個大於等於 min 則小於等於 max 的整數,作為該攤位的定價 (boothPrice)。
  13. simOneShopping() 中以陣列儲存目前市集上詢到的所有價格。為簡單起見,我們在這裡用一個固定大小 (4800) 的整數陣列儲存。 (在第十章的 STL 中,我們會再學到用其他資料結構,像 vector 或 list 來做更有效的處理。) 這個陣列中我們把所有問到的價格由小排到大,所以 prices[0] 就是目前問到的最低價。
  14. 為了讓陣列排序,每次亂數產生 boothPrice 後,會呼叫 insert(int prices[], int currentBooth, boothPrice) 函式,把 boothPrice 插入到陣列中對應的位置。 你可以參考 Insertion Sort 的做法。 currentBooth 代表目前已行進到哪個攤位。
  15. 若 boothPrice 低於 lowestPrice (初始值為 max), 則更新 lowestPrice 的內容為 boothPrice。
  16. 若 boughtPrice 為零,則呼叫 wantToBuy() 函式判定是否成交。 若成交則令 boughtPrice 值為 boothPrice。
    (若 boughtPrice 大於零,代表剛才已經買了,接下來就算看到更便宜的,也無法再買了。)
  17. 每位學姐的 wantToBuy() 規則會不一樣,所以你應該準備 hestia.app, christina.cpp, trista.cpp 三個檔案。每個檔案中都各自是一個 wantToBuy() 式的定義。 編譯時你 CC CPP2x.cpp hestia.cpp -o hestia.exe, 再執行 hestia.exe,就是 Hestia 學姐的演算法。 CC CPP2x.cpp christina.cpp -o christina.exe, 就是 Christina 學姐的演算法。 CC CPP2x.cpp trista.cpp -o trista.exe, 就是 Trista 學姐的演算法。
  18. 三位學姐的 wantToBuy(), 其 function header 都相同,是 bool wantToBuy(int boothPrice, int currentBooth, int prices[], int nBooth)。
  19. Hestia 學姐的 wantToBuy(), function body 內容大致為
    
    if (currentBooth >= nBooth / 3 && boothPrice <= prices[0])
        return true;
    else
        return false;
    
    
  20. Christina 學姐的 wantToBuy() 則為
    
    if (currentBooth >= 3 && boothPrice <= prices[0])
        return true;
    else
        return false;
    
    
  21. Trista學姐的wantToBuy()則為
    
    if (currentBooth > nBooth * 7 / 8)
        if (boothPrice <= prices[7])
            return true;
        else
            return false;
    else if (currentBooth > nBooth * 3 / 4)
        if (boothPrice <= prices[3])
            return true;
        else
            return false;
    else
        if (boothPrice <= prices[1])
            return true;
        else
            return false;
    
    
  22. simNshopping() 依據 simOneShopping 所傳回的 lowestPrice 和 boughtPrice, 更新 nDeal 及 nOptimal。 若 boughtPrice 大於 0,則 nDeal 加一。 若 boughtPrice 等於 lowestPrice, 則 nOptimal 亦加一。
  23. simNshopping() 執行完後,main() 呼叫 printSummary(), 傳入 nIteration, nDeal, nOptimal。 函式會印出在 nInteration 次擬模中, 有 nOptimal/nIteration 比例以「最低價格」成交, (nDeal - nOptimal)/nIteration 比例有買到但非最低, (nIteration - nDeal)/nIteration 比例為空手而回。