普通视图

发现新文章,点击刷新页面。
昨天以前Hawstein's Blog

写给 Coral:去体验只此一次的人生

2025年7月11日 08:00

To: Coral / Aug 26, 2023
关键词:写作 / 职业选择 / 人生体验 / 直觉与理智

Hi,感谢来信!

你的邮件标题是「来自遥远的问候」,其实并不遥远。我已经从瑞士回北京了,目前一边带娃一边做产品。

我是年更博主,所以博客更新得很慢,哈哈。不过如果我认真思考这个问题,大概是因为生活中有比写文章更重要的事情,比如我最近在养娃陪娃,比如我还想再做些产品,所以写文章的事就被我搁置。我的笔记里还有不少文章草稿,只不过没有写完并发出来。

不过我还是很爱写文章,用文字来表达。所以大概再过些时间吧,说不定那时会产出更多。

很高兴我的文章能触动到你,我想这大概就是文字的力量。我最近又在读王小波的书,我总觉得,写作可以把人带到很远的地方,而这一点是程序无法匹敌的。我可以用程序做各种各样的产品,可是它们的可能性远远不如写作。用文字我们可以构造出《三体》,《黄金时代》,《Steal Like An Artist》,《Anything You Want》等等,可以打动人心,可以启迪智慧。

程序构造出来的产品,大概只能用来解决现实世界的问题,以及制造娱乐。

你说你在面大厂,这很好哇。如果有机会,去大厂体验一下,并没有什么不好。《人生不可 DP,但别永远贪心》说的是,你不必事事选眼前最优,不必步步为营。但不是叫你每次都随机选择,每次都要避开眼前最优,见着大厂我就一定不去。不是这样的。只此一次的人生,什么都值得体验。大厂也好,小厂也好,没有厂也好。都值得体验。

谢谢你的祝愿。也祝你顺利毕业,收获一个满意的 offer。当然最最重要的,尽可能多地去 follow your heart。直觉以及 gut feeling 有时候往往比理智做出的选择更有意思更「正确」,更让你不虚此行。

Best,
Hawstein

写给 Muniao:跳入生活这锅汤

2025年7月10日 08:00

To: Muniao / Sep 3, 2023
关键词:人生选择 / 英国留学 / 独立开发 / 冒险精神

认真读完了你的年终总结,点赞。我最喜欢的是倒数第二段:)可能是年岁渐长,更喜欢那种真诚的内心表达,尤其是偏形而上的哲学范畴,对人生的迷茫与思索。

你提到你老婆要去英国读书,然后你也跟着一起去英国。这和当初我和我老婆一起去瑞士挺像的。以我的三观,我肯定是很高兴你们做这样的选择的。人生短暂,多体验一点就赚到一点。换个大环境,给自己的人生加入一些不确定性以及可能性,说不定能探索出更广阔更有意思的未来。关于这个话题,我以前写过一篇相关文章叫《人生不可 DP,但别永远贪心》,你要是感兴趣可以看一看。

你说你还没想好去英国后做什么,其实这很正常的。我觉得很多事情,只有当你「跳」了进去,到了那个环境中,才会找着解决方案。当然,在还没有什么特别好的头绪时,先把目前已有的选择拿出来看一看,挑着一个开始做,也是不错的。比如,你现在已经开始做专栏做课程,那么可以继续沿着这个做。只不过,如果可以加一点约束,我觉得不如你也给自己定一点收入目标。毕竟如果你是打算全职做这个,先把物质基础搞稳了,后面想追求纯精神的东西(比如做个完全免费的好用的 to C 产品,或者搞开源),也不至于压力太大。

一旦决定自己做产品要达到「从 day one 就开始赚钱」,那么有些视角就不一样了。比如做课程,那就得稍微偏向市场需求,而不仅仅是按着自己喜爱或是擅长的来了。我当时辞职时也列了几个可做的方向,即使是做课程,我也有几个可做的内容。比如我最擅长的 Scala/Akka 生态,讲这些不仅是我擅长的,而且是我喜欢的。但我并没有去做这个课程,因为这个内容太小众了,买单的人肯定很少。如果课程做个一年半载,收入寥寥,那么想要继续走这条「独立创造者」的路,就会很难。甚至会和大部分曾经尝试过的人一样,最终回去上班。所以,最终我去做了算法课程。这并不是我偏爱的东西,但我也能讲好。所以我靠着它赚到了钱,走出了第一步。当然很重要的,也给了家属信心,相信我可以做好。

做独立产品是非常美好的,可是刚开始往往没有好的想法。自己闷起来硬想,我估计想出来的点子都不会是什么好点子。所以,要么你已经有不断在召唤你的产品想法,不然的话还是先在平时多看看海外 indie hacker 都在做些什么,然后慢慢找到感觉。

当然了,去英国后先找一家当地公司去工作一段时间也是不错的选择。一边工作再一边想自己的独立产品之路,这也更稳妥些。总之,每个选择都各有利弊,最后还是得根据你自身的情况(比如性格,对未来的态度,存款,家属的情况,等等去考虑)。

祝你和家属在英国的新旅程一切顺利。

Best,
Hawstein

写给 Shawn:不要投资,不要办公室,不组建团队

2025年7月9日 08:00

To: Shawn / Sep 15, 2023
关键词:独立创造 / 现金流 / 小型 B 端 SaaS / 公司的意志与群体性愚蠢 / 不要投资,不要办公室,不组建团队

注:这封回信是 2023 年 9 月写的,那时候说自学技术或代码,言必称 ChatGPT。还不到 2 年的时间,AI 领域又有了巨大变化。我想,身处巨变当中,凡事还是打上时间戳比较保险。因为今天的断言,很可能在明天就失效了。

// 来信片断:关于离开公司开始独立创造的契机

是否要破釜沉舟,我觉得这个是分人的。比如我吧,属于责任心比较强的人,所以一旦在一家公司工作,是比较难做到一边「摸鱼」一边搞副业的。尤其是国内的私企,本来就不是摸鱼氛围,我觉得大部分人都比较难做到一边在国内私企工作,一边做出好的商业产品。但如果有机会换到养老型外企,比如微软或谷歌,那么主业+副业的模式说不定就可行。我知道有个知名独立开发者在「苏州微软」上班,并且副业产品做得如火如茶。但这种「养老型」外企也分部门的,也就是有的部门也比较难摸鱼,所以说,全职搞自己的产品做成的机率肯定要大一些。

破釜沉舟其实也不是那么「悲壮」,也不是真的没有退路。即使我是裸辞来做自己的事情,我也是想好万一做不成之后的路的。我当时定的是半年时间内,产品要上线,且要有付费。不然我就回去找工作。

// 来信片断:好的产品想法应该怎么界定

我说的这两个原则是针对当时我刚辞职,手里什么都没有的情况下,定出来的。也就是说,我辞职了,一下子没有任何现金流收入,在这样的情况下,我最先要考虑的自然是「怎么尽快地能做出有现金流的产品」。所以就像你说的,可以做单一功能的简单产品,也可以做教程知识付费(2018 年知识付费还很火)。至于如何确保它能马上有付费,这个就是自己的判断了,也不能保证 100% 是对的。比如对于我,是因为我之前博客上有关于算法相关的 100 多篇文章,而且常年有稳定的访问流量,加上当时国内知识付费兴起,我一下子就觉得如果我出一套视频教程来讲算法,应该会有人付费。所以就这么去做了。结果证明也如我预想的一样。

一旦做出有稳定现金流收入的产品,那么就不太需要遵循这个原则了。比如我上一个产品,开发了一年,我可以投入大量的时间来做,也不用担心这段时间没有现金流收入。每个人都有不同的数字标准,就是自己定一个能接受的数字,比如每月稳定的现金流收入是 X 万元。OK,达到这个后,我就可以把赚钱的优先级调低一些,甚至去做一些哪怕不赚钱但我很喜欢的 to C 产品。

关于适合 Indie Hacker 的产品形态,我觉得是有的。既然是 Indie Hacker,说明就你一个人或者 2-4 人的小团队,因此不要去做市场太大的产品,不要做大公司看得上的产品,最适宜的产品品类我觉得就是 to small business 的产品,本身是 to B 保证了客户付费意愿更高,而 small B 保证了这个需求足够小,没有大公司或中型公司看得上。

// 来信片断:为什么是面相欧洲市场的产品

肯定是有理性客观原因存在的。那就是出海做产品的话,付费意愿高且付费能力强的,就是欧美国家。北美的话就美国和加拿大,欧洲的话也并不是说欧洲所有国家,而是欧洲那些发达国家:英法德意瑞等国。它反倒和我家属 transfer 到欧洲没太大关系。因为即使我在国内,我也会想到去考察一下欧洲对应的市场需求是否被满足。

// 来信片断:为什么是不要投资,不组建团队

对的,目前仍然是这个原则(不要投资,不要办公室,不组建团队)。我从做视频教程开始,就有投资人找我要给我投资,到后来做 SaaS 一样会有投资人想投。但是我一想到我以前待过的公司(也算是创业公司),我就发现,公司虽然不同,人也不同,但相同阶段遇到的问题很多是相似的。也就是说,你按照现在互联网这一套来搞公司,有些问题就是无法避免的,或者说大概率无法避免的。这个其实挺有意思的,我觉得可以把它称为「群体性愚蠢」,哈哈。就是,你作为单个个体,有时会觉得,公司这个决策看起来这么愚蠢,怎么还会被做出来呢?可是一旦个体处于公司当中,做决策的其实就是「公司」,可以认为公司里所有个体组成了「公司」这个超级个体,它仿佛有了新的意志,而它的每一个决策,其实就是公司里所有人合力的结果。所以自然是有可能做出让每个人都觉得不太满意的决策,包括公司的创始人。

「不要投资」我是肯定会继续坚持的,因为我选择做现金流稳定的产品,投资对我来说其实没有意义。有人会说,有了投资后就可以招人啊,做大做强啊。确实,资本的杠杆就是干这些事的。可是现在的我更愿意过一种自由的,没有来自公司的乱七八糟的问题的生活,哪怕钱赚得少一些。

「不要办公室」也会继续坚持,因为没有必要。

「不组建团队」这一点,未来可能会有变化。也就是说,未来我可能会招人,但不会招全职,而是以合同工的形式进行远程合作。我更喜欢松耦合的组织关系。可以比较容易解耦,其中的个体也会更加自由。关于团队和组织这一块,Gumroad 做了个很好的榜样。未来如果要搞团队,我大概率会参考 Gumorad。相关文章:

No Meetings, No Deadlines, No Full-Time Employees

// 来信片断:Indie Hacker 如何处理技能树问题

我觉得你提到的两个方案都行。具体看自己的偏好。找个技术合伙人一起,就会更快,你不用重新学很多技术。这一块的话,国内有个非常棒的例子,就是:少楠+白光(flomo 的创始人)。少楠擅长产品与营销这一块,白光擅长技术。他们的组合真的就属于非常棒的组合。但这个的难点在于,你得找着这样一个和你志同道合的靠谱的技术合伙人,最好就是你已经认识的前同事/同事/朋友,千万不要临时去哪里抓一个不认识的,我觉得这些都不太靠谱。合伙人选择不只是看技术,人品,责任心,包括其他性格方面的东西,都是很重要的考虑。所以,其实还挺难找的。

自学技术的话就不依赖外界,反正就在自己。而且,现在有了 ChatGPT,说实话,自学技术的难度已经很低了。尤其是,目标是做出产品,而不是去精通技术。在这一块,很多时候,知其然其实就够了。我是后端出身,现在有大量的前端代码,其实就是靠 ChatGPT 帮我写的,我只不过负责验证调优一下。

Best,
Hawstein

