Skip to content

当数学遇上编程,所有的边界都打破了(进阶篇)| 重塑创造的定义

Published:

说明:本文为作者原创,第一时间会发到微信公众号:「大圣不是圣」,由于微信公众号文章发布后不方便修改,因此这里也会同步一篇,错误或修订都更新在这里。

欢迎分享,若需转载请联系微信(ddupxyz),谢谢!

用此文向尊敬的 Seymour Papert(西摩.派珀特)致敬!作为一名教育家、数学家和人工智能专家,Papert 在他 30 年前的著作 《因计算机而强大》 中就已经不可思议地把通过编程去学习的本质讲得非常清楚了!

00

为什么要写进阶篇

之前写了 《当数学遇上编程,所有的边界都打破了-修订版》 这篇文章,前后花了不少时间,当然也得到了很多朋友的认可和喜欢,原本没有计划再写一篇的。

有两个理由,让我还是继续写了。

其一,从上次的文章到现在,已经很长一段时间了,带的孩子慢慢大些了,有的进入中学,有的出国,有些没上课了,但仍在联系,还是会讨论一些与学习相关的事,讨论的话题也自然要“进阶”一点,过程中积累了不少内容,整理并分享出来,或多或少对特定的人群会有一些帮助。

其二,这个原因比重其实更大一些。前段时间有个也在做编程教育的朋友,聊到一个话题,他说不少家长在问,现在 AI 越来越强大,听说 AI 都可以写程序了,那学编程还有必要吗?直接学 AI 就行了。

怎么来回应这个问题?

我个人是一直觉得编程是一种锻炼思维和逻辑能力很好的手段,对学习数学和英语也有相互促进的作用,一举三得的事,多好呀,而且学编程的目的从来都不应该是要去成为一名专业的程序员。

这里还有一个可能的问题是,很多孩子学编程和传统学数学的方式差别不大,刷题刷任务,参加竞赛去拿奖,在学习的过程中并没有感受到编程任何有趣和美的地方

原本写这个系列的目的,就是希望让大家看到,数学遇上编程,看那些原本高深枯燥的数学概念是如何变得鲜活且充满力量的

所以继续写一篇,也是“当数学遇上编程”系列的第二篇(进阶篇),算是回应这个问题。

引言

“What I cannot create, I do not understand.”
— Richard Feynman

在传统的数学课上,我们习惯了计算和求解:解方程、算答案、证定理。但在编程的世界里,我们更倾向于模拟与构造

无论是物理引擎背后的公式,还是绚丽动画背后的函数,亦或是算法优化背后的数论,都在告诉我们同一个道理:数学提供描述世界的语言,编程提供探索世界的工具。

在本文中,会使用到的编程语言包括:Python、C++ 和 JavaScript(p5.js),在算法的表达层面,其实不同语言差异并不大;在呈现上面,我选用了 p5.js,一个基于浏览器的适合交互设计的工具库,这也和我给一些学生上过这方面的课相关。

01

距离与形状:打破思维定势

1. 从一道简单的考题说起

最近有学生要去考 GESP(中国计算机协会主办的编程能力等级认证),练习时遇到一个编程任务:输入一个正整数(奇数)n,用编程去画出菱形图案。

例如,如果输入 n 是 5,输出的菱形图案如下:

..#..
.#.#.
#...#
.#.#.
..#..

如果输入 n 是 7,输出的菱形图案如下:

...#...
..#.#..
.#...#.
#.....#
.#...#.
..#.#..
...#...

这类符号图案,主要是通过观察去找规律,再通过代码根据规律去输出。

先看第一种方法,这种方法更像“画画”,图案怎么样,就怎么画。

设每行的左右两个 # 所在列的位置是 lr,接下来步骤也有了

用 Python 实现,代码如下:

n = int(input().strip())
mid = n // 2

l = mid
r = mid

