哑铃片的组合有几种,以及二猫的编程解决

一副哑铃,有可替换的哑铃片 0.75kg,1.25kg,2kg,2.5kg每种各4片。可以组合出哪些重量?

1. 我哥给我一副哑铃

回老家,我哥给我一对可调节哑铃。

之前我有6.5kg*2kg哑铃一对,用来练深蹲、单侧或双侧俯身划船,农夫行走和土耳其起立。份量有点轻,但是没有更重的。还有3kg*2哑铃一对,是邦哥带我去迪卡侬买的,用来练肩。他说,不用太重,你感觉侧平举不太费劲就对了。一直用到现在,刚刚才有一点觉得轻,但是没有刚好稍微重一点儿的,因此没有进步的意愿。还有2对1kg哑铃,俯身或俯卧练肩后侧或肩胛,有时希望稍微重一点点点。

我哥给我的这对哑铃可调,共16片哑铃片,0.75kg,1.25kg,2kg,2.5kg共4种,每种各4片。哑铃杆2根,每根1.1kg。

我装配出8kg*2哑铃,当天晚上就练了一轮肱二头肌。果然比6.5kg的要给力,略微有点过于给力。第二天晚上,第三天晚上,肱二头肌拉伸时有微微疼痛。还试了左右分别5组土耳其起立,也果然比6.5kg*2要难。我想,是不是应该降一下重量,比6.5kg高5~10%,这样成长曲线更平滑一些,痛苦能少点。

如果有个表格,能把这副哑铃所有能组合出的重量都列出,我每次查找下一个5~10%进步的选项就可以了。最大重量是每个哑铃13(+杆重1.1)kg,练肩的话,我今生也不会达到。总重五十多斤,抱着深蹲对我可见的未来也足够。

问题是,都有哪些组合呢?

2. 用EXCEL试试

凡事,先用最简单的工具试试。Excel最简单,各种计算,特别是简单场景的,无出其右者。有人可能觉得C最简单,或者觉得Python最简单。你得在发着低烧,要求速度的时候,看出错率和体力消耗。这才是简单。

图1 Virgin Python,惟真楼的巨蟒

Excel有求组合和排列的函数,就是英文单词 combin 和 permut 或变形。我刚一输入函数就觉得“不对”。一,这两个函数的作用是求组合和排列的数量,而我要的是列表,仅仅数量对我不够。二,相同重量的组合,对我而言可以算同一种。如何判断?Excel 是否能像 C++ STL 那样支持自定义的谓词呢?十有八九不能。这样对多数用户过于复杂,容易用错。况且,我对谓词的要求是判断求和相等,通用的谓词定义需要何种计算能力,是不是得图灵机等价,那么这里需要用VBA才行。

再查,Excel 365版本以后的,支持给出组合和排列的条目。我手头的版本过低,不知道有多低,不过,想想发行时间应该不足以支持。试了下 一个array类函数{=PERMUT(A2:A4,3)},又试了ctrl-shift-enter组合键,果然不能工作。

再往下就是用VBA或者某个特别的插件,进入奇技淫巧模式了。

3. 二猫出手

所以,这种活儿就应该用C语言。穷举,判断条件自定义。找二猫,问“我有这么个问题”。我没说“这事挺简单”,虽然不确定,但是隐约觉得还有坑。

二猫说“简单,能做。”

然后开始问我尚未细化的需求。俩哑铃是不是需要相同重量?每个哑铃杆有两端,这两端是否需要相同重量?相同重量,使用不同的哑铃片,比如 2 == 1.25+0.75,这算一种还是两种?我又一次体会到 需求难提,用人类语言尽述心中所想的难度,以及为什么会有《猴爪》这样的故事。

没多一会儿,程序就出来了。核心代码大致如下。

我说,输出改成这样的格式吧,这里加几个空格,那里加两个\t,这里乘以2,那里对齐。二猫体会到需求变更是工程中的常态,用户经常在看到输出效果以后才知道“那不是我想要的”;可能程序员最花时间的那部分,做完以后,用户说“这个不重要”,请删除。

最重要最复杂的业务逻辑完成以后,我和二猫不约而同地对剩余的工作采用了额外的工具,而不是修改程序。二猫写了段以map为准的代码,把数据吃进去再排出来,去除完全相同的重复。核心代码大致如下。

我用excel去除重量相同的重复,调整格式,分别得到重量列表和组合列表。

这样,我们得到了以下表格。能够看到相同重量有多种哑铃片组合方式。

双手两手相同重量,不去重复,最右列是哑铃片的组合。

单手,不去重复,最右列是哑铃片的组合。

4.赞叹精妙设计

观察上述两个表格,我发现,我并不太需要哑铃片的组合。只要告诉我这个重量是可行的/,对我来讲信息就足够了。有了可以得到这个信心,很容易算出组合的方式。就算我的口算能力一塌糊涂,也可以再根据想要的重量查上面的两张表格。

因此,我删除哑铃片的组合,去除相同重量的重复行,得到下述两个表格。简洁多了。

双手,杆两端平衡,按相同重量去除重复。

单手,杆两端平衡,按相同重量去重。

观察这两张表格可以发现,哑铃片重量的选择应该是精心设计过的。哑铃总重的增量,绝大多数是0.5kg。增加0.5kg对于提高训练难度,经常不是特别大,也不特别小,相当适合。个别增量是0.25或者1。

再回顾哑铃片的种类,0.75kg,1.25kg,2kg,2.5kg,如果使用分数是 5/2,4/2,5/4,3/4,有点意思。转换成以1/4的倍数表示,那么4种哑铃为 10,8,5,3。这几个数字看起来眼熟得多,隐约有人民币面值的种类,以及质数、合数之类的印象。好了,应该是精心设计过的。就到这里吧,赶紧去锻炼——莫等闲,锈了新哑铃。

你手腕疼吗?