写给 Coral:Keep looking. Don't settle.

2025年7月7日 08:00

To: Coral / Apr 22, 2024
关键词:大厂 / 人生规划 / 自我探索 / 构建自己的游戏 / Keep looking. Don’t settle.

Hi Coral,

很高兴又收到你的来信。正好现在有空,给你快速回一封:)

首先祝贺你去了大厂,刚毕业去大厂是个很不错的选择。在那里你可以学习到职场以及专业领域里所需要的知识。你说时常想起我的文章,真的让我很高兴。文字真的很有意思,这也再一次提醒我应该多写。

与其问,三五年后是否有决心脱离这个巨大系统。不如现在就问自己,想不想脱离这样一个巨大系统,自己想要过怎么样的一种生活。然后开始为之努力或筹备。我不知道你在哪个大厂,也不知道你具体的岗位是什么,其实这些都不是很重要。更重要的可能是你得去想一想那些人生中最最基本的问题:你是怎样一个人,你想过一种怎样的生活,你想和怎样的人一起生活,你想要的那种生活在哪个国家/城市最容易达成。之类的吧。这些问题不是很容易想清楚,所以很多人到了 30 多 40 多甚至 50 多都想不清楚,反正越早想清楚越好,我觉得。

举我自己的例子,我在上学时,就非常明确,我想自己做点事情,我想构建自己的游戏(Build my own game)而不是参与到别人的游戏中。构建自己的游戏与系统,就可以自己制定规则;参与到别人的游戏或系统,就得遵守别人的规则。所以当我一边上大学,我就一边想我能做点什么呢?一边上研究生,也一边想我能做点什么呢?去第一份工作,也一边想我能做点什么呢?我觉得这个思考从未停止过,如果没有满意答案,那就继续生活。一旦有了答案,就可以做点什么(pull the trigger)。所以大家可能看到了一篇文章中的我,但那篇文章后面其实是大量的「念念不忘」与尝试。

乔布斯说过:Keep looking. Don’t settle. 我觉得这描述了很长一段时间的我,甚至现在,我也没有停止探寻。

感谢荐书和荐歌,歌已开始听,书已加入书架。

最后,也祝你工作顺利。然后把老乔的话再一次送给你:

Keep looking. Don’t settle.

Best,
Hawstein

写给 Sulhg:独立创造者的无限游戏

2025年7月6日 08:00

To: Sulhg / May 4, 2024
关键词:Indie Hacker / Micro SaaS / 辞职创业 / 不确定性 / 运气

Hi, 感谢来信!

我五一出去玩,刚回北京,正好小孩睡着了,可以坐下来给你回信。Better late than never :)

很真诚的一封来信哈,我也会很真诚地回复。

正如你所说,让我帮你分析该不该去,其实这种问题价值不大。越老就越发能感觉到,他人的建议属实大多无用,毕竟人与人之间有着无数看不到的差异(我愿称之为暗差异)。我之前听到过一种比较认可的说法,就是与其给别人建议,不如分享自己的故事,作为别人参考的一个数据点。你可以收集很多数据点,最后做出属于你自己才能做出的决策,走一条属于你自己独有的路。

// 来信片断:你当时是怎么做到那么坚定的放弃了自己平稳发展的道路的呢?

说实话,现在回过头去想想,其实应该算是我最后一家公司推了我一把。什么意思呢?就是虽然我是一直想自己出来单干搞事情,但是具体是在什么时间节点,要满足什么条件后才去做,这些我都是还没想好的。因为在当时我也没有一个说让我激动不已的事情,就像某种 calling,让我一定要把工作辞掉去做那件事。并没有。真实的情况是,当时公司内部有一些问题,资源又一直跟不上,所以我所在业务线做得就很累。在这样的情况下,慢慢地我自然就有些 burn out 和心灰意冷。所以有了决定要辞职。当时的第一计划是找家轻松点的公司工作,然后开展副业,等副业收入达到主业收入,再辞职全职自己做。但是日子一天天过去,我就觉得何必这么折中一下?既然我已经知道了自己最终要去的地方,为何不直接就去?决定就是一瞬间做出来的,剩下的就是兵来将挡水来土掩了。

// 来信片断:你当时是如何对抗脱离社会体系这一巨大的不确定性的呢?

我觉得一个很大的因素是我看到过很多很多的 Indie Hacker 成功案例。他们的生活方式,他们的工作方式,让我觉得那是一种我更想要的方式。我记得我在文章中也写到过,就是我真的是看了非常多非常多的 Indie Hacker 的故事,如数家珍我觉得一点不为过。并且我会在 Twitter 上持续跟踪他们以及他们产品的发展。现在国外好几个年收入在百万美元的 Indie Hacker,我是看着他们从几百刀的 MRR 一步一步做起来的。这些「数据点」给了我很大的信心,因为我觉得他们其实并没有比我优秀很多,既然他们可以,我应该也可以。

// 来信片断:比如,是否想过,自己万一没有成功,会被自己的后辈超越,可能还要回到公司打工?中间如果有怀疑,是如何解决的?做 Indie Hacker 后是否焦虑过?

我当然是设想过自己万一没做成,会怎么样的。我当时辞职后给老婆做了个 slides 讲了我的计划,就包含如果 6 个月内没有达到一个设定的目标,我就回去找工作。然后我觉得以自己的水平,找一份工作是没什么问题。所以如果自己做不成,就回公司打工,这个我其实是有预期的。至于会被后辈超越,我倒是没有这方面的焦虑,因为我知道现在许多比我年纪小的人,已经做出比我厉害的成就了。在 Indie Hacker 圈很常见。做 Indie Hacker 后是否焦虑过,我觉得应该是有焦虑过的。就是在产品迟迟没有起色时,或是前期开发产品投入过多时间时,这时可能就会怀疑是不是自己对产品的判断失误了。至于解决方法,我也说不好,有时是再开一条线做个新产品,有时是休息一下,继续投入开发已有产品。有时是尝试一种新的营销手段。反正就是继续「做事」,不管是做什么,反正在目前拿到的有限信息下做出判断然后去做,用行动缓解焦虑。

// 来信片断:你是否还有做一些其他项目呢?

有的。我一直在做新项目。目前手里有好几个项目了。SaaS 的好处就是边际成本会慢慢趋于 0。当软件逐渐成熟,onboarding 流程不断完善,文档也慢慢齐全。理论上到达一个状态后,这个 SaaS 基本上就不太需要我投入多少时间。而我就可以抽身继续做新产品。我觉得做 SaaS,尤其是 Indie Hacker 做 Micro SaaS,像是一场无限游戏(infinite game),可以一直玩下去。

不过就像很多人说过的,这里面是有运气成分的。我觉得这里运气有一个层面的意思是,摆在我面前有许多可以做的产品(至少对于我是这样),而我只能根据我有限的知识和信息,做出决策决定要做哪个。做这一个就成了,做那一个就失败了。这里面我们有时候会觉得,诶是我的决策好,其实不是,可能就是运气好,选对了。

但是,我觉得我们是有办法增加运气光顾自己的概率的。那就是让自己一直在这个赛道,不下牌桌,一直玩下去。许多 Indie Hacker 的路径就是,做 1 个产品失败了,2 个产品失败了,3 个产品失败了,继续做,在第 N 个产品时起飞了。如果是赌博,不下牌桌,有可能会在第 N 把赢把大的,但是后面还可能全输回去。但如果是做 SaaS,尤其是 Indie Hacker 这种 bootstrap 方式做 Micro SaaS,不下牌桌,在第 N 把做了个成功 SaaS 后,不会说在后面把钱全亏掉。bootstrapped micro saas 讲究的就是现金流,低成本,高 margin。所以,我觉得这是一个值得玩的游戏。只要我不下牌桌,那么我就有极大的可能做出个成功的产品。

没想到一口气写了这么多。就当是一个数据点,你看看。

Best,
Hawstein

写给 Sulhg:自我认知与做事的意义

2025年7月4日 08:00

To: Sulhg / Jun 8, 2024
关键词:自我认知与困惑 / 死亡思考 / 独立开发 / 现代性与分工

Hello,

原来你也在北京,后面要是时机合适,可以约着线下见一见:)

// 来信片断引用,省略。

哈哈。看这一段时,我也颇有感触。可能一年大一岁,我现在偶尔会感觉到「青春真是一段一去不复返的美好时光」。我有个朋友在大学当教授,有时候到大学找他,看到大学校园里那一张张青春稚气的面孔,真的会立马感叹自己已经不再青春,至少和他们相比是这样。现在我也有小孩了,前段时间小孩生病,奔波去医院,半夜小孩发高烧得起来喂药。体验了一把上有老下有小,家里有人生病的中年人生活:)也可能是一件件这样的事情,让我偶尔会感叹青春的美好。当然,这也有可能是一种「回忆中的总是更美好」的幻觉。

// 来信片断引用,省略。

那很好诶。我之所以会在上一封邮件反复强调「表达所带有的偏见」,是我在写东西时,常常担心给人一种「站着说话不腰疼」的感觉。由于我知道,每个人的处境和生活实践不一样,我不想把自己对世界的理解,当成一种通用建议或观点,说给别人听。生活中或网络上,我常常看到许多有人生阅历的人(其中不乏大 V 网红成功人士)喜欢言之凿凿地断言,给大众建议,仿佛他们在说着某种绝对真理。每次看到他们那样说话,我就提醒自已,千万不要和他们一样。无论那个人取得了多么大的世俗意义上的成功,我都觉得,世界之复杂与混沌,实在不是他们三言两语或是出本书,就能讲清楚的。

// 来信片断引用,省略。

同感。我的每一次回信,其实也是在自我对话。把自我的一部分投射到文字上。让自己更清晰地认知自我。我觉得,「认识自我」应该是个一生的话题,至少对我而言。我记得周迅在一次访谈中提到,她很惊讶古人说「四十不惑」,她说她 40 多了,可是还有很多困惑。当时我就很喜欢她的坦诚。其实何止 40,我看很多人活了一辈子,也还是有很多困惑,也没完全搞懂自己。只不过,到最后可能都不去想这个事罢了。

// 来信片断引用,省略。

我对这个问题没有解法。或者说我不把它当成一个问题。我觉得我是把它当成一个无法避免的事实,至少对于目前的科技来说是这样。当然了,未来不好说,我感觉未来说不定人类可能会掌握某种永生的技术。比方说全身上下所有的组织细胞都能人工提供/人工换掉,然后意识保持某种连贯性。这个有点扯远了,不管怎么样,我觉得我是赶不上那样的未来科技了。所以思考死亡,更多的就是给自己一点提醒,提醒自己人生短暂,这么短暂的一生到底应该怎么过。让自己思考宏大点的东西,有时候有助于不让自己陷入日常一件又一件的小事所带来的情绪中。

// 来信片断引用,省略。

我不太确定你说的的「奇迹」是什么?不过你提到的这两段,倒是让我想起了现代性的问题。高度分工确实容易让人丧失意义感,所以现在我看到有一小股思潮在兴起。无论是国外的 Indie Hacker 社区,还是最近又重新火起来的独立开发者。有不少个体想通过全程参与到一件事的完整环节中,并且直面市场,重新找到做事的意义。说到这一部分,我又有东西可以推荐。就是李厚辰的「翻转电台」中关于「好生活」,「做事」的那几期播客。他的播客太多了,我就不找了,你可以根据我给的关键词找一找,有空可以听一听。可以听听别人是怎么思考这个问题的。是不是能对自己有一些启发。

// 来信片断引用,省略。

