译:极好的编程学习资源

原文:https://matklad.github.io/2023/08/06/fantastic-learning-resources.html
作者:Alex Kladov
译者:ChatGPT 4 Turbo

人们有时会问我:“Alex,我该如何学习X?”这篇文章是我通常给出的建议的汇编。这是“对我有效的事物”,而不是“世界上最棒的事物”。不过,我确实认为列表上的每一项都很棒,我永远感激那些整合这些资源的人。

学习编程

我认为我没有任何有用的建议关于如何从零开始学习编程。文章的其余部分假设你至少能够,在足够的时间内,编写简单的程序。例如,一个程序,它从一个输入文本文件中读取一个整数列表,使用二次算法对它们进行排序,然后将结果写入另一个文件。

Project Euler

https://projecteuler.net/archives 真是太棒了。前 50 个左右的问题是构建编程肌肉的完美“训练”,可以让你从“我能写一个程序来排序一个整数列表”变成“我可以轻松写出一个程序来排序一个整数列表”。

后面的问题非常依赖数学。如果你对数学有兴趣,这很完美 —— 你既能解决有趣的难题,又能练习编程。如果高级数学不是你的菜,一旦觉得不好玩就随时可以停止做题。

现代操作系统

https://en.wikipedia.org/wiki/Modern_Operating_Systems 非常棒。这本书的一个版本是我第一本阅读的厚重的编程相关书籍。它全面介绍了软件堆栈的内部工作原理,对我个人来说是一个转折点。阅读这本书后,我意识到我想成为一名程序员。

Nand 到 Tetris

https://www.nand2tetris.org 网站非常棒。它扮演了一个类似 MOS 的“大局观”角色,但这次你是画家。在这门课程中,你将从几乎无到有自己构建一个完整的计算系统。它不会教你真实的软件/硬件堆栈是如何工作的,但它彻底消除了任何神秘感,并且非常有趣。

CSES 题库

https://cses.fi/problemset/ 真是太棒了。这是一个算法问题列表,它精心设计,涵盖了所有标准主题到一个合理的深度。这是到目前为止练习算法的最佳资源。

编程语言

https://www.coursera.org/learn/programming-languages 非常棒。这门课程是对几种编程范式的快速导览,让你真正理解编程语言的本质(以及差异性)。

编译器

http://openclassroom.stanford.edu/MainFolder/CoursePage.php?course=Compilers 非常棒。在这门课程中,你将为一个简单但真实的编程语言实现一个工作中的编译器。请注意,你可以用任何语言来实现你的编译器。

软件架构

https://www.tedinski.com/archive/ 真是太棒了。按时间顺序浏览整个档案。这是迄今为止关于“大规模编程”的最佳资源。

随机建议

以下是我自己学到的一些东西。切勿全信!

关于导师制

拥有一个出色的导师是极好的,但导师并不总是可用的。幸运的是,如果你已经度过了最初的学习阶段,编程是可以在没有导师的情况下掌握的。当你编码时,你会得到很多反馈,通过反复试验,你可以处理这些反馈来提高你的技能。实际上,最难的部分其实是找到要解决的问题(而本文建议了很多)。但如果你有问题,你可以通过注意以下几点来自我提高:

  • 你如何验证解决方案是否有效。
  • 常见的错误及其避免技巧。
  • 解决方案的长度:你能使用更短、更简单的代码来解决问题吗?
  • 技巧 —— 你能应用你这周读到的任何内容吗?在 Haskell 中会如何解决这个问题?你能将语言 X 中的模式应用到语言 Y 吗?

在这种情况下,重复解决同一个问题很重要。例如,你可以尝试用你知道的所有语言解决同一个模型问题,并在尝试之间休息一个或两个月。重复做同一件事并注意到尝试之间的差异和相似之处是自学的本质。

关于编程语言

学习你的第一门编程语言是一场噩梦,因为你首先要学习你的编辑环境(PyScripter、IntelliJ IDEA、VS Code),其次是简单算法,最后才是语言本身。但之后就容易多了!

学习不同的编程语言是提高编程技能的最佳方式之一。通过观察它们的相似之处和不同之处,你可以更深入地了解事物的底层原理。不同的语言强调不同的习惯用法,学习多种语言可以大幅扩展你的词汇量。作为额外好处,学习了 N 种语言之后,学习第 N+1 种语言就变成了浏览官方文档的问题。

一般来说,你想要涵盖大类的编程语言:Python、Java、Haskell、C、Rust、Clojure 将是一个很好的基线。之后,Erlang、Forth 和 Prolog 也是不错的补充。

关于算法

学习算法有三个级别。

一级。

你实际上不是在学习算法,你在学习编程。在这个阶段,代码的长度、美观度或效率都不重要。唯一重要的是它能解决问题。通常,这个阶段会在你对递归相当熟悉时结束。Project Euler 的前几个问题在这里是很好的资源。

二级。

