程序員如何輔導兒子做數學?

靠山吃山,當然是藉助編程了。

我家的孩子上小學三年級,比較喜歡數學,課外在深圳上學而思的創新預備班。去年寒假開始我教他學習C語言編程,每天1個小時左右,說是教,其實大部分時間是他自己看大部頭的《C Primer Plus》,也算是半自學。每天在我給他的舊13寸MacBook Pro上用VS Code敲入書上的代碼,或者自己改寫,然後在Mac的終端下gcc編譯,測試,倒也自得其樂。

也許有人會問,爲啥小學生學編程,不學簡單的Python而學更低層、更難的C語言呢?

原因之一是我自己最擅長C/C++,十八般兵器趁手就好,而且C語言並不難。原因之二是Python學校以後很可能會教,遲點再學也不晚。

言歸正傳。如今抗疫年代,深圳的小學推遲開學,數學老師每天在微信羣佈置幾道題目,讓同學們做好上傳。昨天的這道題頗有意思,而且還能編程來驗證,我和孩子深入探討了一番,父子倆樂在其中。

原題目:

長度爲60釐米的木頭,小明每隔2釐米做一個標記,小琪每隔3釐米做一個標記,然後在標記處鋸開,共有多少段木頭?

老師認爲是植樹問題,我們分析後將其看成周期問題,每到6釐米處小明和小琪的標誌會重疊,所以6釐米爲一個週期。故:

解答:每 2 × 3 = 6釐米 爲一個週期,這個週期有4段木頭,共有:(60 ÷ 6) × 4 = 40 (段)。

是不是頗爲簡單?我和孩子又進一步探討,如果將題目通用化,會怎麼樣?比如每3cm,每4cm或每5cm,9cm做記號,結果又會是幾段?

我們將題目改爲通用形式:

長度爲M釐米的木頭,小明每隔a釐米做一個標記,小琪每隔b釐米做一個標記,然後在標記處鋸開,共有多少段木頭?

首先還是得找出週期,週期顯然是a和b的最大公倍數c,如果a、b沒有公約數,則週期直接爲a×b。

然後是找出週期內的段數,顯然是:u = (c÷a) + (c÷b) - 1, 之所以減1,是因爲在週期的最後a、b的標誌重疊了。

總段數:x = (M ÷ c) ∙ u

還需考慮M不能整除c的情況,此時要加上餘下的部分段數。

算法找到了,我們就來編程實現吧,結對編程,爸爸敲入代碼,兒子在旁邊看,理解代碼並指出某些小錯誤。

 

第一步,先實現一個功能函數來可視化標記、並循環統計有多少段。當你用數學方法算出來後,可以用這個函數來驗證。 

這個函數相當於你自己用原始方法在紙上畫線並做標記,然後數有幾段,只是計算機不知疲倦,也不會出錯。

// 實際驗證: 打印並標誌出線段
int Verification(int M, int a, int b)
{
	int count = 0;
	bool a_flag = false;  // 是否該標誌a了
	bool b_flag = false;  // 是否該標誌b了
	for (int i = 1; i <= M; i++) {
		printf("-");  // 打印一短橫線,表示一個單位長度

		a_flag = false;
		b_flag = false;
		if (i%a == 0) {  // a長度到了,需要給a標誌
			a_flag = true;
		}

		if (i%b == 0) {  // b長度到了,需要給b標誌
			b_flag = true;
		}

		if (a_flag && b_flag) {  // a、b標誌重疊
			printf("Y");  // 打印一個 Y
			count++;
		}
		else if (a_flag) { // 僅僅a標誌
			printf("|");  // 打印一個豎線:|
			count++;
		}
		else if (b_flag) { // 僅僅b標誌
			printf("v");  // 打印一個:v
			count++;
		}
	}

	if (!a_flag && !b_flag) {  // 如果最後不是標誌爲a或b,意味着還有剩餘,線段數加1
		count++;
	}

	printf("\n\nline-segments count: %d\n", count);  // 打印出實際測量出的線段數

	return(count);
}