感谢推荐哈。不过最近对我来说,能做的东西有点多了。所以最近我是在思考,当我可以做许多事的时候,我应该做什么。这其实也是一个自我探索的过程。因为有那么些时刻,面对自己的 10 几个产品想法,我发现没有一个能让我提起兴趣来做。人生每个阶段都有每个阶段的 struggle :)最早我没有什么产品想法,所以就不断探寻与搜索,期待能找着一个不错的想法。现在有不少产品想法记录在册,又得想,到底我要做什么。

Best,
Hawstein

写给 Sulhg:其他人也不过如此

2025年7月3日 08:00

To: Sulhg / May 25, 2024
关键词:职场晋升 / 自我认知 / 乔布斯 / 死亡思考 / AIGC 副业

Hi,下一封邮件你可以简单自我介绍一下,给我一个名字或ID,这样我好称呼你:)对了,我对你在哪个城市也感兴趣:)

从你拿 Offer 的情况,其实能看出在职场或者说找工作这一方面,你是很有竞争力的。因为目前这个环境,我看到不少人找工作确实很困难。优秀的人,其实是不需要太关心大环境的。我记得从我毕业那一年起,每年就都会有人说,今年是找工作最难的一年。可事实是,无论哪一年,都会有手里拿 10 多个 offer 的人。像你的情况也类似,大的经济环境确实是不如以前的,无论是从裁员的情况,中小企业倒闭的情况,写字楼空置的情况。甚至连我的小生意受影响的情况,我都能感知到,经济就是不如以前的。你还能拿到不少 offer,那说明你的实力是在那里的。

你说发现纠结的来源,是害怕无法掌控局面。这一点我其实相当能理解。面对未知,担心自己无法胜任。是我们这类偏谨慎型的人会考虑到的。但是我觉得我们可能得有意识地改善这一点。就是去更多地去发现这样一个事实:其他人也不过如此。关于这一点,我实在太喜欢乔布斯下面这个采访的节选了,我已经把它推荐给太多太多人了。这里我也推荐给你。

Steve Jobs Secrets of Life

Highlight: Everything around you that you call life was made up by people that were no smarter than you. And you can change it, you can influence it. You can build your own things that other people can use.

明白了「其他人也不过如此」这一点,并不会说让本来困难的事实变得简单。但是,它会极大增强我们的自信。而自信,我觉得是非常非常重要的。这不是大家喊口号里说的那种自信,而是一种,其他人也不过如此,让他们来面对这个问题,他们也可能毫无头绪,也可能要花很多时间去搞清楚怎么解决它。那么,当我一时不知道怎么处理时,我也不必过于羞愧,给我时间,我能解决的。

我先预祝你半年后完成晋升。但是,如果万一万一没晋升成功,我觉得也没有什么。真诚的,发自内心的,我觉得没有什么。Not a big deal. 晋升不成功,并不会就否定你这个人,你也不会被这样一次晋升成功与否定义。

我知道我这么说,其实是出自我所持有的世界观/人生观/价值观,每个人持有的三观不一样,所以对待同一件事,看法可能是完全不同的。但我还是觉得,人应该活得更松弛一些。我说得极端一点,人生短短不到百年,咱们死去的那天再来看这一次晋升,你会觉得有多重要呢?我觉得至少没你现在想的那样重要。

抱歉提到「死亡」,但我想,这是每个人都无法避免的终点,在 2024 年,应该是可以开诚布公地拿来讨论的。我是经常思考死亡这个话题的,这让我在做选择时,可以更多地听从内心的声音,而不是这个社会所提倡的主流观点。说到这,还有另一个我很喜欢的视频,就是乔布斯在斯坦福的毕业演讲,常听常新。

Steve Jobs Stanford Commencement Address

另外,为了避免我自己一不小心又回到某种谨小慎微/步步为营的状态,我给自己做了个图,贴在了我的电脑主屏,手机主屏,微信 banner,Twitter banner。如下:

Why so serious

关于副业,你说决定去做一些 AIGC 产生内容相关的事情,我觉得很赞,值得去探索一下。我觉得这一波 AI 浪潮是继移动互联网过后,时代又一次给我们的机会。我已经实打实地看到很多大/中/小公司,甚至个体,在 AI 这个领域,做出了有价值的产品,并收获和金钱和乐趣。我觉得浪潮来的时候,尽量还是要冲进去,做点什么,无论是什么都可。Do something.

好了,今天回信就这样。我写下的所有文字,都是基于我自己的世界观/人生观/价值观,你可能认可,也可能反对,都没问题。我自己持有一个观点,表达必然是带有偏见的。意思是,我们每个人的生活与实践不同,必然会产生独特的视角与观点。当然,有的人会与你相似,有的人会与我相似。但最终,没有两个人是完全相同的。而那些真诚地认为,自己在客观地表达的人,大概只是没有认知到世界的复杂性与自身的局限性。

最后希望你的主职工作以及 AIGC 副业一切进展顺利。

Best,
Hawstein

写给 Shen:自我探索与产品创造

2025年7月2日 08:00

To: Shen / May 27, 2024
关键词:自我探索 / 独立开发 / 产品哲学 / 自我表达 / SaaS

Hi Shen,感谢你的分享,很有意思。

你从「亚马逊电商」,到「做外包项目」,最后到现在「独立开发产品」,这个过程探索出来,其实就是一个慢慢了解自我的过程,一个自我探索过程。有的人是喜欢做亚马逊电商的,享受卖货的快乐;有的人是喜欢做外包项目的,享受实现的快乐而又不需要考虑太多产品层面的事情。显然,这些可能都不是你的热情所在,所以最后来到了自己创造自己做产品这条路。

我感觉这种实践出来的踏实感是很棒的。

我自己的话,在这方面可能比较早就想明白。所以不会去尝试做亚马逊电商或接外包项目。之前有很多外包项目找我,或者让我给公司当顾问,或者有投资人找我创业,我都婉拒了。关于这一点,我想的就是做互联网产品,做 SaaS,不拿投资,做现金流充裕的生意。没有实物的烦恼(供应商,工厂,海外仓,物流,有太多要操心的了)。所以在选择上,就给自己加了限制。

你说你现在在做一款自己也会用的产品,在我看来,这是很棒的。至少说,我觉得这是做产品的一种可行方式。关于做什么产品,各有各的说法,有的人说要做经过市场验证的产品,有的人说要做用户想要的产品(言下之意我自己用不用并不是很重要),有的人说要只做自己会用的产品。在我看来,都是可以的。不同时期,可以选择不同的策略。我做过给垂直领域用户用但我自己不用的产品,也做过自己想用而不在乎别人会不会用的产品,我觉得都可以。而艺术家 Rick Rubin 更直接,他在一个采访中这么说的:

I’m not making it for them. I’m making it for me. And it turns out that when you make something truly for yourself, you’re doing the best thing you possibly can for the audience.

最近我也在思考这个问题。我觉得对我来说,可能是分阶段,分目的,会采取不同的方式。比如说,如果我处于一个还没有任何营利产品的阶段,我最高的优先级是赚钱。那我可能会优先做用户想要的产品,因为他们想要,他们才愿意付钱。但如果赚钱已经不是第一优先级,更多的是想做自我表达,是一种艺术性地创造。那必然第一要考虑的就是自己,探索自己的内心,我在意什么,我的 obsession 是什么?我想把什么产品带到这个世界上来,让我的日常生活更加地愉快一些。无论是写字更愉快一些,记笔记更愉快一些,写代码更愉快一些,学习语言更愉快一些,都可以。一个精心设计的产品就是作者的自我表达。他通过创造一个产品,不仅表达出他认为这件事是重要的,也表达出他认为这件事应该这么做,而不是那样做。

因为你现在还有主业收入,所以不必为钱而焦虑。所以直接做一个自己需要的产品,让自己做得开心,我觉得就是一种很棒的方式了。其他的收获,都可以算是副产品。当然了,当你觉得你做的产品已经 ready,也可以充分向外传达出来。我最近看到一种说法,如果你做了一个好产品,那么营销它让更多的人能用上它,甚至是一件有德行的事。仔细想想也对的,但这包含了对自己产品的足够自信,确信它能给他人带来更好的生活。

你最后的结尾我很喜欢。你说的是持续尝试,我则表述为「让自己处于一种做事的状态」,大体上是差不多的意思。我喜欢「做事」这个词,它是一种产出与创造。最近我也一直在探索自我,想更深地了解自我。这真的不是件易事,难怪古希腊德尔菲神庙上才会刻着这样一句话(认识你自己)。渐渐地,我也想找着那些能让自己愿意投入10 年 20 年的长期的事情,而深刻地认识自己几乎就是前置条件了。不然,大概率做 1,2 年就觉得没意思了。

当然我也很是相信,人生是展开式的(unfold)。就是,我并不是在一个时间节点,想清楚了这个事,后面就能按着既定计划一直走。所以,即使在某一个时刻,我觉得自己找着了这么一件长期的事,也可能做着做着就生长出其他的可能性来了。或者自我也发生了变化。嘛,这就是人生啊。奇妙多变,不可预测。

我也祝你持续自我探索,更深入地认识自己。做出令自己满意的作品。

P.S. 如果有机会来北京,可以 ping 我。或者我有机会去广州,可以约饭。

Best,
Hawstein

写给 Melinda:比昨日更勇敢一点

2025年7月1日 08:00

To: Melinda / Sep 16, 2024
关键词:勇敢 / 系统 / 家庭 / 自由 / 死亡

Hi Melinda,感谢来信。

我常想,我们难得来人间走一趟,时间也不过百年,不妨活得大胆一些,勇敢一些。

说得直白一点,我们有一天都是要死掉的。在那个时候,如果回头看之前担心过的东西,害怕过的东西,畏首畏尾的事情,有多少是真的值得担心/害怕/畏首畏尾的呢?我想大概没多少吧。

只要不危害社会,不伤害他人,有什么想做的想说的,应该都给自己打打气,鼓励自己去试一试。

不过确实说起来容易做起来难。尤其是身处此时此刻,具体的事情和细节会被放大,大脑会全情的投入于此刻的细节中,所以很多事情就变重要起来。读书时,可能会觉得一次考试失利,天就要塌了;工作时,可能会觉得此次晋升的成败无比重要。但多年以后回过头看,往往都是云淡风轻。我以前上班时觉得无比重要的事情,在后来不上班的日子里,都觉得无足轻重。甚至在公司 A 觉得无比重要的事情,只要离开公司 A,那件事就变得一点都不重要了。

我想,大概就是因为我们是在一个又一个的系统当中,每个系统都它的价值体系,在那个系统里,我们被规训成遵守那套价值体系的人。

跳出系统,思路往往就不一样了。

不过呢,人是肯定至少要在一个系统中的。极端情况下,我可以不在任何一个学校/公司/机构,不组建家庭,逃离所有可能的系统。但是,我们也还是在一个地球上,还是生活在城市里,还在现今人类构筑的社会里。这是最后一个无法逃离的系统。那么我们就得至少遵守这个人类社会系统中的规范。所以,我肯定不能拿着刀冲到大街上捅人,也不能逆行把车开到对向车道上。// 我可以这么做,但会有很严重的后果。

除开人类社会,绝大多数人也还会在另一个系统中:家庭。当然,现在有不少年轻人不想结婚不要家庭,他自然也就更自由一分。不过我想,绝大多数人,还是组建了家庭。家庭这个系统也会有它的价值体系,有它对我们的要求。所以,当你决定要 follow your heart 去做一些事情时,就也得同时考虑一下对家庭里的成员会不会有什么影响。如果我只有一个人,那么我可以立即马上换一个国家换一个城市生活;我也可以把自己关在家里 1 个月,做一个自认为非常重要的产品。但是我有伴侣和小孩,那么我就不能这么做。

每个系统都有契约,人生也往往要折衷。