与老学生相聚总是很愉快。和老老师相聚,能听到很多掌故,和老学生相聚,能听到很多成长的故事。最近老学生华哥来,戴了一对护腕,一问,手腕疼。没多久,群里,老学生权哥问,“有同学遇到过写代码时间长了造成手腕子疼的情况吗?我以前都不疼,最近换了工位后就开始疼了。”我哈哈大笑,真应景,华哥手腕疼来着。我也疼过。

原理,个人体会,以及解决方案如下。

1. 原理

腕伸动作,就是手背向前臂靠拢,如果用力,或者持续,都会导致手腕疼。打字时手腕不悬,长时间用鼠标,俯卧撑特别支撑手远离头而向腰的方向靠近,都属于腕伸动作。如果留有长指甲,为了手指肚敲击键盘而抬起手背,也会导致腕伸。

2. 个人体会 手腕疼痛和休整的案例

我以前手腕疼,所以戴非常松的护腕保暖,感觉好很多。这个救急非常有效,从1998年到2012年,我持续了15年。需要忽视别人的目光和劝阻才行。我用的是线儿衣(有些地方称为秋衣?),里面穿的衬衣 的袖子,松紧和长短都适合。色调是亮绿,亮蓝,在那个时期的照片中非常突出。有次上公开课,老老师们特意告诉我,需要把这护腕摘掉。我说,疼啊。他们说,忍一下吧。袖子长点,长到手背,也有点作用。

因为腰突,接受向龙同学的建议尝试悬挂在单杠上。引体向上从3个长到7个,没有多久。

对腰突的效果不清楚,手腕好了。我猜测悬挂姿势因为握力增加,以及习惯的发力角度更适合人类进化。

3. 解决方案

(1)锻炼。吊单杠,锻炼腕部肌肉和关节。感觉受力的角度很重要,以这样拉伸的角度锻炼。

(2)避免。避免腕伸受力或长时间腕伸。打字时悬腕。如果觉得累悬不动,那就应该歇会儿。少用鼠标,多用键盘。鼠标动作比键盘精准很多,格外累。俯卧撑用架子或者拳头撑在瑜伽垫上。打人的时候,侧面扇巴掌,不要掌心向前推。当然,不打人更好。

(3-1)手腕的休息、训练、恢复的注意事项,角度。无论腕伸、腕屈、向拇指侧倾、向小指侧倾斜、向尺骨侧旋转、向桡骨侧旋转,都保证只停留在最舒服的范围和角度内。不要在腕伸时立掌,不要在腕屈时达到蛇鹤什么拳那样突起手腕的手背一侧成直角。人类手腕侧向旋转的角度非常小,以舒服为界。如果某个角度疼,不要进入那个角度,或者反向用力。例如把东西甩在肩膀后面提起来这个姿势,可能导致腕伸时疼痛。用力向腕屈方向,由手背侧的关节受力改变为手心侧的肌肉受力,这样就不疼了。甩筷子之类的挥拍运动,末端不要放松手腕,保持一定程度绷紧。

(3-2)手腕训练时的原则,除角度要小以外,负重/力量要小。手腕附近的肌肉非常小,关节也小并且复杂,不会练出多大力量,也不应该有这样的需求。所以,抓握、旋转、屈、伸时,随便找根不太细的棍子。截面要圆的,以免不硌手。一只手的上臂提供的力量,足够训练另一只手的手腕了。负重要足够小,数量稍微多点就会酸。我猜测这就足够了。负重不要大,不会练出大肌肉的。并且,手腕,前端的末端和手连接的地方,那里没有肌肉,天生多细就是多细,想也白扯。

训练的角度包括 腕伸、腕屈、向拇指侧倾、向小指侧倾斜、向尺骨侧旋转、向桡骨侧旋转。再次感叹,这么复杂的关节,不应该承受太大的压力和时长。

(4)器械。

这根棍子,过于夸张了。单手握凳子腿做腕屈,更超过了养生锻炼的范围。至于单手握凳子腿做腕伸,如果你能,那么就不应该读这个贴子。家里有马勺的话,可以试试左手颠勺,如果你是右利手。颠勺所需要的力量,超出健康所需要的。超出,可以,为了超出而锻炼,一方面太难,另一方面可能受伤,不是好事。

猜测 正常人类,有三五根筷子足够。如果你能用腕力折断三五根筷子,那就是练得过于努力了。一根筷子,别折断,那个力量感觉正好。

4.补充

对华哥的护腕的评论:护腕紧的话手不过血,勒出印痒;出汗得洗。

权哥补充说:我发现座椅偏低,手打字时就会出现腕伸动作,时间一长手腕就疼。

当压缩包里的文件名是乱码

有过几次,同学发给我压缩包里的文件是乱码,看起来如下图所示。

进入第一个目录,看起来如下图所示。

如果想看解决方案,请略过中间,跳到文末。

猜测这位同学的机器是 MacOs,苹果的机器。据说,苹果系统对于国别和编码处理得格外优秀,因此在落后的Windwos操作系统上看起来是乱码。有同学建议过我换个苹果系统,不贵,用起来特别顺手,这个乱码的问题会不药而愈合。据说苹果的笔记本用起来特别舒服……但是,我一直不能适应巧克力键盘,更何况由于视力弱,所以我现在已经没有办法用笔记本了,所以这么大的优点对我没有意义,钱会白花。还有同学建议我用 winrar(似乎高版本?)而别继续用zip解压也能解决这个问题。

苹果系统还给过我一个印象,就是有位苹果用户发给我的图片尺寸特别大,上面只有几个字纸件的照片,就达到几十M字节。我深深为自己的带宽、日志和工程存储空间而自卑,也为此发愁。

有的同学发来二三百M字节的ppt或者word文件,往往里面有特别大个儿的一张超高高清晰度(全尺寸展示比你的屏幕还大好多倍)的图片,还有特别的字体完整嵌入。

有的同学打包用7z,告诉我这来自当前最好的压缩工具。

有的同学发送给我wps,告诉我这来自爱国的和正版的工具。

