無痛入手 C++:應用教學 - 1A2B

更新於 發佈於 閱讀時間約 12 分鐘

須具備知識

  1. 無痛入手 C++:基礎系列8 - vector
  2. 如何產生亂數

遊戲規則

1A2B 是一個猜數字的遊戲,首先會隨機產生一組數字皆不重複的四位數,如: 1234。

接著會讓使用者猜數字,程式要依照使用者猜的數字回答猜中了幾個數字,其中位置猜對的為A,只有數字猜對位置猜錯的為B。

舉例來說,使用者猜了 1528,有一個數字 (1) 有出現在答案中且位置正確,有一個數字 (2) 有出現在答案中但位置錯誤,所以程式要回答 1A1B。假設使用者接著猜1278,程式要回答 2A0B。依此類推,直到使用者猜到正確答案。


架構設計

首先我們要隨機產生一組數字皆不重複的四位數。

接著需要讓使用者持續輸入四位數,直到猜對為止。所以應該要使用無限迴圈來讀取使用者猜的四位數,並且在使用者猜對以後跳出迴圈並結束程式。

每次使用者輸入四位數以後,我們需要將它拆解成四個數字,並比計算有幾個數字在答案中且位置正確 (A),有幾個數字雖然在答案中但位置錯誤 (B)。如果計算出來是 4A 的話就表示使用者猜對了。

最簡單且實用的 debug 技巧: 用 cout 把數值印出來看看是否如自己的預期。


產生四位數

首先要隨機產生一組四位數,而且數字不能重複。我們可以用以下程式隨機產生四個介於 0 ~ 9 的數然後存到 vector 裡面:

vector<int> answer;
srand(time(0));
// generate 4 random digits
for (int i = 0; i < 4; ++i)
answer.push_back(rand() % 10);

不過這樣的寫法可能會產生出重複的數字,所以還要檢查產生的數字是否已經出現過了。我們可以在產生新的亂數以後,走訪一次 vector,檢查是不是有相同的數字在裡面了。

// generate 4 random digits
for (int i = 0; i < 4; ++i) {
int randomNumber =rand() % 10;
bool hasDuplicate = false;
// check if there are duplicate digits
for (int j = 0; j < answer.size(); ++j) {
if (answer[j] == randomNumber) {
hasDuplicate = true;
break;
}
}
if (!hasDuplicate)
answer.push_back(randomNumber);
}

在檢查前,先宣告一個 bool 變數: hasDuplicate 來記錄是否有出現找到重複的數字,當內層的迴圈檢查到重複的數字時,就會把 hasDuplicate 設成 true 然後直接跳出迴圈 (因為只要有一個重複的數字就需要重新產生亂數,剩下的就不用檢查了)。

假如檢查完後 hasDuplicate 為 false,表示沒有出現重複的數字,可以把新產生的數字加到 answer 中。

有個地方也要調整: 如果出現重複的數字,就需要重新產生新的數字,也就是說,外層 for 可能需要執行不只 4 次。為了確保可以得到四個不重複的數字,我們將外層的 for 改成無限迴圈,並且在得到四個不重複的數字以後跳出迴圈。完整的程式碼如下:

vector<int> answer;
srand(time(0));
// generate 4 random digits
for (;;) {
int randomNumber =rand() % 10;
bool hasDuplicate = false;
// check if there are duplicate digits
for (int j = 0; j < answer.size(); ++j) {
if (answer[j] == randomNumber) {
hasDuplicate = true;
break;
}
}
if (!hasDuplicate)
answer.push_back(randomNumber);
if (answer.size() == 4)
break;
}


補充

檢查是否有出現重複數字的部分所用到的程式邏輯還算蠻常遇到的,用通用一點的方式描述的話是:

宣告一個用來記錄搜尋結果的變數;
// 走訪 vector 或其他容器的元素
for (...) {
if (檢查元素是否具有特定條件) {
將檢查的結果紀錄在變數裡面;
break;
}
}
if (檢查變數的值)
做出對應的行為​;

現代 C++ 並不鼓勵這種土炮寫法,不過目前學的東西還很有限,等熟練基礎語法以後再學習更好的寫法。


拆解使用者輸入的四位數

我們使用無限迴圈來讓使用者持續輸入四位數,int i 是用來記錄使用者猜了幾次。number 是用來儲存使用者輸入的四位數,digits 則是用來儲存拆開後的四個數字:

// read input from the user
for (int i = 0;;++i) {
int number;
vector<int> digits(4, 0);
cout << "Guess a four digits number(" << i + 1 << "th guess):\n";
cin >> number;
// TODO: split numbers into digits
// TODO: compare the digits with the answer
}
cout << "You win!\n";

我們首先來實作拆解四位數的部分。可以利用 % 來取得個位數字,接著將 number 除以 10,將所有位數往右移,本來的十位數字現在就變成新的個位數字了,重複這個動作 4 次,就可以取出所有的數字。

需要注意的是,我們是從個位數字開始取,最後才取千位數字,也就是說,我們取得的數字順序會剛好跟使用者輸入的數字順序相反。為了解決這個問題,我們需要從 digits 的最後一個位置開始擺數字,這樣個位數字才會是 digits 的最後一個元素,千位數字則會是 digits 的第一個元素

// split numbers into digits
for (int j = 3; j >= 0; --j) {
digits[j] = number % 10;
number /= 10;
}

注意: 外層的 for 使用的 induction variable 叫做 i,記得幫內層的 induction variable 取一個不同的名字。



比對答案

接下來要比對 digits 和我們一開始隨機產生的 answer,計算總共有幾個 A 和幾個 B