所以,勇敢就变得更加困难了。不过我想,正因如此,勇敢才更加可贵吧。学会去争取,去与自己生命中有羁绊的人协商。没办法做到无所顾忌的勇敢,那就尝试比昨日的自己勇敢一点点。

尝试去做,有不少事情应该都是能做到的。

加油,Melinda。

哈哈,我可真能写。娃醒了,我去陪娃了。

Best,
Hawstein

Indie Hacker 笔记 | 第 14 期

2020年1月8日 08:00

又到了码字的时间,这一次的心情很轻松。我想主要原因是,「AlgoCasts 每月小结」系列的完结,带来一个新系列的开始。人总是喜欢新的开始,正如新年给人以希望一样。新系列的名称定为「Indie Hacker 笔记」,由于它的前身「AlgoCasts 每月小结」已经出了 13 期,因此「Indie Hacker 笔记」就从第 14 期开始(关于新系列的命名考虑,文末会给出解释)。

AlgoCasts

距离上次小结已经过去一个多月了。这期间,终于把 AlgoCasts Plan 200 剩余的视频录完。至此,目前网站上 5 个系列共 211 个视频也就全都完结了。昨天把最后一个视频发布出去后,心情有点像来到了一个马拉松补给站。停下来喝点水,吃点东西,然后望望后面的路,想想接下来怎么跑得更好。小伙伴们也开始问我接下来的录制计划,这个在接下来几天之内应该就会发布到论坛公告上。时间过得可真快,不经意间,AlgoCasts 已经上线一年多了。而这一年多,我的工作生活模式也发生了很大的变化,遇到了不少有意思的人和事,感觉也差不多是时候写个跨度大一点的回顾了:)2020 年,我仍然会把大部分时间和精力放在 AlgoCasts 上,感谢一路陪伴与支持我的小伙伴们!除此之外,可能还会稍微分出一点时间来,开发一个小产品。这个小产品我已经构思挺久了,不过去年一直克制自己不要轻易开新坑,希望今年它能顺利面世。

德语

圣诞节后,我们把德语课的频次从一周一节提高到了一周两节。主要基于两个考虑,一个是多留点时间给我准备德语考试,二是德语老师在三月底要去维也纳进修他的语言学专业,我们想在他离开瑞士前把课程上完。到目前为止,我觉得德语学习的效果还是不错的。加之每天就生活在一个讲德语的城市,在学校里,在超市里,在车上,在路上,满目可见的都是德文,耳濡目染,进步也就快一些。我时不时会把这些生活中看到的德文拍照保存,然后再翻译学习。也经常为能看懂生活中某处出现的德文,为能听懂旁人聊天中出现的一些词汇或句子而小小高兴一下。在外买东西时,我也总是优先使用仅有的一点德语和对方交流,直到对方突然说出一个生词或是一个陌生的句子,使我露了馅,对方才会意并礼貌地切换成英语。这样有趣的例子可以说不胜枚举。零基础开始学习一样新东西的好处就是,进步是以一种可见的速度在发生的,毕竟起点太低了,越往后提升就越困难。不过关于德语学习,我还不需要考虑到「往后」这个阶段,毕竟还有段距离。

阅读

12 月看完了一本书,来自 James Clear 的《Atomic Habits》。这本书是从别人推荐书的推文回复里看到的(最近书架上至少有 4 本书是从别人的推文中挖掘出来的,我管这叫 steal from the tweets),于是在网上看了看评价后果断下单。书挺薄的,每天通勤路上看,很快就看完了。我觉得这本书写得还不错,书中倡导的「Build Your System」让我联想到福柯的「自我技术」。简言之就是,你的外在或内在系统,都可以由你精心设计,设计成适合你自己的系统,让你在这样一个系统内沿着理性为自己设定的路径前行,并且阻力尽可能小。整体上来说,我还是挺推荐去看一看这本书的。我有不少观点和书中观点一致,但如果让我写,现阶段应该没办法像作者写的那么好。关于输入和输出,我自己有一个理论,就是你永远只可能输出你所有输入的一个很小占比。所以,为了写一本还不错的书,为此要储备的知识与素材,要远多于书中所呈现的东西。当然了,输出垃圾不受这条理论约束:)

顺便提一句,关于「Build Your System」或者说「自我技术」,目前我了解到的人中,Stephen Wolfram 可以说是做到了极致。Stephen Wolfram 是计算机科学、数学、理论物理方面的著名科学家,是 Wolfram Research 的创始人,Mathematica 的首席设计师,《一种新科学》的作者。多年来,他一直在 hack his personal infrastructure。关于这些,强烈推荐阅读一下他写的长文:Seeking the Productive Life: Some Details of My Personal Infrastructure,相信你会获得一些启发。

App

最近在朋友的推荐下,开始使用一个 App 来记录时间,叫「Now Then」。一开始我对这个 App 并不抱多大预期,但在用了一小段时间之外,竟然发现意外地简单好用。如果你像我一样有记录时间的习惯,可以考虑下载来使用一下。关于记录时间这件事,最早我是从「奇特的一生」这本书看到的。这本书讲述了主人公「柳比歇夫」是如何使用他的时间统计法,对他做的事情以及花费的时间进行记录和研究的。后来我就开始探索适合自己的时间记录方式,也简单搜索过相关的辅助工具,不过都没遇到特别好用的。所以后来我干脆就用 Wunderlist + Evernote 的方式手工记录。我记录时间的方式不像「柳比歇夫」那样严苛,主要是围绕工作以及一些比较花时间的事情,碎片时间一般不记。这个不经意的习惯已经跟随我多年,目前我觉得带来的好处是,除了知道自己大量的时间花费到了哪里,对时间的敏感度也提升了。另外,在开始做一件事前,我会预估一个时间,做完后再记录下实际使用的时间,进行对比。如果这件事是需要经常反复做的,一般经过一段时间调整后,时间预估就可以达到一个比较准确的程度。

旅行

旅行方面,12 月份去了趟布达佩斯。在把所有细节都忘记后,目前我脑里关于布达佩斯的记忆就剩下:好看的建筑,好吃的美食,舒服的温泉。吃的方面,无论是食材的丰富程度还是做法的多样性,都比瑞士要高出不少。当然了,如果你是从国内出发去布达佩斯,请忽略我关于美食的那句评价。一月份还有两次出行:米兰和湾区。来瑞士后,出行变多了,所以最近还在思考以及需要解决的一个问题就是,如何在出行期间不打断我的某些 routine。所幸,我工作或生活中做的大部分事情,都不太需要依赖很「重」的外部条件。因此,大部分的事情即使在旅行中,也一样可以做。目前唯一的影响可能是我的录音设备不太方便携带,因此目前我决定出行期间就不录音,转而撰写/积累视频素材,或是做产品开发。(其实,非要携带上那台小小的 Blue Yeti 倒也不是不可以,最近我在推上还看到 Marques Brownlee 在去 CES 期间,把一整套非常专业的录音设备布置到了他入住的酒店里,为了消音,还把被子/毯子挂到了房间的墙上。虽说他是专业做这行的,但我也不禁小小感慨了一下,很多事情只要你想做,自然就会有办法。)

结语

前段时间接受了一个科技媒体采访,其中有一个问题的回答我自己还挺喜欢的,就用它作为这一期的结束语吧。问题是:工欲善其事,必先利其器。无论是工作还是生活,有什么特好的“磨刀法子”是你巴不得大家都知道的?面对这个问题,我第一反应并不是想到要分享什么「利器」,因为我发现当代社会影响我们「善其事」的首要因素,往往已经不是我们手中的「器」了,而是别的东西。以下是我的回答:

删掉抖音以及其他同类产品;把绝大多数的微信群(或者其他 IM 群)都设置成消息免打扰;所有的 App 都默认禁用通知,除非你认为这个通知重要得不能禁用;把所有的 IM App 都放到二级文件夹,或者移到首屏之外。

这个世界太嘈杂,嘈杂得想好好安静下来做一件事都特别困难。办法只有主动去关闭一些与世界的连接。相信我,这个世界大多数时候都只是在发出噪声而已,关闭一些连接的好处远远大于坏处。

完。

关于新系列的命名考虑

我受国外 Indie Hackers 影响挺大的,并且从 2018 年辞职成为一名 Indie Hacker,而我写的东西一般都围绕我的工作和生活展开,所以很自然地就想到用「Indie Hacker 笔记」这样的题目了。

为什么这个系列的名字要中英混用呢?因为至今我也没在中文世界找到 Indie Hacker 的合适对应词。Indie Hacker 直译是「独立黑客」,但中文里「黑客」的定义过于狭义了,刻板印象总会觉得「黑客」一定是和计算机或编程相关,但 Indie Hacker 覆盖的范围比这要大得多。事实上,我觉得在英文世界里,hack 这个词的泛化程度已经和中文里的「搞」有得一拼了。几乎什么都可以 hack:hack your life, hack your mind, hack your project, hack your iPhone…自然地,Hacker 的含义也就不再仅仅是一群搞计算机和写代码的人了。关于 Indie Hacker 这个词,indiehackers.com 的创始人(也是提出这个词的人)在 About 页面是这样描述的:

You’re an indie hacker if you’ve set out to make money independently. That means you’re generating revenue directly from your customers, not indirectly through an employer. Other than that, there are no requirements! Indie hackers are often solo founders, software engineers, and bootstrapped, but it’s totally okay if you have cofounders, can’t code, and have raised money.

由于实在没有合适的对应词,于是就决定保留英文原词了。另外再附上 IndieHackers 网站的一个相关讨论帖子:

[Discussion] What is an Indie Hacker?

AlgoCasts 2019 年 10 月小结

2019年11月30日 08:00

现在是瑞士时间 11 月 30 日晚上 9 点多,还有几个小时就 12 月了,踩在 11 月的尾巴上,得赶紧把我的每月小结写一写。这篇可能是「AlgoCasts X 年 Y 月小结」系列的最后一篇了,所以嘛,先来首淡淡忧伤的音乐定定基调:Flightless Bird, American Mouth。

So,是 AlgoCasts 要倒闭了么?这个倒没有,事实上 AlgoCasts 第一年的表现已经超出了我的预期,今年的营收目标也在 9 月份的时候就已经达到了。那为什么「AlgoCasts X 年 Y 月小结」要停了呢?Hmm,这个嘛,因为在这个框架下我的写(扯)作(淡)才华发挥不出来。事实上,每月围绕 AlgoCasts 写小结都让我绞尽脑汁。因为,每月围绕 AlgoCasts 做的事情其实是很简单的,就是录制算法视频。就算我一个月出 100 个视频,我觉得也没什么可写的。因为它的事件类型其实是单一的,就算我写出花来,说的也是我在做视频,对不对?嘛,自由职业者的生活就是这么朴实无华且枯燥:)因此,当我回顾过去一年写的 AlgoCasts 小结,除开做视频,我都会尽量在一个月里做点功能开发、搞点营销活动,这样写起小结来就还能扯点别的。不过最近几个月我已经不怎么做网站功能开发了,因此,我的AlgoCasts 月小结真的就要没有素材可写了。

当然,这个月我还是做了一次营销活动,非要写的话,整个 800 字,再升华一下随手谈谈人生,对我来说也不是什么难事。但我已经有点倦了。每月小结最主要是写给我自己看的,我要按我喜欢的方式来写。因此,「AlgoCasts X 年 Y 月小结」这个系列要停了,但是,一个涵盖范围更大,讨论主题更广的每月小结将就此产生。Surprise!