以上这些,我统统都能想办法打开,然后削减尺寸以后保存。同时我经常杞人忧天地为他们犯愁,万一哪天被官方当作垃圾把文件扔了该有多可惜。

我对我钟爱的工具也非常执着,但是范围仅限于我的机器。多数时间用 emacs 写作,但是发给编辑或同事或同学的时候,一向要转换为 word。因为猜测这是收件一方中的多数人最常用的系统。

近些年,情况略有变化。有的同学发给我md,还有短小精悍或长篇大论或口齿清晰的或吐字不清的微信语音。有的同学发来截图的字体极难辨认,不是手写,胜似草书。我有时候说 不能听、打不开、不识字,有时候不吱声想办法查看,然后在心里祝福他以后不会被官方以打不开为由而丢掉文件。

说回压缩包里的乱码。最简单的方法是请对方换个方法重发一次,如果对方是计算机系的学生还好,不然万一他不会,你教他怎么操作是件相当繁琐的事。附,我的经验是,一定要拒绝远程解决任何故障。如果他有能力描述故障,他就十有八九有能力解决。如果他不能解决,十有八九他也说不清楚。如果你不知道故障的现象,能解决才怪——他会还觉得你笨,这都解决不了。

解决压缩包的乱码,再就是换个压缩软件,嗯,或者换操作系统和机器。在机器上装winrar触发了我的洁癖,所以不行。我机器上刚好有 Java运行环境 JDK,你的机器差不多也有,很多人的机器上都有。JDK里有个 jar.exe,常用它运行java的jar包文件。

Jar.exe能解压。Java对国别和编码处理得好,这一点也很著名。

鼠标单击资源管理器的地址栏(红框处)。

输入 CMD,回车,弹出命令行窗口,当前目录是资源管理器的地址栏所在目录。

在命令行下按如下命令操作。

Jar xvf 文件名

或者这样。

jar xf 文件名

解压完毕。

验证,所有文件名都不再显示为乱码了。

感谢 MWW同学 和 先帝创业未半而中道崩组 提供的示例。我收到过不少压缩包中文件名乱码的,妥善保存、容易找到、近期的就是这个。

FTP 502 故障解决一例

ftp服务器设置强制tls连接以后,ftp客户端报告502错误并连接失败。本文给出解决这个故障的一个例子。

1 故障现象

我平时通过 VPN 使用 FTP ,以为从端到端足够安全,因此用明文(Plain FTP(insecure)此处有警告标志),未用加密连接。同事尾巴巴G老师告诉我,口令被侦听到了,我在心里发了个惊叹号。想了一下,被侦听的路段应该是从 VPN 服务器到 FTP 服务器之间。所以,最要防范的恰恰是内部人。或者换个角度,如果黑客攻破到 VPN服务器以内,我的口令就暴露在扫描范围之中了。

希望能更加安全,因此我设置 FTP 服务器只接受加密 TLS 连接。这样,从我的 FTP 客户端到 FTP 服务器 会有基于 TLS 的端到端加密,因此口令不会被侦听到。

在 FileZilla FTP Sever的管理器中,我这样设置,强制用户登录使用TLS。

图1 服务端:强制TLS

从 FTP 客户端发起连接失败,现象如下,错误号502。

图2 故障现象 502

2 不好的解决方案

502的含义是什么?

根据RFC,https://www.rfc-editor.org/rfc/rfc959 ,"502 Command not implemented",即命令没有被实现。没实现的是什么命令?就是上一条我的 FTP 客户端发出的 "AUTH TLS",要求用TLS鉴权。更具体的信息在上面这个截图里有,在当前情况,是"Explicit TLS authentication not allowed",不允许TLS。

我不是刚刚配置了要求必须用TLS吗?

在网上搜索了一下,最前面几条给出的解决方案居然是 取消强制 TLS ,或者在 FTP 客户端上强制用非 TLS 即明文 plain FTP 连接。这相当于,为了安全,我在门上加把锁,用钥匙拧一下没打开。咋解决?方案是把锁头卸了。

所以,不靠谱的不仅AI而已,搜索引擎、博客,是否能作为依据,是否能提供帮助,还得自行判断。

3 解决方案

猜测在 FTP Server 里有什么配置没有做,还是理论不够通晓。同时,感慨,图形用户界面的配置真是麻烦啊,配置一共10个大类,小类还没算,大类小类里每面又很多条目。在 Unix/Linux 习俗中,还有 windows的ini中,是个文本文件,虽然对入门用户不太友好,但是搜索起来真是方便啊。

我遍历了所有配置,找到了。在这里。

图3 服务端:允许TLS + 禁止不加密连接 + 私钥

启用了 FTP over TLS,顺便禁止了不加密的 plain FTP。为了 TLS 能够工作,需要私钥,生成一个。这样,当 FTP 客户端第一次连接的时候,会问你要不要接受公钥是什么什么的服务器,需要同意。

我在 FTP客户端设置加密的选项为 (如果可用)使用TLS。

图4 客户端:支持TLS

现象表明,502故障解决,连接建立成功。

图5 故障解决

总结,FTP 强制 TLS,需要设置的地方有
图1 服务端强制TLS;
图3 服务端:允许TLS + 禁止不加密连接 + 私钥;
图4 客户端:支持TLS。

博客地址

https://younggift.net

https://www.zhihu.com/column/younggift

微信公众号 杨贵福

我和我学生背单词的7年岁月

有一天,我们决定开始背单词的这个游戏。之所以称为游戏,是因为要有奖励。

因为时间久远,日志回溯困难,微信聊天记录、红包记录、图片,都需要翻很久。记录到头了,从上下文看还不是开头。再转换成其他方法再回溯。经过艰难查询,找到了起始那一天。