for i in range(n):
    for j in range(n):
        if j == l or j == r:
            print('#', end='')
        else:
            print('.', end='')
    print()

    if i < mid:
        l = l - 1
        r = r + 1
    else:
        l = l + 1
        r = r - 1

如果只是考试解题的话,已经可以通过了。

不过感觉这个任务好像和数学也没什么关系,主要和观察的规律相关。

再来看另外一个完全不同思路的方法,这个方法会涉及到使用数学中的曼哈顿距离求解。

在学校里,我们学习的通常是欧几里得距离(也就是两点之间的直线长度)。但在计算机和城市规划中,还有另一种常用的距离:曼哈顿距离

注:画出上面的菱形图案,实际上有很多种方法,毕竟菱形是一个对称图案,可以从不同角度去观察并实现。

2. 什么是曼哈顿距离

这个名字源于美国纽约的曼哈顿区。

曼哈顿的街道规划得非常整齐,就像一个巨大的棋盘网格。街道主要由横向的“街”和纵向的“道”组成,把城市切成了一个个方块。

思考一个问题。

如果你站在曼哈顿的街头,要从一个路口(A点)走到另一个路口(B点):

走折线,也就是你只能横向或纵向(上、下、左、右)去走。

这样走的折线路程,就是曼哈顿距离。因为它最早是因为研究像曼哈顿这种方块街区的交通距离而得名的,所以也被称为“出租车几何距离”

这个距离很好求,就是横向走的路程与纵向走的路程之和。

假设我们在一个平面直角坐标系中:

横向走的距离,就是两个点的 X 坐标之差,因为我们不知道 x1 和 x2 谁大谁小,距离不能是负数,所以我们要求一个绝对值。

横向距离=x1x2横向距离 = |x1 - x2|

同样,纵向走的距离,就是两个点的 Y 坐标之差的绝对值。

纵向距离=y1y2纵向距离 = |y1 - y2|

这时候,两点间的曼哈顿距离就是横向与纵向两个距离之和,用下面的数学公式表示。

曼哈顿距离=x1x2+y1y2曼哈顿距离 = |x1 - x2| + |y1 - y2|

4. 用曼哈顿距离画菱形

如果我们换个角度来看上面的菱形图案,可以发现,构成菱形的每一个 # 符号,都满足:

每个 # 号到中心点的曼哈顿距离都相等。

如果是 5 行 5 列,每个 # 到中心点的曼哈顿距离都是 2;如果 7 行 7 列,每个 # 到中心点的曼哈顿距离都是 3。

更通用一点,如果是 n 行 n 列(n 是奇数),下标从 0 开始计算:

利用曼哈顿这个数学性质,上面菱形图案的实现代码就非常简单、优美:

n = int(input().strip())
mid = n // 2

for i in range(n):
    for j in range(n):
        d = abs(i - mid) + abs(j - mid)  # 曼哈顿距离
        if d == mid:
            print('#', end='')
        else:
            print('.', end='')
    print()

在这个基础,如果上面的 d == mid 从等式变为不等式,图案有什么样的变化呢?

尝试把等式改为 d >= mid,图案变成下面这样:

#####
##.##
#...#
##.##
#####

把等式改为 d <= mid,输出的图案是下面这样:

..#..
.###.
#####
.###.
..#..

当孩子发现 d <= mid 这样一个简单的数学不等式,竟然能控制打印出一个完美的菱形时,他们对坐标系和距离的理解一定会有一个质的提升

5. 关于曼哈顿距离的一点思考

如果我们继续探索,无论是从编程的角度,还是数学的角度,都还可以多一些思考。

从数学的角度来看,来思考一个问题,在平面上,两点之间的欧几里得距离(直线距离)和曼哈顿距离(折线距离),哪个长哪个短?

看下面这个示意图,是不是容易发现,三角形的斜边就是欧几里得距离,直角边之和恰好就是曼哈顿距离,三角形有个有性质,两边之和大于第三边,这种情况下,欧几里得距离永远是小于曼哈顿距离的。