以后的每月小结将不会再只谈 AlgoCasts,而会是过去一个月工作/学习/生活中值得记录的内容。当然,能升华的我还会继续升华 233。毕竟生活中不只有柴米油盐,还有智人透过柴米油盐 YY 出来的哲学道理。由于 AlgoCasts 会继续占据我生活很大的一部分,因此每月小结肯定还是会有它的身影。不过,再次谈起来,我希望可以更感性一些。而不是冷冰冰的本月录了 X 个视频,修改了 Y 份简历,做了 Z 场模拟面试。听起来就很冷,缺少温度。

那么,「AlgoCasts X 年 Y 月小结」的最后一篇是不是还得守好最后一班岗,交待一下本月做的事情呢。其实上面我就已经说了:录了 X 个视频,修改了 Y 份简历,做了 Z 场模拟面试。哦,还搞了次促销活动。

你看,自由职业者的生活就是这么朴实无华且枯燥:)

完。

AlgoCasts 2019 年 8 月小结

2019年9月28日 08:00

每月小结又拖出新高度了,看了眼 Wunderlist 和 Google Calendar,过去一个月可以概括如下:搬新家,装家具,修电器,做视频。网站方面加了个简单的 EDM 功能,大家肯定没兴趣听;做视频呢,也没什么好讲的。那就简单讲讲生活上的事情吧。本月的小结音乐来自人称西班牙版彭于晏的帅气男歌手 Álvaro Soler:「Sofía」。

过去一个月最主要的事情就是搬进了新租的房子。在瑞士,一个多月就找到房子并入住,应该算是比较快的了。我们租的是不带家具的房子,所以跑了几次宜家,才慢慢把家具买全。然后把安装家具这件事分摊到过去一个月,隔三差五安装几件,包括安装大件的家具和钻孔装灯。在国内,这些都花钱雇人搞定。但考虑到这边人工比较贵且我们对这边还没那么熟悉且德语还达不到自由交流的地步,所以就自己动手了。在瑞士,一般上一个租客走的时候会把他的家具都清理掉,包括灯(除非你和他提前商量好要保留)。所以我们刚搬进来时,客厅、走廊和几个房间的房顶上都只有几根裸露的电线=。=作为一个经常吐槽自己生活经验为 0 的人,在房顶上钻孔装灯对我来说还是比较困难的。。不过在安装了一两个灯找到感觉后,就越来越顺手,可以说是指哪钻哪,钻哪装哪。

除了自己安装家具,租来的房子里有几件电器坏掉了。于是开始了和本地 3 家公司的打交道过程,分别是 Service 7000 AG,Clean Up AG,Baumann Koelliker AG。每次预约上门修电器时,机智如我都是快速用英语介绍一下自己,希望对方知道我并不会讲德语。然而这并没有什么用,对方还是一定会用德语回答我。然后我会再显式地问一下对方,会不会讲英语。这基本上成了我在这边接打电话的基本 Pattern 了。Service 7000 公司的工作人员大多会讲英文,无论是电话里的客服,还是上门维修的师傅。烘干机是 Service 7000 上门维修的,换了一个中控板子,一次搞定。洗碗机就没那么幸运了,目前已经上门维修 3 次了,第 4 次约在国庆节后。橱柜灯目前上门维修 2 次,第 3 次也约在国庆后。它们分别由 Clean Up 公司和 Baumann Koelliker 公司负责,这两个公司的人员基本上不会讲英语。他们一般上门后,一句 Hello 打过招呼就开始干活。有什么问题也是直接用德语问我,然后我通过当时的上下文和他的肢体动作猜他在说什么,再用英文回答他。我估计对方会使用同样的方法猜我回答的是什么=。=当然,关键场合还少不了 Google 翻译,对方会把一些重要的信息在手机上用 Google 翻译后给我看。比如说,是哪个部件坏了需要换。

工作上,如果看过我上个月小结,就知道我目前是去 ETH 办公。又过了一个月的时间,目前可以说是轻车熟路了。我自己对比了一下之前在北京咖啡厅办公时的状态,整体上在大学里办公的状态比咖啡厅里还要更好一些。随着搬入新房子慢慢安顿下来,这个月视频产出的数量也慢慢恢复上来了。希望这个状态可以保持下去。

过两天就是国庆长假了,预祝大家假期快乐!

十大经典排序算法视频讲解

2019年9月16日 08:00

代码仓库

代码仓库包含了完整的代码实现和测试,其中 Java 版本是官方实现,其他语言版本来自社区贡献。对于每一种排序算法,如果有多种实现方法,都会尽量提供。另外代码仓库中还提供了完备的测试用例,以此确保实现的排序算法覆盖了每一种可能的情况。

GitHub 链接:https://github.com/HawsteinStudio/algocasts-sorting-algorithms

冒泡排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/AEpoDvWQ

截图:

代码:

public class BubbleSort {

  private void swap(int[] arr, int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }

  // Time: O(n^2), Space: O(1)
  public void sort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length;
    for (int end = n-1; end > 0; --end) {
      for (int i = 0; i < end; ++i) {
        if (arr[i] > arr[i+1]) {
          int tmp = arr[i];
          arr[i] = arr[i+1];
          arr[i+1] = tmp;
        }
      }
    }
  }

  // Time: O(n^2), Space: O(1)
  public void sortShort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length;
    for (int end = n-1; end > 0; --end)
      for (int i = 0; i < end; ++i)
        if (arr[i] > arr[i+1])
          swap(arr, i, i+1);
  }

  // Time: O(n^2), Space: O(1)
  public void sortEarlyReturn(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length;
    boolean swapped;
    for (int end = n-1; end > 0; --end) {
      swapped = false;
      for (int i = 0; i < end; ++i) {
        if (arr[i] > arr[i+1]) {
          swap(arr, i, i+1);
          swapped = true;
        }
      }
      if (!swapped) return;
    }
  }

  // Time: O(n^2), Space: O(1)
  public void sortSkip(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length;
    int newEnd;
    for (int end = n-1; end > 0;) {
      newEnd = 0;
      for (int i = 0; i < end; ++i) {
        if (arr[i] > arr[i+1]) {
          swap(arr, i, i+1);
          newEnd = i;
        }
      }
      end = newEnd;
    }
  }

}

选择排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/Z5mzdwpd

截图:

代码:

public class SelectionSort {

  private void swap(int[] arr, int i, int j) {
    if (i == j) return;
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }

  // Time: O(n^2), Space: O(1)
  public void sort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length;
    for (int i = 0; i < n; ++i) {
      int minIdx = i;
      for (int j = i+1; j < n; ++j)
        if (arr[j] < arr[minIdx])
          minIdx = j;
      swap(arr, i, minIdx);
    }
  }

  // Time: O(n^2), Space: O(1)
  public void sortFromEnd(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length;
    for (int i = n-1; i > 0; --i) {
      int maxIdx = i;
      for (int j = 0; j < i; ++j)
        if (arr[j] > arr[maxIdx])
          maxIdx = j;
      swap(arr, i, maxIdx);
    }
  }

}

插入排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/dbGY9eG5

截图:

代码:

public class InsertionSort {

  // Time: O(n^2), Space: O(1)
  public void sort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    for (int i = 1; i < arr.length; ++i) {
      int cur = arr[i];
      int j = i - 1;
      while (j >= 0 && arr[j] > cur) {
        arr[j+1] = arr[j];
        --j;
      }
      arr[j+1] = cur;
    }
  }

}

希尔排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/zbmKZgWZ

截图:

代码:

public class ShellSort {

  // Time: O(n^2), Space: O(1)
  public void sort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    for (int gap = arr.length>>1; gap > 0; gap >>= 1) {
      for (int i = gap; i < arr.length; ++i) {
        int cur = arr[i];
        int j = i - gap;
        while (j >= 0 && arr[j] > cur) {
          arr[j+gap] = arr[j];
          j -= gap;
        }
        arr[j+gap] = cur;
      }
    }
  }

  // Time: O(n^2), Space: O(1)
  public void insertionSort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    for (int i = 1; i < arr.length; ++i) {
      int cur = arr[i];
      int j = i - 1;
      while (j >= 0 && arr[j] > cur) {
        arr[j + 1] = arr[j];
        j -= 1;
      }
      arr[j + 1] = cur;
    }
  }

}

快速排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/kVG9Pxmg

截图:

代码:

public class QuickSort {

  private void swap(int[] arr, int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }

  // [ ... elem < pivot ... | ... elem >= pivot ... | unprocessed elements ]
  //                          i                       j
  private int lomutoPartition(int[] arr, int low, int high) {
    int pivot = arr[high];
    int i = low;
    for (int j = low; j < high; ++j) {
      if (arr[j] < pivot) {
        swap(arr, i, j);
        ++i;
      }
    }
    swap(arr, i, high);
    return i;
  }

  // lomuto partition 的另一种实现,可以把最后的 swap 合并到循环中。
  private int lomutoPartition2(int[] arr, int low, int high) {
    int pivot = arr[high];
    int i = low;
    for (int j = low; j <= high; ++j) {
      if (arr[j] <= pivot) {
        swap(arr, i, j);
        ++i;
      }
    }
    return i-1;
  }

  private void lomutoSort(int[] arr, int low, int high) {
    if (low < high) {
      int k = lomutoPartition(arr, low, high);
      lomutoSort(arr, low, k-1);
      lomutoSort(arr, k+1, high);
    }
  }

  private int hoarePartitionDoWhile(int[] arr, int low, int high) {
    int pivot = arr[low + (high-low)/2];
    int i = low-1, j = high+1;
    while (true) {
      do {
        ++i;
      } while (arr[i] < pivot);
      do {
        --j;
      } while (arr[j] > pivot);
      if (i >= j) return j;
      swap(arr, i, j);
    }
  }

  // [ ... elem <= pivot ... | unprocessed elements | ... elem >= pivot ... ]
  //                         i                      j
  private int hoarePartition(int[] arr, int low, int high) {
    int pivot = arr[low + (high-low)/2];
    int i = low, j = high;
    while (true) {
      while (arr[i] < pivot) ++i;
      while (arr[j] > pivot) --j;
      if (i >= j) return j;
      swap(arr, i++, j--);
    }
  }

  private void hoareSort(int[] arr, int low, int high) {
    if (low < high) {
      int k = hoarePartition(arr, low, high);
      hoareSort(arr, low, k);
      hoareSort(arr, k+1, high);
    }
  }

  // Time: O(n*log(n)), Space: O(n)
  public void lomutoSort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    lomutoSort(arr, 0, arr.length-1);
  }

  // Time: O(n*log(n)), Space: O(n)
  public void hoareSort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    hoareSort(arr, 0, arr.length-1);
  }

}

归并排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/M0G2k7pz

截图:

代码:

public class MergeSort {

  // sorted sub-array 1: arr[low ... mid]
  // sorted sub-array 2: arr[mid+1 ... high]
  private void merge(int[] arr, int low, int mid, int high, int[] tmp) {
    int i = low, j = mid + 1, k = 0;
    while (i <= mid && j <= high) {
      if (arr[i] <= arr[j]) tmp[k++] = arr[i++];
      else tmp[k++] = arr[j++];
    }
    while (i <= mid) tmp[k++] = arr[i++];
    while (j <= high) tmp[k++] = arr[j++];
    System.arraycopy(tmp, 0, arr, low, k);
  }

  private void mergeSort(int[] arr, int low, int high, int[] tmp) {
    if (low < high) {
      int mid = low + (high - low) / 2;
      mergeSort(arr, low, mid, tmp);
      mergeSort(arr, mid + 1, high, tmp);
      merge(arr, low, mid, high, tmp);
    }
  }

  // Time: O(n*log(n)), Space: O(n)
  public void sortRecursive(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int[] tmp = new int[arr.length];
    mergeSort(arr, 0, arr.length - 1, tmp);
  }