这是我查到的咱们单词打卡游戏的第一次红包,那么,游戏的开始应该是2017年10月6日。奇怪,这天是假期,怎么会聚餐呢(后来换手机查到了,第一天是2017年9月1日)。不过,我确定记得是从某次聚餐开始的。印象里,是个周五的组会。我们在餐桌上聊起来背单词的事,不止一位同学正在用百词斩、扇背、墨墨背单词。谁提议的,忘了,我们确定以下规则。

每天背单词,在群里打卡。在这一周结束的时间,由教师我在群里发红包。如果参与者在本周没有任何一天遗漏,可以领红包。如果有任何一天遗漏,要在第二天在群里发红包。

使用的背单词软件不限,每天背多少单词不限,选哪本教材不限。

组会在以后的岁月换过不止一次时间,并不总是周五。能记得定下契约的时间是周五,因为我发红包的时间总是周五。这是我们每一周开始的日子,在这一天对上一周背单词回顾总结。

当时是不是所有同学都参加了?不记得了,应该是大部分。因为每天群里打卡蔚为壮观。最初我们都经常在午夜以前突然群体出动,纷纷打卡。也有人在午夜过后把第二天的卡先打出来。

既然是游戏,那参加是自愿的,绝不强迫,也不会对不参加的同学有负面评价。字面意思,并非阴阳。后来的同学,开始的几个届,研究生进组以后我会讲一下这个游戏,询问是否加入。再后来,发现师兄师姐们早就给新生讲完了,我仅需要按人数把红包的数量增加了就行。

为什么红包的数量是增加而不是增减?因为游戏并无退出机制,并且老学生在毕业以后,仍然在打卡领红包的也有。有的参与者从最初入盟就已经是毕业了的老学生。

起初,同学们加入时已经背了十几天,几十天。又一年,后来的新生加入时就已经有几百天的。

最近的记录是1626天,2916天,2118天。从最初参加游戏的元老,以及后来逐渐加入的各位新进,我们已经共渡2402天合343个星期。

在这其间,有过本组以外的老师、同学、前辈、同事觉得这种形式非常好,希望加入。我都拒绝了,因为我们打卡使用的是研究生的讨论群,每天怼人被怼不断,不足为外人道也。

XL同学有个亲戚群,组织了一群小孩天天英语打卡,他发红包鼓励。很多年过去,成果显著,有优秀人才成长起来了。

7年来,我们的打卡也在逐渐变化。过一了段时间,有同学不向群里打卡了,把打卡移到了朋友圈。再后来,看不到有的同学在任何地方打卡,但是仍领红包,一问,他能提供打卡满勤的截图。只有G同学明确说过,我不玩了。我说那么你差多少星期的打卡,得我们红包啊。他说,想想怎么办。后来不知道想得如何了,不过他送了我们一个大书架子,缓解了我们把书堆得到处都是的问题。也许这就是想的结果,不过他没有明确声明。

与G同学相比,其他同学的处理更为隐含。又过了很久,有的同学既不打卡,也没见领红包。在这个游戏里,失去了他/她的消息。

就这样一周一周过去了。我已经不关心大家的状态了,只是按当初的规则或者说契约执行。每周五发红包,然后自己领了。如果我偶尔差一天,就只发红包,不领。今年1月份,Z同学说,老师你别发红包了,红包只有咱俩在领。我说奇怪啊,没有人说退出游戏。有同学晒了打卡的记录,但是没有同学晒领红包的记录。

本周五,因为忙于俗世,我忘了发红包。回顾检点了几次红包,发现只有我一个人在领。这就是成年人的拒绝吧。我当年还相信吐口吐沫就是钉,后来这些年才知道大家可能对契约的记忆差异很大。当初我们没有立有文字,现今想拿着条款找同学们核对已不可行。

在群里宣布:以后不发红包了,游戏结束,感谢陪伴。

人类动机的表达经常含糊隐晦,再加上经常诸多掩饰,实在难以猜测。大家可能觉得心有灵犀、显然如此、不必多说、何必言明,因此渐行渐远、突然消失,按习俗,不宜深切追究,唐突询问。猜测无端,只能由他去吧。

好在,我们总是能不断换成新的身份继续相处。当年的学生,毕业后变成老学生。有的成为师弟师妹,有的成为同事,有的同为计算机领域的工程师。还有的会成为上级。也有的虽然我们从事的领域完全不同,也并不影响就共同知道的话题聊天扯淡。

以上,就是对我和我学生背单词7年岁月的总结。

AI的姓氏:与长春市十一高中学校同学的对谈

2024年3月29日,在长春市十一高中做科普讲座《AI的姓氏:与长春市十一高中学校同学的对谈》。以下是PPT,以及现场没有完整播放的suno作曲和演唱伴奏的歌曲。

已知错误:

1. 现场的同学不只高一的,还有高二的同学。现场同学已经纠正过了,在PPT中保留了这一错误。

所见:从冬天路滑到春天的公交车

1. 冬天路滑

看我拄棍,一位大妈说,"这天你登山?"

我说,"就是防摔。"

我走得急匆匆的,时速7公里左右,所以只来得及对话一句。

看我跑步赶公交,一位大爷说,"别摔着。"

怕摔着我头都没敢抬,想点个赞也没来得及。

2. 雕塑,孔子和苏格拉底

校园里的雕塑,孔子和苏格拉底分别携众弟子各据一方。孔子的学生,有一人鞠躬,另有一人面对教师,一人对着教师弹唱,两人交头接耳。苏格拉底(?)的学生,没有一个看老师的,看看都干啥呢,两两聊天的,自己看书的,站没站相佝偻着,靠椅子扶手的。那个看书的,脚都拿上来了,是不是抠脚丫子呐,啊?

学生们距离教师全两米以上,都偏好后排座位。这两位老师啊,唉。

3. 买葱

前一阵某天是个节,冬至吧,要包饺子,去买葱。老板娘正讨论吉林和缅甸电信诈骗间的关系,"不然他没事到咱们这儿来?你要大葱还是小葱?"

"不知道啊。"

"是不是包饺子?小葱,对,就那么一把就够了。四块。"