用更具體的方式描述的話是: 從 digits 逐一拿出數字,檢查這個數字是否出現在 answer 中,如果有的話,檢查他們在 vector 中的 index 是否一樣。

有了具體的描述以後,就可以很容易的將要做的事情轉換成程式碼: 從 digits 逐一拿出數字這件事情會需要一個 for,而每個數字又需要檢查是否出現在 answer 中,所以還需要第二個巢狀 for。

// compare the digits with the answer​
int numA = 0;
int numB = 0;
for (int j = 0; j < 4; ++j) {
for (int k = 0; k < 4; ++k) {
if (digits[j] == answer[k] && j == k)
numA += 1;
else if (digits[j] == answer[k])
numB += 1;
}
}

注意: 第二個判斷式其實不需要加上 j != k ,因為第一個 if 會攔截 j == k 的情況,所以會進到第二個 if 的一定是 j != k。

假如 numA 是 4,表示使用者猜對了,遊戲結束。不然的話,要將 A 和 B 的數字印出來:

if (numA == 4)
break;
cout << numA << " A " << numB << " B\n";


完整的程式碼

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
using namespace std;

int main() {
vector<int> answer;
srand(time(0));
// generate 4 random digits
for (;;) {
int randomNumber =rand() % 10;
bool hasDuplicate = false;
// check if there are duplicate digits
for (int j = 0; j < answer.size(); ++j) {
if (answer[j] == randomNumber) {
hasDuplicate = true;
break;
}
}
if (!hasDuplicate)
answer.push_back(randomNumber);
if (answer.size() == 4)
break;
}

// read input from the user
for (int i = 0;;++i) {
int number;
vector<int> digits(4, 0);
cout << "Guess a four digits number(" << i + 1 << "th guess):\n";
cin >> number;
// split numbers into digits
for (int j = 3; j >= 0; --j) {
digits[j] = number % 10;
number /= 10;
}
// compare the digits with the answer​
int numA = 0;
int numB = 0;
for (int j = 0; j < 4; ++j) {
for (int k = 0; k < 4; ++k) {
if (digits[j] == answer[k] && j == k)
numA += 1;
else if (digits[j] == answer[k])
numB += 1;
}
}
if (numA == 4)
break;
cout << numA << " A " << numB << " B\n";
}
cout << "You win!\n";
return 0;
}


挑戰題

檢查使用者輸入的數字是否符合要求:
1. 正整數
2. 四位數
3. 沒有出現重複的數字

如果不符合要求的話,請使用者輸入一組新的四位數。





留言
avatar-img
留言分享你的想法!
avatar-img
鏟薯員的窩
5會員
14內容數
程式設計 & 電腦系統 & 系統軟體
鏟薯員的窩的其他內容
2024/05/05
C 語言的函式庫定義了許多好用的函式,在寫 C++ 的時候可以拿來用。這是因為 C++ 當初在設計的時候,就有刻意把 C 涵蓋進來。 基本用法 首先要導入 C 語言的標準函式庫: #include <cstdlib> 以 c 作為開頭表示它是 C 語言的函式庫,只是被我們拿來 C++ 的程式
Thumbnail
2024/05/05
C 語言的函式庫定義了許多好用的函式,在寫 C++ 的時候可以拿來用。這是因為 C++ 當初在設計的時候,就有刻意把 C 涵蓋進來。 基本用法 首先要導入 C 語言的標準函式庫: #include <cstdlib> 以 c 作為開頭表示它是 C 語言的函式庫,只是被我們拿來 C++ 的程式
Thumbnail
看更多
你可能也想看
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
分享一個猜數字的遊戲題目,給予提示讓玩家找出正確的四位數密碼。
Thumbnail
分享一個猜數字的遊戲題目,給予提示讓玩家找出正確的四位數密碼。
Thumbnail
題目敘述 Single Number II 給定一個輸入陣列,已知有一個烙單的數字,其他剩餘的數字都恰巧出現三次。 請找出這個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [2,2,
Thumbnail
題目敘述 Single Number II 給定一個輸入陣列,已知有一個烙單的數字,其他剩餘的數字都恰巧出現三次。 請找出這個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [2,2,
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
題目給定一個布林代數的二元樹,要求我們計算最後的結果。 葉子節點都是真假值 非葉子節點都是布林運算子
Thumbnail
題目給定一個布林代數的二元樹,要求我們計算最後的結果。 葉子節點都是真假值 非葉子節點都是布林運算子
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給定三個參數a, b, c。 請問透過bit flip a 或 b 的binary bits,讓 a OR b = c 最少需要幾次bit flip? 題目的原文敘述 測試範例 Example 1: Input: a = 2, b = 6, c = 5 Output:
Thumbnail
題目敘述 題目會給定三個參數a, b, c。 請問透過bit flip a 或 b 的binary bits,讓 a OR b = c 最少需要幾次bit flip? 題目的原文敘述 測試範例 Example 1: Input: a = 2, b = 6, c = 5 Output:
Thumbnail
題目敘述 題目會給定一個猜數字的場景和介面 (包含一個可以呼叫,驗證是否為答案的API guess() function), 要求我們實現猜數字的function guessNumber(int n)。 題目已經事先設定好一個祕密數字,要求我們去找出來那個祕密數字是多少。 就好像小時候
Thumbnail
題目敘述 題目會給定一個猜數字的場景和介面 (包含一個可以呼叫,驗證是否為答案的API guess() function), 要求我們實現猜數字的function guessNumber(int n)。 題目已經事先設定好一個祕密數字,要求我們去找出來那個祕密數字是多少。 就好像小時候
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News