  // Time: O(n*log(n)), Space: O(n)
  public void sortIterative(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length;
    int[] tmp = new int[n];
    for (int len = 1; len < n; len = 2*len) {
      for (int low = 0; low < n; low += 2*len) {
        int mid = Math.min(low+len-1, n-1);
        int high = Math.min(low+2*len-1, n-1);
        merge(arr, low, mid, high, tmp);
      }
    }
  }

}

堆排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/jwmBqnW8

截图:

代码:

public class HeapSort {

  private void swap(int[] arr, int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }

  // Time: O(log(n))
  private void siftDown(int[] arr, int i, int end) {
    int parent = i, child = 2 * parent + 1;
    while (child <= end) {
      if (child+1 <= end && arr[child+1] > arr[child]) ++child;
      if (arr[parent] >= arr[child]) break;
      swap(arr, parent, child);
      parent = child;
      child = 2 * parent + 1;
    }
  }

  // i 从 end/2 开始即可,因为在二叉堆中,更大的 i 是没有子节点的,没必要做 siftDown
  // Time: O(n)
  // Reference:
  // * https://www.geeksforgeeks.org/time-complexity-of-building-a-heap/
  // * https://www2.cs.sfu.ca/CourseCentral/307/petra/2009/SLN_2.pdf
  private void buildMaxHeap(int[] arr, int end) {
    for (int i = end/2; i >= 0; --i) {
      siftDown(arr, i, end);
    }
  }

  // Time: O(n*log(n)), Space: O(1)
  public void sort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    buildMaxHeap(arr, arr.length - 1);
    for (int end = arr.length - 1; end > 0; --end) {
      swap(arr, 0, end);
      siftDown(arr, 0, end - 1);
    }
  }

}

计数排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/XOp19ap2

截图:

代码:

public class CountingSort {

  // indexes 最后存储的是排序后,相同数字在结果数组的开始位置,相同数字会依次向后(右)填充。
  // Time: O(n+k), Space: O(n+k)
  public void sortLeft2Right(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int max = arr[0], min = arr[0];
    for (int num: arr) {
      if (num > max) max = num;
      if (num < min) min = num;
    }

    int k = max - min;
    int[] indexes = new int[k+1];
    for (int num: arr) ++indexes[num-min];

    int start = 0;
    for (int i = 0; i <= k; ++i) {
      int count = indexes[i];
      indexes[i] = start;
      start += count;
    }

    int[] tmp = new int[arr.length];
    for (int num: arr) {
      int idx = indexes[num-min];
      tmp[idx] = num;
      ++indexes[num-min];
    }
    System.arraycopy(tmp, 0, arr, 0, arr.length);
  }

  // indexes 最后存储的是排序后,相同数字在结果数组的结束位置,相同数字会依次向前(左)填充。
  // Time: O(n+k), Space: O(n+k)
  public void sortRight2Left(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int max = arr[0], min = arr[0];
    for (int num: arr) {
      if (num > max) max = num;
      if (num < min) min = num;
    }

    int k = max - min;
    int[] indexes = new int[k+1];
    for (int num: arr) ++indexes[num-min];

    --indexes[0];
    for (int i = 1; i <= k; ++i)
      indexes[i] = indexes[i] + indexes[i-1];

    int[] tmp = new int[arr.length];
    for (int i = arr.length-1; i >= 0; --i) {
      int idx = indexes[arr[i]-min];
      tmp[idx] = arr[i];
      --indexes[arr[i]-min];
    }
    System.arraycopy(tmp, 0, arr, 0, arr.length);
  }

}

桶排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/VBpL2omD

截图:

代码:

public class BucketSort {

  private void insertionSort(List<Integer> arr) {
    if (arr == null || arr.size() == 0) return;
    for (int i = 1; i < arr.size(); ++i) {
      int cur = arr.get(i);
      int j = i - 1;
      while (j >= 0 && arr.get(j) > cur) {
        arr.set(j+1, arr.get(j));
        --j;
      }
      arr.set(j+1, cur);
    }
  }

  // 每个桶的大小,由于桶内使用插入排序,因此桶的大小使用一个较小值会比较高效。
  //
  // 一般来说,当处理的数组大小在 5-15 时,使用插入排序往往会比快排或归并更高效。
  // 因此在桶排序中,我们尽量让单个桶内的元素个数是在 5-15 个之间,这样可以用插入排序高效地完成桶内排序。
  // 参考链接:https://algs4.cs.princeton.edu/23quicksort/
  // 参考段落:
  // Cutoff to insertion sort. As with mergesort,
  // it pays to switch to insertion sort for tiny arrays.
  // The optimum value of the cutoff is system-dependent,
  // but any value between 5 and 15 is likely to work well in most situations.
  private int bucketSize;

  public BucketSort(int bucketSize) {
    this.bucketSize = bucketSize;
  }

  // Time(avg): O(n+k), Time(worst): O(n^2), Space: O(n)
  public void sort(int[] arr) {
    if (arr == null || arr.length == 0) return;
    int max = arr[0], min = arr[0];
    for (int num: arr) {
      if (num > max) max = num;
      if (num < min) min = num;
    }

    int bucketCount = arr.length / bucketSize;
    List<List<Integer>> buckets = new ArrayList<>(bucketCount);
    for (int i = 0; i < bucketCount; ++i)
      buckets.add(new ArrayList<>());

    for (int num: arr) {
      int idx = (int)((num - min) / (max - min + 1.0) * bucketCount);
      buckets.get(idx).add(num);
    }

    int idx = 0;
    for (List<Integer> bucket: buckets) {
      insertionSort(bucket);
      for (int num: bucket)
        arr[idx++] = num;
    }
  }

}

基数排序

视频链接:https://algocasts.io/series/sorting-algorithms/episodes/q2m595mz

截图:

代码:

public class RadixSort {

  /**
   * @param arr  待排数组
   * @param bits 每次处理的二进制位数(可选值:1, 2, 4, 8, 16)
   * @param mask 每次移动 bits 个二进制位后,使用 mask 取出最低的 bits 位。
   */
  // b 表示每次处理的二进制位数
  // Time: O(32/b * n), Space: O(n + 2^b)
  private void sort(int[] arr, int bits, int mask) {
    if (arr == null || arr.length == 0) return;
    int n = arr.length, cnt = 32/bits;
    int[] tmp = new int[n];
    int[] indexes = new int[1<<bits];
    for (int d = 0; d < cnt; ++d) {
      for (int num: arr) {
        int idx = (num >> (bits*d)) & mask;
        ++indexes[idx];
      }

      --indexes[0];
      for (int i = 1; i < indexes.length; ++i)
        indexes[i] = indexes[i] + indexes[i-1];

      for (int i = n-1; i >= 0; --i) {
        int idx = (arr[i] >> (bits*d)) & mask;
        tmp[indexes[idx]] = arr[i];
        --indexes[idx];
      }

      Arrays.fill(indexes, 0);
      int[] t = arr;
      arr = tmp;
      tmp = t;
    }
    // handle the negative number
    // get the length of positive part
    int len = 0;
    for (; len < n; ++len)
      if (arr[len] < 0) break;

    System.arraycopy(arr, len, tmp, 0, n-len); // copy negative part to tmp
    System.arraycopy(arr, 0, tmp, n-len, len); // copy positive part to tmp
    System.arraycopy(tmp, 0, arr, 0, n); // copy back to arr
  }

  // 基数为 256,每次取 8 个二进制位作为一个部分进行处理,32 位整数需要处理 4 次。
  // 每次取出的 8 个二进制位会作为计数排序的键值,去排序原始数据。
  // 每次处理 8 个二进制位,是时间/空间上比较折衷的方法。
  // 如果一次处理 16 个二进制位,速度会稍微快一些。但需要额外的空间是 2^16 = 65536,远大于每次处理 8 个二进制位所需空间。
  // 如果一次只处理 4 个二进制位,速度则会慢很多。
  public void sort4pass(int[] arr) {
    sort(arr, 8, 0xff);
  }

  // 基数为 16,每次取 4 个二进制位作为一个部分进行处理。32 位整数需要处理 8 次。
  // 时间上比起 sort4pass 要差很多。
  public void sort8pass(int[] arr) {
    sort(arr, 4, 0x0f);
  }

  // 基数为 65536,每次取 16 个二进制位作为一个部分进行处理。32 位整数需要处理 2 次。
  // 时间上比 sort4pass 要稍微好一些,但额外要使用多得多的空间。
  public void sort2pass(int[] arr) {
    sort(arr, 16, 0xffff);
  }

  // 基数为 2,每次取 1 个二进制位作为一个部分进行处理。32 位整数需要处理 32 次。
  // 时间上比快排要差很多。
  public void sort32pass(int[] arr) {
    sort(arr, 1, 1);
  }

  // 基数为 4,每次取 2 个二进制位作为一个部分进行处理。32 位整数需要处理 16 次。
  // 我是打酱油的。
  public void sort16pass(int[] arr) {
    sort(arr, 2, 3);
  }

}

从一期播客说起

2025年8月13日 08:00

最近作客「捕蛇者说」,录了一期播客。万万没想到,主播 laike9m 为此还写了篇文章,叫 我最喜欢的一期《捕蛇者说》。作为一个友善的人,我觉得我也应该回应一篇文章。我愿把这种行为叫做「创造者之间的礼尚往来」。

既然决定坐下来写篇文章,那就写长一点吧。

我是一个年更博主,最新的一篇公开的文章《一个独立创造者的五年》是 2023 年 7 月发表的,然后我仿佛就从网络上消失了。这句话不是我说的,是我最近随机漫步在 w2solo Products 群里突然说了一句话,一位群友说的。他说,Hawstein 你消失两年了吧。这句话不完全对,因为我偶尔也在推特上发点什么,还发起过一次孤独开发者吃饭活动。

不过整体上来说,过去两年我在网络上确实不怎么活跃。我的主要活动在线下,在生活,在养娃,在做产品。这不仅不奇怪,甚至说这才是主流,大部分人的生活在线下,N 年也不会在网上说一句话。

那么,我怎么又突然在网络上「炸尸」并且竟然还录了一期播客呢?因为我在我的生活里卡住了。我开始怀疑我做的事情,感觉像是陷入「存在主义危机」。

觉知到这一点后,我决定停下来。脱离原来既定的轨道,做点不一样的事情。正如 2019 年的文章《人生不可 DP,但别永远贪心》中所写的,我决定给目前的生活添加一点随机扰动。我找朋友长谈,整理以前的读者来信,重读以前喜欢的书,陪太太一起看电视剧(一般情况下我不看电视剧),疯狂听播客,在社交媒体上表达自己的困惑,还有分享自己写的一些读者回信。// 也许我也该学习 61 刻意去玩玩游戏

然后 laike9m 就在推特上 dm 我,问我要不要上播客随便聊一聊。仔细想想,这种「连接」最是恰到好处。为此,在播客录制结束后,我还发了一条推:

最近处于一种不想做产品的状态,于是决定做点产品以外的事:比如找朋友聊天,整理读者来信,发推。这股强烈的表达欲,被 laike9m 捕捉到了,然后就收到他的私信,约我到播客聊天。

对于播客邀约我一般持谨慎态度。《一个独立创造者的五年》这篇文章发布后,我收到不少播客邀约,其中还有我挺喜欢的播客。但写完那篇文章后,我确实没有太多表达欲了,所以当时的播客邀约我都婉拒了。不是因为别的什么,电台很好,主播也很棒,就是那个时期的我真的没什么想说的了。

而最近表达欲溢出,正好被另一个人捕捉到,在和主播确认了对于我不想谈的内容可以不谈后,就欣然接受这次在线闲聊了。这次闲聊大概 2 小时,非常愉悦,全程 in the flow。最后是因为我这边家人在等着我去吃饭,所以我不得不下线了。不然的话,我感觉以当时的状态,应该还能再聊 2 小时。