扫码付款。

"你是不是给了10块。四块,哪有那么贵。"

找了我现金。

我说,"你要不告诉我都不知道。"

"那我多亏心呐。"

4. 大吉普

一辆特别高大的白色大吉普车向左停在路边,没熄火,女司机下车,敞着车门。

前面不远的洗车小伙可能做了什么手势,她喊回去“不洗车!”

对面又喊了一句什么,轿车声音大我听不清。司机喊“停这咋地?”

这时我走得近些了,隐约小伙的声音,“警察贴条,这几天好几个了”

“我乐意!”她走向路边的店铺,车门和引擎仍然开着。

5. 春天,公交车

等公交车。

啪一声,是个瓶盖扔到马路上。银行保安大哥扭头看。一个姑娘的背影,挑染短发,蓝白牛仔裤,一只手扔瓶盖还没收回来,另一只手举起小半瓶饮料往嘴上凑。吨吨吨。啪踏,瓶子甩到马路牙子上,剩的底儿溅出来。头也不回,义无反顾,潇洒得像电影里向火药库甩出打火机的特工。全程动作行云流水,人已经绕过公交站点消失了,保安大哥好像还在犹豫要不要喊她。

一个胖小孩,"打车吧,这得等到什么时候"咬字很清晰而奇怪,哪里的口音呢,听不出来。"你出钱?"回应的是爷爷吧。奶奶说,"你买的……也养不活啊,现在还不是时候。等六月……我会在院子里养,不会在屋里养。"小胖子一个劲重复"有卖的有卖的有卖的",也不知道是在反对"不是时候,养不活"还是再重申诉求。

等了六分钟,公交车到了,上车。

坐对面的一位女士特别娇滴滴地语音,像是用鼻子说话:妈我跟你说我太厉害了。有个人特别气人,居然……我曝光他让大家出来谴责,才知道他……你说我是不是特别厉害?

一个小伙梳着马尾坐我旁边,一声不响地看着语音的女生,好像藏着笑意。

路过的公交站点广告是结婚通告,人名像真的。最近看到还有在报纸上通报结婚的宣传广告。感觉像鲁迅李大钊的时代。

公交车上有人打呼噜,但是都睁着眼睛,谁呢。大家你瞅我我瞅你。谁在逗乐呢吗?要下车时才找到,一位老爷子带个口罩,埋头在别人身后,刚刚挡住了。

下车。人来人往,有穿羽绒服的,有穿光腿神器的。有的走得急匆匆的,有的扭成各种姿势拍照,拍照的比被拍的扭得更投入。树叶还一片也没有,冰雕和积雪也不见了,只有黄草和枯叶。路边蘑菇雕塑之下积了一摊水。春天要来了。

仓鼠的速度

养了一只熊类仓鼠,每天晚上疯狂跑轮。据我熬夜的时候观察,长达三四个小时连续不断。轮子有轻轻的嗡嗡声,小爪子打在跑轮的内壁上,嗒嗒嗒嗒。据二猫查资料,仓鼠每天要跑8.4公里,不然不舒服。据说有人录了视频,他家仓鼠跑了5个多小时。

那么,时速多少呢?我想实测一下。

1. 技术方案的逐步退化

最初考虑,在跑轮上贴张小纸片,当跑轮带动小纸片切断光源时,红外传感器检测到转了一圈。用树莓派,或者用 arduino 记录红外传感器的信号,然后显示在LED上并且上传到手机APP或者小程序上。最后一步,数据由树莓派上推到服务器,然后从APP或小程序周期下拉。

又考虑,这得把树莓派翻出来,arduino放哪了?得在PC机上安装开发环境,好几个G,只能干这个,没啥别的用处。

要不,不用树莓派或arduino,换成PC机?

红外传感器上行咋接PC?把红外传感器换成摄像头?那么,需要 OpenCV,也许训练AI识别小纸片?

摄像头,手机就有……

总之,从捕捉跑轮转动这个动机出发,考虑用手机,手动,一次性的解决方案。如此对付。

2.速度

求速度的话,可以从视频信号中抽取出仓鼠腿的移动。这样 步频 可以从视频中观察 每秒小腿儿倒腾的次数 得到,步幅,可以在整理箱或跑轮上粘贴坐标纸作为背景。但是如果亲眼看到仓鼠跑轮,就会放弃这个想法。因为小腿倒腾得实在太快了,在频闪50Hz的日光灯下看不太清楚。通过手机的摄像头看,有虚影。仓鼠无影脚。

所以,准备采用上节中提到的通过跑轮间接测量。

(1)跑一圈的位移

跑轮直径标称25cm。我把手掌张开比划了一下作为交叉验证,大致差不多。根据圆的周长公式可以求得周长为 25*3.14=78.5cm。仓鼠每驱动跑轮转一周,虽然事实上并无位移,简称 跑一圈,行进距离计为78.5cm。以下为跑轮大致侧视角度,直尺用于作为参照物,其量程不够测量25厘米。

(2)跑轮转速 跑轮每转一圈所花费时间的测量

在跑轮的外圈表面上用马克笔做明显标记,称为 mark。我打了3个加号“+”,排成一行,与跑轮的轴平行。以下为跑轮的顶视角度。

把手机摄像头近距离对准mark,开启视频录制功能。在仓鼠奔跑的中间阶段,速度较为稳定时,开始录制,十几秒后停止。保存视频。以上,录制多次得到多个视频文件。

把上述视频用 anlink 传到 PC 机,以下在PC机上操作。

用视频编辑工具 shotcut 打开视频文件中的一个。

在filters 中 选择 timer 加入。之所以选用这个过滤器的原因,是因为 shotcut默认的时间戳为 xx:yy:zz 格式,其中最后一个冒号后为30进制,而不是60进制,需要换算。我在最初使用的时间不知道那是30进制,算了几次时间感觉不对,逐帧查看时间戳才找到原因。

逐帧向后翻,找到如下图所示,mark刚刚显露出来的时候。