还有一种情况,当 A 和 B 在同一行或同一列时,你不需要拐弯(折线)就可以直接到达,这时欧几里得距离和曼哈顿距离就刚好相等。

因此我们可以说,欧几里得距离(直线)确实永远小于等于曼哈顿距离(折线)

在很多游戏中,角色的移动通常是按“格”来计算的,比如,如果要快速判断一个怪物是否在你的攻击范围之内,这个范围的计算往往就是用的曼哈顿距离。

为什么不直接用欧几里得距离呢?毕竟欧几里得距离更短。

这是因为曼哈顿距离的计算只是使用了加减和求绝对值,而欧几里得距离涉及到开方运算,而开方运算在计算机中的代价要高得多,相比之下,求曼哈顿距离的运算速度非常快,所以经常被用来快速估算距离

这一点也恰好体现出,数学侧重于抽象层面的精确性,而编程上的问题要更具体一些,经常会考虑到工程上的优化和取舍

02

算法中的数论之美

数学不仅仅是计算,更是寻找规律与优化的艺术。在编程算法中,数学定理往往能将程序的效率提升成百上千倍。

1. 最大公约数与最小公倍数

1) 最大公约数与暴力求解

求两个数的最大公约数(GCD),是小学数学的经典题目。

比如求 12 和 18 的最大公约数。

我们可以把这两个数的所有公共的约数找出来,是 1, 2, 3, 6,那最大公约数就是最大的那个公共约数:6。

如果用编程去求解呢,学编程不久的孩子通常都会用“暴力枚举法”去做,就是一个一个去试,这种计算思想很直观,也好理解。而枚举一般来说都会用循环去实现,也正好是编程所擅长的。

要一个一个去试,那还得确定一个要尝试的可能范围。容易发现,两个数的公约数,不可能大于较小的那个数

比如,求两个整数 n 和 m 的最大公约数,写出的代码也很简单:

n = int(input()) # 两行分别读入 n 和 m
m = int(input())

gcd = 1 # 最差情况,两个数还有一个共同的约数 1
lower = min(n, m)

for i in range(1, lower + 1):
    if n % i == 0 and m % i == 0:
        gcd = i # 不断更新,最后留下的就是最大的

print(gcd)

当然,编程的实现很灵活,既然是找最大的,可以倒着来,这样第一个找到的就是最大公约数,效率上更高一些。

n = int(input())
m = int(input())

gcd = 1 # 最差情况,两个数还有一个共同的约数 1
lower = min(n, m)

for i in range(lower, 0, -1):
    if n % i == 0 and m % i == 0:
        gcd = i # 第一个找到的就是最大公约数
        break

print(gcd)

2) 最大公约数与欧几里得的智慧

上面的求解很好理解,但有个问题,如果要计算的数特别大,或者要计算很多组数的最大公约数呢,效率就会就比较低,有没有更高效的计算最大公约数的方法呢?

又回到数学,在公元前 300 年,欧几里得就发现了著名的辗转相除法(伟大的欧几里得再次出现):

gcd(a,b)=gcd(b,a(modb))\gcd(a, b) = \gcd(b, a \pmod b)

用文字来描述:两个数的最大公约数,等于其中较小的那个数和两数相除余数的最大公约数

用这个方法手动计算 30 和 18 的最大公约数:

当余数为 0 时,停止计算,因此 30 和 18 的最大公约数就是 6。

这个古老的数学智慧,恰好完美的对应了编程当中的递归算法。

写成 Python 代码极其优雅,同时也体现了递归之美

def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)

n = int(input())
m = int(input())

print(gcd(n, m)) # 输出 6

代码只有短短几行,却让编程与数学跨越千年相遇,多么美好的一种融合呈现。

如果要求最小公倍数呢?同样,也可以用暴力枚举法去求解,我自己在编程课上也会鼓励学生尝试用这种方法,毕竟,当数据量比较小时,暴力求解,是用编程去解决问题的一种最直接,最好理解的方式