我和 laike9m 说,最近我处于一种不想做产品而想找人聊天的阶段,不早不晚你正好就 reach out 了,这种感觉就像是缘分到了。时机正好,水到渠成,我喜欢这种感觉。

这一期播客我们没有提前准备什么,没有列提纲,也没有说固定要聊什么主题。我的心态完全是,我要去和两个朋友聊天,这一次与平时的区别就是要把这个过程录下来。

对话的过程非常享受,录出来的效果也很不错。GeekPlux 评论说「即兴的总是最好的」;孟岩则在 E10 让万物穿过自己 这一期播客中说:好的内容是生长出来的,不是计划出来的;而 Naval 则在 Conquering the Mind 中表述为:

I find that when I’m speaking, I do best when I’m not thinking about what I’m going to say and I won’t even hear what I’m going to say until it comes out of my mouth.

当然,我觉得,「即兴的总是最好的」这里的「最好」是针对参与者本人的最好。即,只与自己比。同样是即兴,我与孟岩和 Naval 可能还差了很远。

不过话说回来,当我回去听这一期播客,能发现许多不太完美的地方。因为听播客的时候,更多的理性与思考参与了进来。

录制播客时,我无法进行过多的自我审查。因为我们处于对话当中,我不能在每次说话前都停顿几分钟对要说的话进行审查。在这种多人对话情境下,自然而然地,我会想让对话流畅地进行。而想要对话流畅地进行,我几乎不能进行过多的思考,而是让本来在我脑里的东西蹦出来。因此,在播客中的表达往往是「不完美的」。录制完以后去听,往往会觉得,嗯,这个地方我可以举一个更好的例子;那个地方我可以给出一个更好的回答,等等。但正好是这种「不完美」的形式,这种想要保持对话流畅性的倾向,让所有参与者跨过「表达障碍」,进行了表达,不管表达水平是高还是低,内容是精彩还是无聊。录制播客,可以让所有参与者都完成表达。而「完成」对表达来说非常重要。

同样的内容,如果以写作作为表达方式,可能就死在「草稿箱」里了。非常惭愧,我的草稿箱里有许多这样「未完成」的表达。而「未完成的表达」在我看来,就等于没有表达。

从这一点看来,播客真是一种很好的表达方式。

话说回来,这期播客只是我脱离既定轨道后,所做尝试的其中一件,我还会继续其它探索。最后就以孟岩在 E30 让奇迹发生 这一期播客中的一句话来结束这篇文章吧:

人生是旷野,不是所有的游荡都意味着迷失。

P.S. 如果你还没听这一期播客,可以去听听 👉 小宇宙链接捕蛇者说链接

Plan 200 完结后做什么

2020年1月13日 08:00

近期 AlgoCasts 完成了网站上 5 个系列(https://algocasts.io/series),共 211 个算法视频的制作。算是一个小小的 Milestone 吧,接下来会做以下几件事情:

  • 录制 Plan 250
  • 录制专题:好玩的数据结构
  • 制作极简题解
  • 开发神秘 Chrome 插件(不要问,问就是否认三连:我不是/我没有/别瞎说啊)

录制 Plan 250

视频制作仍然会是 AlgoCasts 的主要工作,根据近期收到的反馈,接下来的算法视频中,会提高以下两类题目的录制频率:

  • 动态规划
  • 贪心

录制专题:好玩的数据结构

之所以要录制这个专题,是由于在讲解算法题目的过程中,有的算法需要用到一些高级一点的数据结构。但如果在讲解题目的视频中,花大篇幅讲某个通用数据结构,会有以下问题:

  • 喧宾夺主。(讲算法题目的视频就应该专注讲题目,数据结构是辅助工具)
  • 通用知识耦合到一个解决具体问题的视频中。

因此,我决定做个单独的专题来讲解这些好玩又好用的数据结构。

制作极简题解

如果说算法新手需要一个详尽的视频来讲解每个算法题目,那么对于已经有一定经验的小伙伴或者说基础本来就不错的小伙伴,一份类似 Cheat Sheet 的极简题解,就会是一份更加称手的学习资料或复习资料。即使对于同一个人,前期看视频讲解,后期通过 Cheat Sheet 复习,这种组合学习法,也往往会比单一学习法更加高效。

开发神秘 Chrome 插件

目前没有更多信息可以透露。不要问,问就是否认三连:我不是/我没有/别瞎说啊。

Indie Hacker 笔记 | 第 14 期

2020年1月8日 08:00

又到了码字的时间,这一次的心情很轻松。我想主要原因是,「AlgoCasts 每月小结」系列的完结,带来一个新系列的开始。人总是喜欢新的开始,正如新年给人以希望一样。新系列的名称定为「Indie Hacker 笔记」,由于它的前身「AlgoCasts 每月小结」已经出了 13 期,因此「Indie Hacker 笔记」就从第 14 期开始(关于新系列的命名考虑,文末会给出解释)。

AlgoCasts

距离上次小结已经过去一个多月了。这期间,终于把 AlgoCasts Plan 200 剩余的视频录完。至此,目前网站上 5 个系列共 211 个视频也就全都完结了。昨天把最后一个视频发布出去后,心情有点像来到了一个马拉松补给站。停下来喝点水,吃点东西,然后望望后面的路,想想接下来怎么跑得更好。小伙伴们也开始问我接下来的录制计划,这个在接下来几天之内应该就会发布到论坛公告上。时间过得可真快,不经意间,AlgoCasts 已经上线一年多了。而这一年多,我的工作生活模式也发生了很大的变化,遇到了不少有意思的人和事,感觉也差不多是时候写个跨度大一点的回顾了:)2020 年,我仍然会把大部分时间和精力放在 AlgoCasts 上,感谢一路陪伴与支持我的小伙伴们!除此之外,可能还会稍微分出一点时间来,开发一个小产品。这个小产品我已经构思挺久了,不过去年一直克制自己不要轻易开新坑,希望今年它能顺利面世。

德语

圣诞节后,我们把德语课的频次从一周一节提高到了一周两节。主要基于两个考虑,一个是多留点时间给我准备德语考试,二是德语老师在三月底要去维也纳进修他的语言学专业,我们想在他离开瑞士前把课程上完。到目前为止,我觉得德语学习的效果还是不错的。加之每天就生活在一个讲德语的城市,在学校里,在超市里,在车上,在路上,满目可见的都是德文,耳濡目染,进步也就快一些。我时不时会把这些生活中看到的德文拍照保存,然后再翻译学习。也经常为能看懂生活中某处出现的德文,为能听懂旁人聊天中出现的一些词汇或句子而小小高兴一下。在外买东西时,我也总是优先使用仅有的一点德语和对方交流,直到对方突然说出一个生词或是一个陌生的句子,使我露了馅,对方才会意并礼貌地切换成英语。这样有趣的例子可以说不胜枚举。零基础开始学习一样新东西的好处就是,进步是以一种可见的速度在发生的,毕竟起点太低了,越往后提升就越困难。不过关于德语学习,我还不需要考虑到「往后」这个阶段,毕竟还有段距离。

阅读

12 月看完了一本书,来自 James Clear 的《Atomic Habits》。这本书是从别人推荐书的推文回复里看到的(最近书架上至少有 4 本书是从别人的推文中挖掘出来的,我管这叫 steal from the tweets),于是在网上看了看评价后果断下单。书挺薄的,每天通勤路上看,很快就看完了。我觉得这本书写得还不错,书中倡导的「Build Your System」让我联想到福柯的「自我技术」。简言之就是,你的外在或内在系统,都可以由你精心设计,设计成适合你自己的系统,让你在这样一个系统内沿着理性为自己设定的路径前行,并且阻力尽可能小。整体上来说,我还是挺推荐去看一看这本书的。我有不少观点和书中观点一致,但如果让我写,现阶段应该没办法像作者写的那么好。关于输入和输出,我自己有一个理论,就是你永远只可能输出你所有输入的一个很小占比。所以,为了写一本还不错的书,为此要储备的知识与素材,要远多于书中所呈现的东西。当然了,输出垃圾不受这条理论约束:)

顺便提一句,关于「Build Your System」或者说「自我技术」,目前我了解到的人中,Stephen Wolfram 可以说是做到了极致。Stephen Wolfram 是计算机科学、数学、理论物理方面的著名科学家,是 Wolfram Research 的创始人,Mathematica 的首席设计师,《一种新科学》的作者。多年来,他一直在 hack his personal infrastructure。关于这些,强烈推荐阅读一下他写的长文:Seeking the Productive Life: Some Details of My Personal Infrastructure,相信你会获得一些启发。

App

最近在朋友的推荐下,开始使用一个 App 来记录时间,叫「Now Then」。一开始我对这个 App 并不抱多大预期,但在用了一小段时间之外,竟然发现意外地简单好用。如果你像我一样有记录时间的习惯,可以考虑下载来使用一下。关于记录时间这件事,最早我是从「奇特的一生」这本书看到的。这本书讲述了主人公「柳比歇夫」是如何使用他的时间统计法,对他做的事情以及花费的时间进行记录和研究的。后来我就开始探索适合自己的时间记录方式,也简单搜索过相关的辅助工具,不过都没遇到特别好用的。所以后来我干脆就用 Wunderlist + Evernote 的方式手工记录。我记录时间的方式不像「柳比歇夫」那样严苛,主要是围绕工作以及一些比较花时间的事情,碎片时间一般不记。这个不经意的习惯已经跟随我多年,目前我觉得带来的好处是,除了知道自己大量的时间花费到了哪里,对时间的敏感度也提升了。另外,在开始做一件事前,我会预估一个时间,做完后再记录下实际使用的时间,进行对比。如果这件事是需要经常反复做的,一般经过一段时间调整后,时间预估就可以达到一个比较准确的程度。

旅行

旅行方面,12 月份去了趟布达佩斯。在把所有细节都忘记后,目前我脑里关于布达佩斯的记忆就剩下:好看的建筑,好吃的美食,舒服的温泉。吃的方面,无论是食材的丰富程度还是做法的多样性,都比瑞士要高出不少。当然了,如果你是从国内出发去布达佩斯,请忽略我关于美食的那句评价。一月份还有两次出行:米兰和湾区。来瑞士后,出行变多了,所以最近还在思考以及需要解决的一个问题就是,如何在出行期间不打断我的某些 routine。所幸,我工作或生活中做的大部分事情,都不太需要依赖很「重」的外部条件。因此,大部分的事情即使在旅行中,也一样可以做。目前唯一的影响可能是我的录音设备不太方便携带,因此目前我决定出行期间就不录音,转而撰写/积累视频素材,或是做产品开发。(其实,非要携带上那台小小的 Blue Yeti 倒也不是不可以,最近我在推上还看到 Marques Brownlee 在去 CES 期间,把一整套非常专业的录音设备布置到了他入住的酒店里,为了消音,还把被子/毯子挂到了房间的墙上。虽说他是专业做这行的,但我也不禁小小感慨了一下,很多事情只要你想做,自然就会有办法。)

结语

前段时间接受了一个科技媒体采访,其中有一个问题的回答我自己还挺喜欢的,就用它作为这一期的结束语吧。问题是:工欲善其事,必先利其器。无论是工作还是生活,有什么特好的“磨刀法子”是你巴不得大家都知道的?面对这个问题,我第一反应并不是想到要分享什么「利器」,因为我发现当代社会影响我们「善其事」的首要因素,往往已经不是我们手中的「器」了,而是别的东西。以下是我的回答:

删掉抖音以及其他同类产品;把绝大多数的微信群(或者其他 IM 群)都设置成消息免打扰;所有的 App 都默认禁用通知,除非你认为这个通知重要得不能禁用;把所有的 IM App 都放到二级文件夹,或者移到首屏之外。