在 output 的 track 上打标记。

同时,由于 filter | timer 的效果,时间戳会显示在视频的当前帧上。

为每个要计时的时刻按如上方法打标记。把所有标记抄写到 excel 中,求出每两个时间戳的时间差。

求时间差的平均值 0.895333333 秒。

以上表明,仓鼠跑轮周圈0.9秒左右。不到1秒,25厘米,一“拿”多长的的跑轮,就要转一圈。快不快?

(3)速度

根据上述数据 周长 78.5cm,转速 0.90秒左右,求得仓鼠速度为

=3.14*0.25/0.895333333=0.876768429 米/秒。约每秒跑将近1米。

合 3.156366345公里/小时。比我快速徒步6公里/小时或5公里/小时略慢,比我漫步时还要快一些。现在如果持续三四个小时,我不知道自己能不能走到3公里/小时这么快。

(4)关于精度和误差的讨论

我最担心的是测量精度不够,特别是亲眼看到仓鼠的小腿跑出虚影的时候。在跑轮上与在地面上不同,仓鼠的脚没有与地面保持相对静止的那个时间段,脚一直在随着跑轮的转动而移动,所以更难测量。当然,在地面上有另外的困难,可能需要镜头拉远,才能看到仓鼠的脚不止一次落地。

先做了个可行性的技术原型,用手机摄像头在暗光下,人手动转跑轮,速度差不多与仓鼠转得在相同数量级,能够明显看到mark标记在镜头里出现。那么,差不多能测出来。

大约1秒钟跑轮转1圈。1秒钟,手机视频采样30帧,因此测量的时间间隔,即时间分辨率为 1/30 约0.033秒。上述测得的每圈花费时间0.90秒左右,考虑到时间分辨率,应为0.90-0.03~0.90+0.03,即0.87秒~0.93秒。

以上是对测量精度的考虑。

测量误差我的考虑有问题。从跑轮正上方拍摄,计时的时机是mark出现在边缘,还是mark到达正中间?mark在正中间附近时,线速度在视野中投影的值 比 在边缘要 大。因此,相同的mark位移误差时,mark在正中间对应的时间误差更小。所以mark在正中间可以测得更精确的时刻。但是我测量的时间没有考虑到这一点。后来又考虑到,多次测量取平均值,在累加的过程中会把这一误差消除掉,所以保留了这一方案。

(5)小结

仓鼠奔跑每秒跑将近1米。

2.步频

仓鼠跑起来嗒嗒嗒,每秒多少步呢?求步频。想起来我练跑步的时候,tiger和zhumao分别告诉我,不要那么大的步子,对心脏压力太大,小步倒腾,倒腾的频率要高。仓鼠深得其中精髓。

(1)原理

观看仓鼠奔跑侧面的视频,同时听声音,可以知道仓鼠的两只前脚仅负责支撑,并且产生的声音较小;仓鼠两条后腿后蹬是主要动力,并且会产生“嗒”的一声。

我们测量这些嗒声音之间的时间间隔。

(2)音频

用 ffmpeg 把上述视频转为 mp3格式。

>ffmpeg -i 1.mp4 1.mp3

(3)测量嗒声的时间间隔

用 audacity 打开得到的mp3文件。

上图中,较亮颜色为选中的区段,3个红色箭头所指的,就是发出嗒嗒声的位置。因为声音轻微,所以看起来振幅不明显。上图中右下角是来自窗口下边缘的截图,需要注意的是选中部分时长为0.294秒。

可以看出,大约0.3秒,嗒声3次。即每0.1秒跑出一步。

为了现象更明显,对声音做 normalize ,得到下图。大约0.5秒,共5次踏步。

能够看出/听出,在每次嗒的时候,波形较密集地穿越了零点,即频率较高。转换成频谱可以看到,此处有几个与其他部分相比频率较高的峰。

再看一段,0.3秒左右3次踏步。

(4)小结

每0.1秒跑出1步,合每秒10步。

3.步幅

步幅可以用 速度/步频 大致算出。

0.9 (米/秒) / 10(步/秒) = 0.09米/步

9厘米!每步9厘米这么大的步子。耗子全身抻直了大约12厘米也没有多长啊,一步这么远么?

考虑到仓鼠行动时在跑轮上,而不是在地面上。所以,此时的步幅为每次跨步跑轮转过的边缘长度长,与地面上每两次跨步间的地面距离不同。人类在跑步机上和在地面上的跨步、步频,还有能耗,也是不同的。由于在跑步机上不需要推动身体,仅需要每步保持抬起身体,据说更容易一些。不过,仓鼠确实向身后以线速度推动了滚轮,转动惯量转换为线速度再与仓鼠的质量相比,不知道哪个更大,差多少。

4. 总结

周长接近1米的跑轮,仓鼠每1秒钟转1圈,这1秒钟1圈仅10步,每条后腿迈步5步。

无论速度1米/秒、步频10步/秒、步幅10厘米/步,都远比我原以为的要厉害。真快啊!每天看它跑个不停,我都觉得人生美好、积极向上、阳光开朗,它可真是享受生活。

 

用AHK脚本实现滴答清单每天任务自动推迟

技术路线:使用AHK脚本,通过图片搜索功能实现,使用了AHK的函数。

你是不是也有这么个习惯?不停地把新任务加到 todo list 中,只来得及完成紧急的,重要的任务,凡是不能半小时完成的,就越推越久直到清单变得特别长,长到甚至不想去看一眼。

我也经常这样。有不少同学问过我,为什么看起来我这么激情洋溢、乐观向上。因为我在朋友圈和博客里只展示激情洋溢、乐观向上的一面。推迟任务是常有的事,因为时间或者精力不够。任务推迟很久的,我就取消掉!说明即使我不完成天下也不会大乱。一般地,文章我不当时看,而是加到列表里。列里很长,闲了的时候就找出一篇来看。偶尔有还没来得及看的文章已经失去了链接、作者(被迫?)主动删除的,那就算了,也不特别地惋惜。每天,如果计划清单太长,看起来不可能完成那么多,我就删除最古老的那些,把计划看的文章移到爱好阅读或专业阅读列表里。诸如此类,眼不见心不烦。

