程序员如何辅导儿子做数学?

靠山吃山,当然是借助编程了。

我家的孩子上小学三年级,比较喜欢数学,课外在深圳上学而思的创新预备班。去年寒假开始我教他学习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亿元的大买卖;咱们老鼠的儿子会打洞,程序员的儿子会写代码也不错。再说了,以后他当个科学家了,也得会编程验证自己的科学结果不是?

你可能感兴趣的:(实现,个人感悟,C/C++,数学,儿童编程,C,抗疫,寒假)