不过在数学上,最小公倍与最大公约数,实际上有一个换算关系。

lcm(a,b)=abgcd(a,b)lcm(a, b) = \frac{ab}{gcd(a, b)}

因此,求最小公倍数再简单不过:

def lcm(a, b) {
  return (a * b) / gcd(a, b)
}

2. 再谈质数求解

在上一篇文章中,我们判断质数是一个个去试除,虽然我们当时通过缩小范围去提升了一些效率,但一个个去判断的效率还是太低了。

古希腊数学家埃拉托斯特尼(Eratosthenes)想到了一种绝妙的方法,被称为埃氏筛法

思路非常“编程”:

  1. 先把 2 到 n 的所有数扔进一个列表。
  2. 找到第一个没被划掉的数(2),它是质数。然后把 2 的倍数(4, 6, 8…)全划掉(标记为合数)。
  3. 找到下一个没被划掉的数(3),它是质数。把 3 的倍数(6, 9, 12…)全划掉。
  4. 重复这个过程…

这就像用一个筛子,把合数筛掉,剩下的就是质数。下面是用 C++ 实现的版本,非常高效:

#include <bits/stdc++.h>
using namespace std;

void sieve(int n) {
  // 创建一个布尔数组,初始化为 true (假设都是质数)
  vector<bool> is_prime(n + 1, true);
  is_prime[0] = is_prime[1] = false;

  for (int p = 2; p * p <= n; p++) {
    if (is_prime[p]) {
      // 如果 p 是质数,标记 p 的所有倍数为 false
      for (int i = p * p; i <= n; i += p)
          is_prime[i] = false;
    }
  }

  // 输出所有质数
  for (int p = 2; p <= n; p++)
    if (is_prime[p]) cout << p << endl;
}

int main() {
  int n;
  cin >> n;
  sieve(n); // 找出 n 以内的质数
  return 0;
}

如果输入 20,输出

2 3 5 7 11 13 17 19 

这里留一个问题,为什么按这样去筛,剩下的就一定全部是质数呢?

学算法的同学应该知道,很多算法思想实际是来自于数学,妥妥数学家的研究成果,而且有些算法比计算机出现的还早,只是计算机出现后,有了编程这个手段,正好可以最大化释放出这些算法的威力

3. 用概率算 π\pi

圆周率 π\pi 怎么算?除了公式法,还有一种基于概率的“暴力美学”:蒙特卡洛方法

想象一个边长为 2 的正方形,里面有一个半径为 1 的内切圆。

如果你闭着眼往里扔飞镖,飞镖落在圆内的概率是 π4\frac{\pi}{4}

只要扔的飞镖足够多,我们就能利用下面的公式反推出 π\pi

π4×落在圆内的次数总次数\pi \approx 4 \times \frac{\text{落在圆内的次数}}{\text{总次数}}

这里使用 p5.js 来让这个过程动态可视化,代码如下:

let R = 360;
let total = 0; // 总次数
let inside = 0; // 落在圆内的次数
let pointsPerFrame = 500; // 每帧撒点数量,值越大填充越快
let estPi = 0;

function setup() {
  createCanvas(R + 220, R + 40);
  textFont("monospace");
  background("#323252");

  // 画坐标区域边框(正方形)
  push();
  translate(20, 20);
  noFill();
  stroke(200);
  rect(0, 0, R, R);

  // 画四分之一圆(半径 R,圆心在左下角)
  stroke(120);
  arc(0, R, 2 * R, 2 * R, -HALF_PI, 0);
  pop();
}

function draw() {
  // 每帧撒 pointsPerFrame 个点
  push();
  translate(20, 20);

  noStroke();
  for (let k = 0; k < pointsPerFrame; k++) {
    let x = random(0, R);
    let y = random(0, R);

    // 判断是否在 1/4 圆内:圆心(0, R),半径 R
    let dx = x;
    let dy = y - R;

    total++;
    if (dx * dx + dy * dy <= R * R) {
      inside++;
      fill(60, 220, 120, 180); // 圆内点(绿)
    } else {
      fill(240, 80, 80, 160);  // 圆外点(红)
    }
    rect(x, y, 1, 1); // 画点
  }
  pop();

  estPi = 4 * inside / total; // 更新估计值
  drawPanel(); // 右侧信息面板
}