在这里,你将正确学习算法。这里的目标主要是对常见技术有百科全书式的知识。虽然有相当多的算法,但数量并不过多。在这个阶段,最有用的是理解算法背后的数学原理 —— 能够用铅笔和纸解释算法,证明其正确性,并分析 Big-O 运行时间。通常,你需要学习算法或技术的名称,阅读并理解完整的解释,然后实现它。

我建议首先做一个抽象实现(即,不是“用 HashMap 解决问题 X”,而是“仅仅是 HashMap”)。在你的实现中包含测试。使用随机测试(例如,在测试排序算法时,不要使用有限的示例集,生成一百万个随机的)。

实现同一个算法多次是可以的,甚至是可取的。解决问题时,比如 CSES,你可以抽象你的解决方案并重复使用它们,但最好每次都从头开始编码,直到你完全内化了该算法。

三级。

有一天,我大学毕业很久之后,我成了一门算法课程的助教。这门课的讲师是最初通过一门类似的算法课教会我编程的人。然后,在一个咖啡休息时间,他说了些什么。

我们教授算法并不是为了让学生们在工作中能闭着眼睛编写 Dijkstra 算法。他们可能根本不需要自己编写任何复杂的算法。

我们教授算法,以便学生在编写代码时学会思考不变量和属性。现实生活中的代码通常足够简单,如果你只是随意尝试,它大多数情况下都能工作。但它并不总是有效。要在工作中编写正确、健壮的代码,你需要思考不变量。

编写算法的诀窍是,编码它们很难。避免错误的唯一方法是强迫自己用不变量的思维方式来思考。

我被雷劈了!我没意识到这就是我学习(嗯,那时候是教)算法的原因!以前,我总是通过随机地调整大致正确的东西来混过算法,直到它能工作。例如,在二分查找中,只要在某处加上 +1 ,直到它不会在随机数组上循环。听了这个建议后,我回家又写了第一百万个二分查找,但这次我实际上加上了带有循环不变量的注释,而且第一次尝试就成功了!我在课程的剩余部分应用了类似的技巧,从那以后,我主观上感觉到的代码错误率(对于正常工作代码)大幅下降。

所以这是算法的第三层次——你磨练编程技能以实现无错误的编程。如果你已经对算法相当熟悉,不妨再尝试一次 CSES。但这次,在提交代码前无论花多少时间都要进行仔细检查,尽量确保第一次就做到无误。

关于算法名称

这是你可能想要能够以算法方式完成的事情列表。你不需要能够立即编写所有代码。我认为如果你知道每个词是什么意思,并且至少在过去实现过一次,这会有帮助。

线性搜索,二分搜索,二次排序,快速排序,归并排序,堆排序,二叉堆,可增长数组(又称 ArrayList,向量),双向链表,二叉搜索树,AVL 树,红黑树,B 树,伸展树,哈希表(链地址法和开放定址法),深度优先搜索,广度优先搜索,拓扑排序,强连通分量,最小生成树(普里姆算法 & 克鲁斯卡尔算法),最短路径(广度优先搜索,迪杰斯特拉算法,弗洛伊德-沃尔萨尔算法,贝尔曼-福特算法),子字符串搜索(二次,拉宾-卡普,博耶-摩尔,克努特-莫里斯-普拉特),字典树,Aho-Corasick 算法,动态规划(最长公共子序列,编辑距离)。

更大的程序

从零开始编码一个中等规模的项目是一个非常有力的练习。这样的项目需要的时间超过一天,但少于一周,并且具有意义的架构,这个架构可能恰到好处,也可能会搞砸。以下是一些很好的项目可以尝试:

光线追踪器

将一个 3D 场景的分析描述转换为彩色 2D 图像,通过模拟光线在物体上反弹的路径来实现。

软件光栅化器

将 3D 场景的描述作为一组三角形,通过将三角形投影到观察平面上并按正确顺序绘制投影,将其转换为彩色 2D 图像。

动态类型编程语言

一个解释器,它读取源代码作为文本,将其解析成 AST,并直接执行 AST(或者可能将 AST 转换成字节码以加快速度)

静态类型编程语言

一个编译器,它读取源代码文本,并输出二进制文件(WASM 将是一个极好的目标)。

关系型数据库

几个组件:

  • 存储引擎,它将数据持久地存储在磁盘上,并实现了磁盘上有序的数据结构(B 树或 LSM)
  • 关系数据模型是在原始有序数据结构之上实现的。
  • 关系语言用于表达模式和查询。
  • 要么是一个 TCP 服务器来接受作为数据库服务器的事务,要么是一个用于嵌入的 API,用于进程内的“嵌入式”数据库。

聊天服务器

一项关于网络和异步编程的练习。多个客户端程序连接到一个服务器程序。客户端可以向特定的不同客户端发送消息,或者向所有其他客户端发送消息(广播)。实现这一功能有许多变体:阻塞读/写调用、 epollio_uring 、线程、回调、futures、手动编码的状态机。

再次强调,进行六次带有变化的同一练习,比一次性完成所有练习更有价值。