这个世界太嘈杂,嘈杂得想好好安静下来做一件事都特别困难。办法只有主动去关闭一些与世界的连接。相信我,这个世界大多数时候都只是在发出噪声而已,关闭一些连接的好处远远大于坏处。

完。

关于新系列的命名考虑

我受国外 Indie Hackers 影响挺大的,并且从 2018 年辞职成为一名 Indie Hacker,而我写的东西一般都围绕我的工作和生活展开,所以很自然地就想到用「Indie Hacker 笔记」这样的题目了。

为什么这个系列的名字要中英混用呢?因为至今我也没在中文世界找到 Indie Hacker 的合适对应词。Indie Hacker 直译是「独立黑客」,但中文里「黑客」的定义过于狭义了,刻板印象总会觉得「黑客」一定是和计算机或编程相关,但 Indie Hacker 覆盖的范围比这要大得多。事实上,我觉得在英文世界里,hack 这个词的泛化程度已经和中文里的「搞」有得一拼了。几乎什么都可以 hack:hack your life, hack your mind, hack your project, hack your iPhone…自然地,Hacker 的含义也就不再仅仅是一群搞计算机和写代码的人了。关于 Indie Hacker 这个词,indiehackers.com 的创始人(也是提出这个词的人)在 About 页面是这样描述的:

You’re an indie hacker if you’ve set out to make money independently. That means you’re generating revenue directly from your customers, not indirectly through an employer. Other than that, there are no requirements! Indie hackers are often solo founders, software engineers, and bootstrapped, but it’s totally okay if you have cofounders, can’t code, and have raised money.

由于实在没有合适的对应词,于是就决定保留英文原词了。另外再附上 IndieHackers 网站的一个相关讨论帖子:

[Discussion] What is an Indie Hacker?

AlgoCasts 2019 年 10 月小结

2019年11月30日 08:00

现在是瑞士时间 11 月 30 日晚上 9 点多,还有几个小时就 12 月了,踩在 11 月的尾巴上,得赶紧把我的每月小结写一写。这篇可能是「AlgoCasts X 年 Y 月小结」系列的最后一篇了,所以嘛,先来首淡淡忧伤的音乐定定基调:Flightless Bird, American Mouth。

So,是 AlgoCasts 要倒闭了么?这个倒没有,事实上 AlgoCasts 第一年的表现已经超出了我的预期,今年的营收目标也在 9 月份的时候就已经达到了。那为什么「AlgoCasts X 年 Y 月小结」要停了呢?Hmm,这个嘛,因为在这个框架下我的写(扯)作(淡)才华发挥不出来。事实上,每月围绕 AlgoCasts 写小结都让我绞尽脑汁。因为,每月围绕 AlgoCasts 做的事情其实是很简单的,就是录制算法视频。就算我一个月出 100 个视频,我觉得也没什么可写的。因为它的事件类型其实是单一的,就算我写出花来,说的也是我在做视频,对不对?嘛,自由职业者的生活就是这么朴实无华且枯燥:)因此,当我回顾过去一年写的 AlgoCasts 小结,除开做视频,我都会尽量在一个月里做点功能开发、搞点营销活动,这样写起小结来就还能扯点别的。不过最近几个月我已经不怎么做网站功能开发了,因此,我的AlgoCasts 月小结真的就要没有素材可写了。

当然,这个月我还是做了一次营销活动,非要写的话,整个 800 字,再升华一下随手谈谈人生,对我来说也不是什么难事。但我已经有点倦了。每月小结最主要是写给我自己看的,我要按我喜欢的方式来写。因此,「AlgoCasts X 年 Y 月小结」这个系列要停了,但是,一个涵盖范围更大,讨论主题更广的每月小结将就此产生。Surprise!

以后的每月小结将不会再只谈 AlgoCasts,而会是过去一个月工作/学习/生活中值得记录的内容。当然,能升华的我还会继续升华 233。毕竟生活中不只有柴米油盐,还有智人透过柴米油盐 YY 出来的哲学道理。由于 AlgoCasts 会继续占据我生活很大的一部分,因此每月小结肯定还是会有它的身影。不过,再次谈起来,我希望可以更感性一些。而不是冷冰冰的本月录了 X 个视频,修改了 Y 份简历,做了 Z 场模拟面试。听起来就很冷,缺少温度。

那么,「AlgoCasts X 年 Y 月小结」的最后一篇是不是还得守好最后一班岗,交待一下本月做的事情呢。其实上面我就已经说了:录了 X 个视频,修改了 Y 份简历,做了 Z 场模拟面试。哦,还搞了次促销活动。

你看,自由职业者的生活就是这么朴实无华且枯燥:)

完。

AlgoCasts 2019 年 8 月小结

2019年9月28日 08:00

每月小结又拖出新高度了,看了眼 Wunderlist 和 Google Calendar,过去一个月可以概括如下:搬新家,装家具,修电器,做视频。网站方面加了个简单的 EDM 功能,大家肯定没兴趣听;做视频呢,也没什么好讲的。那就简单讲讲生活上的事情吧。本月的小结音乐来自人称西班牙版彭于晏的帅气男歌手 Álvaro Soler:「Sofía」。

过去一个月最主要的事情就是搬进了新租的房子。在瑞士,一个多月就找到房子并入住,应该算是比较快的了。我们租的是不带家具的房子,所以跑了几次宜家,才慢慢把家具买全。然后把安装家具这件事分摊到过去一个月,隔三差五安装几件,包括安装大件的家具和钻孔装灯。在国内,这些都花钱雇人搞定。但考虑到这边人工比较贵且我们对这边还没那么熟悉且德语还达不到自由交流的地步,所以就自己动手了。在瑞士,一般上一个租客走的时候会把他的家具都清理掉,包括灯(除非你和他提前商量好要保留)。所以我们刚搬进来时,客厅、走廊和几个房间的房顶上都只有几根裸露的电线=。=作为一个经常吐槽自己生活经验为 0 的人,在房顶上钻孔装灯对我来说还是比较困难的。。不过在安装了一两个灯找到感觉后,就越来越顺手,可以说是指哪钻哪,钻哪装哪。

除了自己安装家具,租来的房子里有几件电器坏掉了。于是开始了和本地 3 家公司的打交道过程,分别是 Service 7000 AG,Clean Up AG,Baumann Koelliker AG。每次预约上门修电器时,机智如我都是快速用英语介绍一下自己,希望对方知道我并不会讲德语。然而这并没有什么用,对方还是一定会用德语回答我。然后我会再显式地问一下对方,会不会讲英语。这基本上成了我在这边接打电话的基本 Pattern 了。Service 7000 公司的工作人员大多会讲英文,无论是电话里的客服,还是上门维修的师傅。烘干机是 Service 7000 上门维修的,换了一个中控板子,一次搞定。洗碗机就没那么幸运了,目前已经上门维修 3 次了,第 4 次约在国庆节后。橱柜灯目前上门维修 2 次,第 3 次也约在国庆后。它们分别由 Clean Up 公司和 Baumann Koelliker 公司负责,这两个公司的人员基本上不会讲英语。他们一般上门后,一句 Hello 打过招呼就开始干活。有什么问题也是直接用德语问我,然后我通过当时的上下文和他的肢体动作猜他在说什么,再用英文回答他。我估计对方会使用同样的方法猜我回答的是什么=。=当然,关键场合还少不了 Google 翻译,对方会把一些重要的信息在手机上用 Google 翻译后给我看。比如说,是哪个部件坏了需要换。

工作上,如果看过我上个月小结,就知道我目前是去 ETH 办公。又过了一个月的时间,目前可以说是轻车熟路了。我自己对比了一下之前在北京咖啡厅办公时的状态,整体上在大学里办公的状态比咖啡厅里还要更好一些。随着搬入新房子慢慢安顿下来,这个月视频产出的数量也慢慢恢复上来了。希望这个状态可以保持下去。

过两天就是国庆长假了,预祝大家假期快乐!

启程,新的冒险

2019年7月24日 08:00

时差,好像已经倒过来了。

现在是瑞士时间晚上九点多,天空才慢慢要开始暗下去。这两天我和小柳熟悉了一下临时住所周围的环境,逛了逛附近的超市,看看我们接下来一个月的食材有哪些;办理了临时居住证,去银行开户,预约时间去移民局办理 Permit。接下来最主要的 TODO 就是在苏黎世找到一个满意的房子(希望顺利),毕竟要在这住一段比较长的时间。

就这样,我们开始了在瑞士的生活。

把时间拉回 2018 年一月底,那个时候我和小柳正在瑞士度过我们不长不短的 10 天婚假。10 天时间,我们从瑞士北部玩到了南部,印象深刻,流连忘返。那一次瑞士之旅,让我们喜欢上了这个国家。于是时常想,以后要是有机会,一定要在这里生活几年。没想到一年以后,小柳工作上有机会可以内部转岗到苏黎世,而我也已经自由职业了一段时间,工作上不受地点限制。所以在看到这个机会时,我们的第一反应就是决定去。当然了,有第一反应就会有第二反应。当时我们才搬进新买的房子,在装修上费了好大劲自己 DIY,因此住起来也算舒心满意。小柳在北京的工作性价比很高,还能在业余时间做一下喜欢的甜品。而我刚开始自由职业,做的事情算是刚有起色,正需要一段安稳的生活,好把更多的注意力和精力放在这件事情上,增加产出。另外,我们还计划明年要小孩。还有在国内的父母和朋友们等等考虑…这第二反应仿佛在说,哟,年轻人,不要瞎折腾,在北京好好过日子。

可是,不折腾,还是我么?

我经常思考死亡这件事情。一想到人生短短不过百年,然后就要掉进死亡的深渊,生活中那些看起来聪明的盘算,就显得那样微不足道;而那些谨小慎微与步步为营,就显得那样没有必要。当然,我并不想把自己推向虚无主义,不想说因为我们终将死去,就否定活着时候的一切。我只是常想,死亡就像一座山峰,站在它上面去看问题,我们会有一个很不一样的视角。那些原本看来优劣差别特别大的选择,在这个视角下,可能并没有什么差别。你选 A 很好,选 B 也很棒。站在这样一座山峰上,我们会有更好的视野以及更开放的心态,「为什么要」变成「为什么不」,「不可以」变成「可以」,「否」变成「是」。站在这样一座山峰上,我们更能抛开普世价值观强加在一些事物上面的标签,追随自己的好奇心与直觉,去探索、去发现。

所以,如果你问我,为什么要来瑞士折腾。我的回答是,为什么不呢?

我们喜欢这个国家,现在有机会可以来这里生活,于是就来了。相当简单的逻辑。至于来到一个新国度开启一段新生活会遇到的困难与问题,一一面对并解决就是了。当然了,长居和旅游不同,在瑞士生活一段时间后,有两种可能的结果。一种可能是,整体上来说喜欢生活在这里,那我们就待久一些。另一种可能是,整体上来说并不喜欢生活在这里,那我们就少待些日子,有合适的机会就回北京。但如果选择不来瑞士,在我们的脑中就有一千种可能:如果当初去了瑞士,可能会怎么样怎么样…抑或是在未来的某一天,突然感叹,曾经有一个机会摆在我面前,我没有珍惜。

这几天,在这个陌生的国度,开始学习许多新东西。从选择哪家移动运营商/银行到选择什么交通方案/套餐;从弄清各种连锁超市特点到现学各种日常生活德语;从学习近乎严苛的垃圾分类到几乎每顿饭都亲自下厨(并且做得还不错)…总的来说,新生活的开始,感觉还不错。

希望接下来的旅程也一切顺利,好好享受这段新的冒险。

❌
❌