function drawPanel() {
  // 为节约篇幅,此处画右侧信息面板的代码省略
}

运行的动态效果:

这一刻,枯燥的常数 π\pi 变成了数千个随机点的宏大乐章。

03

动画中的数学

在这个主题的前一篇文章中,大家已经看到不少动画的呈现,尤其是 Scratch 的相关示例,这部分我们专门来讨论动画这个话题,编程的核心是逻辑,一旦再引入数学,画面就开始呼吸,有生命了

1. 动画原理

动画的本质,其实是一场欺骗眼睛的魔术。

人类的眼睛并不是摄像机,无法捕捉绝对的连续画面。当一个图像在我们的视网膜上消失后,神经信号并不会立刻消失,而是会保留大约 0.1 到 0.4 秒。

这就是视觉暂留效应。

如果你在 0.1 秒内连续播放稍微移动了一点的多张图片,你的大脑就会自动把这中间的空隙“脑补”起来,认为物体在连续运动。

比如上面是拍摄的骑马的静止照片,如果连续播放这些照片,就动起来了。

人类历史上第一部动态影像

网上流行的翻页书(Flipbook) 也是同样的原理。

翻页书

每秒钟播放的画面越多,动画就越平滑。

2. P5.js 简介

这里了为了通过编程去展示动画,选用了 p5.js 这个框架,主要是因为 p5.js 是基于浏览器设计的,而且官方提供了在线编辑器,打开就可以写代码,而且马上就可以看到效果,不需要任何额外的安装操作,而且分享展示也很方便。

在 p5.js 中,有两个核心函数,这两个核心函数是动画制作的基础。

  1. setup(),准备工作都在这里。

想象你要画画。你需要先买纸、调色、把画布架好。setup() 里的代码只会运行一次。

  1. draw(),动画的关键。

draw() 函数是一个无限循环,默认情况下,它每秒钟会运行 60 次,每一次运行,就相当于画了一页翻页书。因此需要做的变化(位置、颜色等)都应该放在 draw() 函数里。

由于本篇文章并不是要讲 p5.js,这里只是一个非常简单的介绍,感兴趣的可以去查阅官方文档,官方文档也有很多讲解和示例。

大家可以直接通过网站:https://editor.p5js.org/ ,编写 p5.js 代码,即时可以看到运行结果。

3. 三角函数的魔力

在高中数学中,提到三角函数,很多同学就会头痛,各种题目,一会儿是公式,一会是图形,一会又是数形结合。

今天我们从另一个角度来看看三角函数,让你感受一下原来它的魅力这么大。

在数学课上,三角函数的核心是 正弦 (sin\sin) 和余弦 (cos\cos),讨论的三角形的边长比。

而在动画中,它们是周期性运动的灵魂。无论是心跳、钟摆、弹簧还是波浪,任何来回往复的运动都离不开它。

学过正弦函数与余弦函数的应该都知道这两个函数的曲线长什么样子。

随着角度的变化,正弦曲线和余弦曲线也动起来了,效果是这样的。

注:上面这个动画效果,本身也是在 p5.js 里完成的

有什么用呢?可大了,我们知道正弦和余弦的值是在 (-1, 1) 之间来回变化,而且这个变化非常平滑。

利用这个特性,将这个平滑变化的值映射到任何我们希望动画的属性上去。

下面这段代码展示了一个圆,将正弦值映射到圆的大小(直径),直径的变化一下就平滑起来了,模拟出“呼吸”感。

let angle = 0;

function setup() {
  createCanvas(600, 600);
  noStroke();
}