每天,我还都会做一个操作——在“滴答清单”PC版里,昨天未完成的任务,已经被系统标注为过期的,我手动把它们标注为计划今天完成。这个动作已经持续了不止一年,这两天终于觉得DRY原则应该贯彻一下。AHK的语法忘得光光,边查边写,花1.5小时写了脚本,第二天又花半小时优化了一下代码,完成以后共72行。

过程如下。

1. 计划

干活之前,我先坐下来列个计划。

技术路线确定 用AHK的图片搜索功能。以前我用过这个技术,还写过一小段函数(有时好使,有时不)。滴答清单,操作时按的几个按钮,用AHK带的工具WindowSpy.ahk探测以后,都没有找到窗口句柄。没有稳定的句柄类,我了解的技术方案就只剩了图片搜索。这会导致限制屏幕的分辨率。不过,脚本只在我的机器上跑,基本不切换分辨率。对分辨率的限制可以忍受。

计划的功能,就是我平时的操作过程——由按列表排序改为按日期排序,然后点击 推迟,最后再改回按列表排序。操作涉及的按钮如下图所示。

列计划时预计得干一两个小时。如果实施中时间超过太多,那就得分割成几天完成了。

实际写代码计时,花了1.5小时,包括挣扎过想用函数,但是语法不熟悉而失败所花费的时间。

2. 运行的预置条件,检查 或 假设

程序/算法的特征之一是 可重现性。之所以有些程序不符合重现,十有八九是因为外部环境不同。所以,程序在运行之前,要保证环境(以及输入)是相同的。

我把AHK脚本中对预置条件的检查放在了初始化部分。

包括 分辨率,滴答清单进程的窗口激活/放在最上层、窗口最大化,在滴答清单中跳转到今日任务。

分辨率,粗糙地判断分辨率符合2560*1440,否则退出脚本。如果想增加通用性,并不需要修改这里,但是对每种分辨率都要单独截出所有按钮图标的图片。对分辨率的限制是由AHK搜索图片的机制决定的。

激活窗口并最大化,是为后面的鼠标操作创造条件。更严格地做法,应该在此之前禁用鼠标和键盘,避免用户误操作。由于我是唯一用户,主动运行而不是由时钟等外界因素触发,所以我可以做到不操作,不干扰脚本运行。更通用的做法,还可以包括把滴答清单的进程所对应的路径写成变量;更通用,还可以通过配置文件或GUI配置界面由用户修改。

在滴答清单中跳转到今日任务的方法,是发送快捷键。这不是滴答清单的默认配置,而是我的机器设置的。这里,假设AHK脚本运行在我的机器上。

以上,要么检测环境,要么粗暴地假设。粗暴的假设最省时间,检测、容错、适应各种环境越多越细致,工作量越大。所有这些检测都不是核心代码,而是核心代码工作的必要条件。有时,核心代码工作很好,有时,莫名其妙就崩了。并非核心代码有异,而是环境不同,或者如葛老师常说的,能运行的时候,还没出错,那不是技术,只是运气。工程和产品,运行别人的手里,不像原型只工作在受控的实验室环境下,需要巨大的工作量消耗在更多的非核心代码上。

往往,需要很多无聊换得些微快意,真是无耐。

3. 关键技术 语法

AHK的语法不熟悉,有点像我的母语basic,又不完全像。基本上每条语句都要查语法手册,包括内置函数调用、字符串、变量赋值这些基本操作,真是举步维艰。

用完就忘。