輸出短橫線-表示單位長度,豎線|爲a的標誌,v是b的標誌,Y是a、b重疊處的標誌。

假設M=60,a=2,b=3,這個函數可以輸出這樣的效果:

--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y

line-segments count: 40

很是直觀。

第二步,實現一個求最小公倍數的函數

2和3的最小公倍數是兩者相乘等於6,但如果4和6,則最小公倍數不是24而是12,在數學上計算最小公倍數需要分解素數,太複雜了。本算法簡化之,採用蠻力運算。

// 計算最小公倍數
int LowestCommonMultiple(int a, int b)
{
    int m = a>b ? a : b;  // 取a b中的最大者爲搜索起點
    int n = a * b;  // 兩數相乘爲最大的公倍數

    for (int i = m; i <= n; i++) {
        // 找到第一個可以同時被a或b整除的即爲最小公倍數
        if (i % a == 0 && i % b == 0) {  
            return(i);
        }
    }
    return(n);
}

第三步,實現主函數,將數學算法實現並驗證之

代碼會說話,請看代碼:

int main(int argc, char* argv[])
{
   if (argc < 4) {
      printf("Usage: %s M a b\n", argv[0]);
      return 0;
   }

   int M = atoi(argv[1]);
   int a = atoi(argv[2]);
   int b = atoi(argv[3]);
   if (a == 0 || b == 0 || M == 0) {
      printf("M a b must > 0\n");
      return 0;
   }

   // 數學算法開始:
   int x = 0; // seegments number,存放最終結果的變量

   if (a % b == 0 | b % a == 0) { // 如果a是b的倍數,則取其小者簡單計算即可
      int m = a > b ? b : a;
      x = M / m;
      if (M % m)
         x++;
   } else {
      // 計算週期長度,等於a b最小公倍數
      // 一開始使用a b相乘,不對。比如a=6, b=4, ab=24,實際的最小公倍數爲12
      // int cycle = a * b;  
      // 後來專門寫了個函數來計算最小公倍數:
      int cycle = LowestCommonMultiple(a, b);
      printf("cycle = %d\n", cycle);

      // 計算週期內的段數,減1是因爲在一個週期結束時兩者重疊,需去掉1個:
      int unit = cycle / a + cycle / b - 1; 
      printf("unit = %d\n", unit);

      x = (M / cycle) * unit;

      // 計算餘下部分的長度:
      int y = M % cycle; //如果不能整除,y爲餘數
      if (y > 0) {
         int z = y / a + y / b;
         if (y % a && y % b) { // 兩者皆不能整除,則加一
            z++;
         }
         x += z;
      }
   }
   printf("M=%d, a=%d, b=%d, Segments Number: %d\n\n", M, a, b, x);

   // 驗證:
   // verification for draw line
   Verification(M, a, b);

   return 0;
}

最後一步,編譯,測試輸出結果:

編譯:gcc linesegment.cpp -o linesegment

測試1:

./linesegment 60 2 3

cycle = 6

unit = 4

M=60, a=2, b=3, Segments Number: 40

--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y--|-v-|--Y

line-segments count: 40

測試2:

./linesegment 60 4 6

cycle = 12

unit = 4

M=60, a=4, b=6, Segments Number: 20

----|--v--|----Y----|--v--|----Y----|--v--|----Y----|--v--|----Y----|--v--|----Y

line-segments count: 20

測試3:考慮除不盡的情況

./linesegment 65 3 7

cycle = 21

unit = 9

M=65, a=3, b=7, Segments Number: 28

---|---|-v--|---|--v-|---|---Y---|---|-v--|---|--v-|---|---Y---|---|-v--|---|--v-|---|---Y--

line-segments count: 28

 

不必羨慕生子當如孫仲謀,王健林的兒子會做2億元的大買賣;咱們老鼠的兒子會打洞,程序員的兒子會寫代碼也不錯。再說了,以後他當個科學家了,也得會編程驗證自己的科學結果不是?

發佈了102 篇原創文章 · 獲贊 14 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章