function draw() {
  background("#323252");

  // 大小计算:利用 sin 让直径变化,模拟呼吸
  // map 函数将 sin 的 -1~1 映射到 50~100 的直径范围
  let d = map(sin(angle * 1.5), -1, 1, 50, 100);

  fill("#EFA5FF");
  ellipse(width / 2, height / 2, d);

  // 增加角度,控制速度
  angle += 0.05;
}

运行后的动态效果是:

4. 极坐标的跨越

极坐标是让我们从“工程师思维”跨越到“艺术家思维”的关键一步。

想象你在城市里指路,你可能会说:“向东走 3 个街区,再向北走 4 个街区。” 这就是笛卡尔坐标系 (x,yx, y),也就是我们通常说的直角坐标系,它是人类建造城市的逻辑,方方正正,不仅精确而且便于计算。

但大自然不这样指路。如果你问一朵花它是怎么长出来的,它不会告诉你 xxyy。它会遵循极坐标系 (r,θr, \theta) 的逻辑:从中心出发,旋转一定的角度 (θ\theta),然后向外生长一定的距离 (rr)。

要想在屏幕(基于像素 x,yx, y)上画出自然(基于极坐标 r,θr, \theta),我们需要一座桥梁。这就是著名的极坐标转换公式

{x=rcos(θ)y=rsin(θ)\begin{cases} x = r \cdot \cos(\theta) \\ y = r \cdot \sin(\theta) \end{cases}

xxyy 的计算方式从“固定数值”换成“基于角度的公式”,我们就瞬间拥有了画出圆形、螺旋线甚至玫瑰线的能力。

先来看一个花瓣效果:

如果在直角坐标系一下,很难去想到如果实现这个图案的绘制,但极坐标与角度相关,而且这里花瓣数量决定了如何选择角度,代码如下,也不复杂。

function setup() {
  createCanvas(800, 450); 
  noLoop(); 
  colorMode(HSB, 360, 100, 100); 
}

function draw() {
  background('#323252');
  translate(width / 2, height / 2); 
  noFill();
  strokeWeight(3); 
  
  let k = 5; 
  let maxRadius = min(width, height) * 0.4;

  beginShape();
  
  for (let a = 0; a <= TWO_PI; a += 0.01) {
    // 极坐标方程
    let r = maxRadius * cos(k * a);
    // 转换为笛卡尔坐标
    let x = r * cos(a);
    let y = r * sin(a);
    let hue = map(a, 0, TWO_PI, 0, 330);
    stroke(hue, 80, 100);
    vertex(x, y);
  }
  endShape(CLOSE);
}

如果多做几层花瓣,每一层花瓣有一个波动,就可以实现出下面这样的动画效果。

5. Perlin 噪声的自然法则

“随机是混乱的,但噪声是有序的。柏林噪声不仅是数学算法,它是对大自然‘无序中有序’这一哲学的完美复刻。”

自然界有很多看似混乱,但背后却有一定规则的现象,比如山峰,比如波浪。

如果我们用随机去模拟,比如,用 random() 去随机画一座山,你得到的可能是一堆乱七八糟的锯齿状图形。

这一小节,我们来看一个很有意思的算法:Perlin Noise(Perlin 噪声),它的作者 Ken Perlin,因为发明了这个算法而获得了奥斯卡技术成就奖。

为什么他会获得这个奖项呢?在他之前,用计算机特效去生成的一些纹理效果(如云层、波浪、火焰)都假得离谱,自从有了这个算法,电影里的特效才终于像样了,看起来很自然,不那么假了。

我们还是以 p5.js 为例来展示。

数字海洋,下面这段代码展示了如何利用 Perlin 噪声算法去实现一小段波浪运动的动画。

let time = 0;

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background("#323252"); // 深海蓝背景
  noFill();
  stroke(100, 200, 255);
  strokeWeight(3);

  beginShape(); // 开始绘制波浪线条  
  let xoff = 0; 
  for (let x = 0; x <= width; x += 10) {
    // 核心魔法:perlin noise
    let y = map(noise(xoff, time), 0, 1, 100, 300);
    vertex(x, y); // 确定波浪上的一个点
    xoff += 0.05;  // 值越小,越平缓;增加得越大,越陡峭
  }
  endShape();
  
  // 增加时间,让下一帧采样“旁边”的数据,形成流动感
  time += 0.01; 
}