更糟糕地是,在网上查到的语法,即使是相当大的网站,仍然可能是错的。我猜测可能由于AHK的大小版本更迭,作者们对语法保持兼容性也不太执着吧。用bing搜索到的,比如(https://wyagd001.github.io/v2/docs/Functions.htm) 这种内容和目录看起来非常权威的,也是错的,或者与我的AHK版本不兼容。

更更糟糕的是,写代码语法出错时,AHK的编译器或解释器并不报错。它猜测我的意思就跑下去了,我也无从知道它到底怎么想的。反正结果出来了,不是我想要的。

最权威的,目前最好使的语法手册,是AHK自带的help文件名为AutoHotkey.chm。所以,如果你也写AHK脚本,别去搜索手册了,用本地的。有时候解决方案需要搜索网上的,也别全信,复制过来十有八九不能跑,版本不同,而且不告诉你。

更更更糟糕的是,我在这1.5小时代码中,一直想用函数。由于搜索图片若干次,操作类似,所以我想把这几步操作封装成函数。但是!不好使。我要调用,没有调用;我要定义,跑到了函数里面;没有报错,还是其他更明显的语法报错中给出一些AHK解释器执行的代码,我才知道它的理解和我的意图相差甚远。回忆起来,隐约有印象,每次我想用函数都不顺畅。极其偶尔成功过,我也并不知道为什么就成功了。

回顾本节标题,语法居然能成为关键技术,真是程序员之耻。

4. 关键技术 找图片

涉及到几张图片,我分别先用微信alt+a快捷键截图,保存成png无损图片,在AHK脚本中定义如下。

图片看起来如下。

之所以需要把变量提取出来,是因为类似 clock 这样的图标需要使用不止一次。根据DRY原则,根据经验,如果不抽象,那么在后续的修改中可能经常会需要修改不同的几个地方,稍一疏忽就漏掉一处——接着就是无穷无尽的不知道哪里出来的毛病。想起YP同学问过我,如果换成我,如果解决她遇到的问题。我当时说:由于咱俩的工作习惯不同,所以你遇到的问题,我不会遇到的。就像地上有个坑,你问我怎么跳出来。我不会跳进去的,离老远就看到,早早绕开。

搜索图片,在AHK中用法大致如下。

使用内置函数 ImageSearch,注意第一个逗号这种稀奇(我见识少)的语法。第1个参数X和第2个参数Y,用于输出,获取找到的图片的横纵坐标;X1,Y1,X2,Y2这4个参数是搜索的范围;最后一个参数%icon%中的百分号是必须的,一言难尽(我理解不深),是要搜索的图片,比如list.png。

但是!不好使呢。我在这句后面调用鼠标点击,鼠标不动。

此时,应该贯彻原则,不在项目代码中做技术原型实验。但是我自恃用过,觉得不会错,就在项目代码中做了实验。折腾了一圈,打印不出来图片的横纵坐标,空的;手动传参调用鼠标移动,好使。诸如此类。我终于想起来,在 ImageSearch 之后加了一条,

判断 ImageSearch 是否执行出错——包括有没有找到。没有,没找到图片。然而,我亲眼看到了图片在GUI界面上,又反复检查了截的图片文件 list.png。

查手册,AHK帮助说,用PrtSc截屏,粘贴到画笔中。

我突然想起来,这事儿,以前也发生过啊,不止一次。想起来当年关同学说:没有QQ,我怎么截屏啊。还得是基本操作。微信在截屏的过程中,压缩了吧,而AHK非常可能逐个像素判断的。也正因为这个,所以图片搜索依赖分辨率。如果支持多分辨率,就需要每个分辨率都截图(也包括滴答清单的不同皮肤,包括夜晚和白天不同),根据读到分辨率选择匹配的搜索图片。无他,遍历是最有效的方法。要么,就像我这次写的代码,粗暴地只针对我机器的分辨率。

5. 关键技术 函数

完成以上工作以后的代码相当丑陋,搜索6次按钮图标,每个点击一下,每段的代码看起来都是下面这样的。

因为我不会写函数,只能先这样对付着,6段非常相似的代码。能跑了。

第二天,我不甘心,又去搜索AHK的函数到底怎么写。搜索到的结果都表明,我写的对。但是运行起来的结果并不对。同时,我也不知道以前对的时候为什么就对了。错的时候倒是似乎好一点,虽然不知道为什么,反正大多数时候结果都是错的。

苍天不负苦心人,终于找到原因。从此以后不会再错了,也不会再对得不明不白了。

AHK 2.X版本手册中在程序员自定义函数中提到

https://www.autohotkey.com/docs/v2/Functions.htm,

"When a function is defined, its parameters are listed in parentheses next to its name (there must be no spaces between its name and the open-parenthesis)"

函数的名字 和 容纳函数参数列表的括号对儿 之间,不能有空格。

得这样写 foo(a,b)

不能写成 foo (a,b)

上述两写法的差别就只在 foo 的后面有没有空格。没有空格的才能正确运行。我不由得感叹:这什么烂语法,太不符合常规了。

蓦然一惊,这个,我是不是知道过,又忘了啊。

把6大段改成6次对同一函数的调用。

代码的逻辑清晰多了。运行前,
第1步.假设已按列表排序,所以点击列表图标;
第2步.改为按时间排序;
//以上是设置操作的环境
//以下是操作
第3步. AHK脚本点击延迟图标,滴答清单弹出对话框,
AHK脚本点击对话框中的延迟按钮;
//以上是操作

//以下是恢复操作的环境为原始状态
第4步.按列表排序

//以上是恢复操作的环境为原始状态

每两次鼠标点击之间 sleep 1秒钟,为了滴答清单有充分时间响应,并且我也能看到并享受自动操作的快乐。因为每两次操作都要sleep,所以这段代码也放到了ImageSearch_and_MouseClick函数之中。

6. 附录 代码

6.1 文件列表

ahk postpone.ahk

by_date.png

by_list.png

clock.png

list.png

postpone.png

postpone_button.png

6.2 代码

;;------------------

; icons

list=list.png

clock=clock.png

by_date=by_date.png

by_list=by_list.png

postpone=postpone.png

postpone_button=postpone_button.png

; init-------------

;; resolustion assumed

X1:=0

Y1:=0

X2:=2560

Y2:=1440

if (! (A_ScreenWidth == X2) || (ScreenHeight == Y2))

{

msgbox ,,, 需要显示分辨率为 2560*1440 才能正常工作。

exit

}

;; active window

Run, C:\Program Files (x86)\滴答清单\TickTick.exe

;; maximize window

Sleep 500

WinActivate, ahk_class HwndWrapper[TickTick.exe;;52329611-5419-4919-bf17-3295f24271e2]

WinMaximize , A

;; goto today-------------

SendInput ^h

sleep 500

;--- action seq---------------

;;-- in menu, find icon of list

;; *假设* default 排序图标是 list AKA "sorted by list"

ImageSearch_and_MouseClick(list)

;; sort "by date"

ImageSearch_and_MouseClick(by_date)

;; click "postpone"

if (ImageSearch_and_MouseClick(postpone) != 0)

{

ImageSearch_and_MouseClick(clock)

ImageSearch_and_MouseClick(by_list) ; 如果没有需要postpone的工作,此处恢复为 by_list

Exit

}

;; click "postpone_button"

ImageSearch_and_MouseClick(postpone_button)

;;-- in menu, find icon of clock AKA "sorted by date"

ImageSearch_and_MouseClick(clock)

;; sort "by_list"

ImageSearch_and_MouseClick(by_list)

Exit

ImageSearch_and_MouseClick(icon){ ; https://www.autohotkey.com/docs/v2/Functions.htm, "When a function is defined, its parameters are listed in parentheses next to its name (there must be no spaces between its name and the open-parenthesis)"

X1:=0

Y1:=0

X2:=2560

Y2:=1440

ImageSearch, X, Y, X1, Y1, X2, Y2, %icon%

if( ErrorLevel!=0 ) {

msgbox ,,, %icon% not found

return 555

}

MouseClick,% left, X+10, Y+10

Sleep 1000

return 0

}