运行得到的动画效果如下:

在此基础之上,做一些层叠调整,就可以得到下面这样的效果,很酷吧!原来看起来复杂无比的波浪效果,在算法的加持下,通过编程就这样模拟出来了。

6. 涌现与生命

涌现,本是复杂科学中的一个词,它触及了计算机科学与生物学、社会学交汇的边缘。

神奇的是,对于涌现来说,你没有编写复杂的行为,你只写了简单的规则,但复杂的智慧行为即诞生出来了

1986年,Craig Reynolds 提出了著名的 Boids 算法。他发现,只需要给每个个体赋予三个简单的向量力,就能模拟出极其逼真的鸟群。

在传统的动画里,如果我们要画一群鸟飞过天空,我们可能会计算每一只鸟的路径(自上而下的控制)。

但在“群集算法”中,我们放弃上帝视角

你不再控制一个物体,而是创造一群“有个性”的个体(比如 100 只鸟)。你不需要告诉它们“排成一队飞”,你只需要给每只鸟 3 条简单的局部数学规则

这是数学上被称为 Boids 算法,应用还是比较广泛,鸟群飞舞、鱼群回旋、甚至是复杂的交通流模拟,都与这个算法相关。

运用 Boids 算法来模拟下面鱼群的游动的代码较长,这里就不贴在文章里了,感兴趣的朋友可以加我微信,我发给你。

数学遇上编程,让我们看到无意义的简单物体如何从混乱中产生出秩序。

代码里没有任何一行写着大家一起往左转或者排成一字长蛇阵。每一条鱼都是近视眼,只关心自己周围 50 像素内的邻居。但当 150 条近视的鱼放在一起时,整体的秩序感出现了,涌现出鱼儿群体智慧。

05

在 AI 时代,重塑创造的定义

回顾这段旅程,我们并没有在枯燥的题海中沉浮,而是完成了一次思维的跳跃,数学不再是试卷上静止的符号,它是描述规律的语言;编程也不再是单纯的逻辑堆砌,它是构建世界的工具

回到文章开头朋友问的那个问题:“AI 既然这么强大,不仅能画画还能写代码,孩子还有必要辛苦学编程、悟数学吗?”

我的答案其实更加坚定:有必要,且比以往更有必要。 有个不太恰当的类比:有了自行车或汽车代步,是否就不需要锻炼身体了呢?

AI 确实能生成代码,也能生成画面,我们直接得到的是结果。

而数学与编程思维,掌握的是源头。写代码 这项技能或许会贬值,但 将现实世界的问题抽象为数学模型,并用计算思维去解决它 的能力,将变得无比稀缺。

当数学遇上编程,我们获得了一种上帝视角,不再满足于在别人构建的软件里点击按钮,我们开始渴望去定义重力、去编织光影、去创造生命。

当然,从学习的角度来看,相信这样的尝试更是有价值的!

【全文完】

// 广告时间

我建了一个相关的付费微信群,这个群大概会涉及到下面一些内容:

感兴趣的可以单独加我上面的微信,麻烦备注 “申请入群”,期待更多有趣、深入的讨论!

注:为了保证社群交流的质量,设了一个门槛费用,99 元/人/永久!若介意,请勿扰!

另外,符合下面条件的,可申请折扣或免费加入:

  1. 在读学生,29 元/人
  2. 以前参加过我在竹白发起的针对小朋友的 dailyup 每日挑战的付费朋友,可免费加入
  3. 有其它正当理由,希望折扣或免费加入的朋友

下一篇
你的生活,需要保留 10% 的“随机性”