周报 002

本周没有完全完成之前的计划,但是调整了新博客和旧博客。这周还收到了中国无线电协会业余无线电台操作证书。

提升公开讲话自信的三个技巧

“自”信 在进行其他训练之前,每个人都能做到的最简单的事情就是“自”信。 当出现小失误的时候,不要一直“抱歉”、“对不起”说个不停。当感觉大家都没有跟上思维时,不要说“可能大家都没听懂我在说什么”。自己发现问题后,自己把问题解决就好。出现失误,更正即可;表述不清,换种方式重新说清即可。 无论是接连不断的道歉还是反复强调自己没有让大家听懂,都不会帮助你解决问题,更不会帮助你的听众,反而会让你更加紧张或者自我否定,所以在公开讲话时不要这样做,要“自”信。 听众并非时刻注意你的一举一动 当进行公开讲话时,你可能以为听众都会把100%的注意力投入到你身上,以为听众会注意到你说的每一个字,以为听众会注意到你做的每一个动作。事实是,听众更可能会在自己的头脑里构建自己的理解,而忽略了你演讲的细节。 很多对你而言非常明显的细节,对听众而言,甚至会被全部忽略。这也是人们会反复强调重点的原因——为了不让听众错过重点——听众总是在忽略信息。知道了这一点,下次公开讲话时你会更加自信。 训练克服恐惧 正常情况下,每个人从小到大都在讲话,经过这么多年的练习,一个人的语言能力已经足以支撑其自如地讲话。每个人都至少能和某个人自然舒适地讲话,这个人可能是家人、朋友或伴侣,甚至可以是自己。 在公开讲话时不自信,更多的情况是思想没有跟上能力,具体一点就是不相信自己能在压力之下把话讲好。这种压力往往来自于对尴尬的恐惧。让自己多尴尬几次是一种解决办法。 可以这样训练:在一个安全的环境里——比如找上一些志同道合的朋友,大家都是一起进行训练的,所以并不会真的产生实质性损失——让自己处于各种可能出现的尴尬场景之中。可以让“听众”问你一些尖锐的问题;可以让“听众”“强迫”你把话讲明白,把句子说完整;还可以让“听众”在你讲话时打断你,使你不得不在讲话正中间冷场很长一段时间。总之,让“听众”想方设法使你尴尬难堪。多次进行这种训练,日积月累你会发现自己能处理好各种情况,在压力之下也能自信从容地完成自己的公开讲话。

Python PuLP 简单求解线性规划问题

一般步骤 导入 PuLP 使用 LpProblem 定义问题(名称、类型) 使用 LpVariable 定义变量(名称、下限、上限、类型) 添加目标表达式 添加约束条件 求解 输出结果 简单实例 问题 要用最低成本生产一种猫粮,同时使这种猫粮的营养成分满足一定的要求。生产者希望通过改变每种原料的添加量来实现这一目标。 每份猫粮为100克,其营养成分要求为:蛋白质不少于8克,脂肪不少于6克,纤维不多于2克,盐不多于0.4克。 猫粮的主要原料和其每克成本为:鸡肉($0.013)、牛肉($0.008)、羊肉($0.010)、大米($0.002)、小麦($0.005)、凝胶($0.001)。 每克原料对最终猫粮的营养贡献如下表所示,表中数据以克为单位: 原料 蛋白质 脂肪 纤维 盐 鸡肉 0.100 0.080 0.001 0.002 牛肉 0.200 0.100 0.005 0.005 羊肉 0.150 0.110 0.003 0.007 大米 0.000 0.010 0.100 0.002 小麦 0.040 0.010 0.150 0.008 凝胶 0.000 0.000 0.000 0.000 求成本最低的原料添加量方案。 简化版问题 先考虑简化后的问题,假设只有鸡肉和牛肉两种原料。原料的添加量不能为负值。 简化版问题求解 导入 PuLP from pulp import * 使用 LpProblem 定义问题 prob = LpProblem("The Cat Food Problem", LpMinimize) LpProblem 第一个参数为问题的名称,可自定义。第二个参数为问题类型,可在 LpMinimize 和 LpMaximize 中选择。本题求成本最小值。 使用 LpVariable 定义变量 x1 = LpVariable("ChickenPercent", 0, None, LpContinuous) x2 = LpVariable("BeefPercent", 0) LpVariable 第一个参数为变量的名称,可自定义。第二个参数为变量的下限,本题为 0。第三个参数为变量的上限,None 在上下限处表示正无穷或负无穷。第四个参数为变量的类型,可选的值有 LpContinuous, LpInteger 和 LpBinary,意思即为字面意思,默认为 LpContinuous。 添加目标表达式 prob += 0.013 * x1 + 0.008 * x2, "Cost per can" "Cost per can" 为标识符,可自定义也可省略。注意使用 += 而不是 =。 添加约束条件 prob += x1 + x2 == 100, "PercentSum" prob += 0.100 * x1 + 0.200 * x2 >= 8.0, "Protein" prob += 0.080 * x1 + 0.100 * x2 >= 6.0, "Fat" prob += 0.001 * x1 + 0.005 * x2 <= 2.0, "Fibre" prob += 0.002 * x1 + 0.005 * x2 <= 0.4, "Salt" 同样,引号中的内容为标识符,可自定义也可直接省略整个引号。注意使用 += 而不是 =。 求解 prob.solve() 使用默认求解器进行求解。 输出结果 print("Status:", LpStatus[prob.status]) for v in prob.variables(): print(v.name, "=", v.varValue) print("Cost per can = ", value(prob.objective)) prob.status 是求解的状态,为整数型,有 LpStatusOptimal, LpStatusNotSolved, LpStatusInfeasible, LpStatusUnbounded 和 LpStatusUndefined 这四种返回,意思即为字面意思。该值是整数型,可以通过 LpStatus[prob.status] 来获取字符串类型的状态。 prob.objective 是求解后目标表达式的值,在本题中即为所求的最低成本。 完整问题求解 求解的基本步骤是一样的,不过在使用 LpVariable 定义变量、添加目标表达式和添加约束条件时可以使用一些方法来减少需要编写的代码。 导入 PuLP from pulp import * 定义问题中的数据 Ingredients = ["CHICKEN", "BEEF", "MUTTON", "RICE", "WHEAT", "GEL"] costs = { "CHICKEN": 0.013, "BEEF": 0.008, "MUTTON": 0.010, "RICE": 0.002, "WHEAT": 0.005, "GEL": 0.001, } proteinPercent = { "CHICKEN": 0.100, "BEEF": 0.200, "MUTTON": 0.150, "RICE": 0.000, "WHEAT": 0.040, "GEL": 0.000, } fatPercent = { "CHICKEN": 0.080, "BEEF": 0.100, "MUTTON": 0.110, "RICE": 0.010, "WHEAT": 0.010, "GEL": 0.000, } fibrePercent = { "CHICKEN": 0.001, "BEEF": 0.005, "MUTTON": 0.003, "RICE": 0.100, "WHEAT": 0.150, "GEL": 0.000, } saltPercent = { "CHICKEN": 0.002, "BEEF": 0.005, "MUTTON": 0.007, "RICE": 0.002, "WHEAT": 0.008, "GEL": 0.000, } 为了简化后续所需编写的代码,在此把问题中需要使用的数据先进行定义。 使用 LpProblem 定义问题 prob = LpProblem("The Cat Food Problem", LpMinimize) 使用 LpVariable 定义变量 ingredient_vars = LpVariable.dicts("Ingredients", Ingredients, 0) 在这里我们直接使用先前定义的 Ingredients 列表来定义变量。 添加目标表达式 prob += ( lpSum([costs[i] * ingredient_vars[i] for i in Ingredients]), "Cost per can", ) 同样,在这里我们直接使用先前定义的 costs, ingredient_vars, Ingredients 来定义变量。 其中,lpSum 会把其参数列表的各项相加。 添加约束条件 prob += lpSum([ingredient_vars[i] for i in Ingredients]) == 100, "PercentSum" prob += ( lpSum([proteinPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 8.0, "Protein", ) prob += ( lpSum([fatPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 6.0, "Fat", ) prob += ( lpSum([fibrePercent[i] * ingredient_vars[i] for i in Ingredients]) <= 2.0, "Fibre", ) prob += ( lpSum([saltPercent[i] * ingredient_vars[i] for i in Ingredients]) <= 0.4, "Salt", ) 同上,使用预先定义的数据来简化代码。 求解 prob.solve() 输出结果 print("Status:", LpStatus[prob.status]) for v in prob.variables(): print(v.name, "=", v.varValue) print("Cost per can = ", value(prob.objective)) 参考资料 Optimization with PuLP(外部链接)

为移动硬盘禁用 UAS

使用 UAS 可能导致 smartmontools 等工具无法正常工作。可以通过编辑 GRUB 配置给内核传递参数,为某一特定设备禁用 UAS。 例如,编辑 /etc/grub.d/10_linux 文件,在文件首部添加 GRUB_CMDLINE_LINUX="usb_storage.quirks=idVendor:idProduct:u" 这一行配置,将 idVendor 和 idProduct 替换为设备的实际值。 idVendor 和 idProduct 可以通过 dmesg 获得。 比如下面的 dmesg 信息: usb 2-1: new SuperSpeed USB device number 8 using xhci_hcd usb 2-1: New USB device found, idVendor=0bc2, idProduct=231a usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [...] scsi host4: uas scsi 4:0:0:0: Direct-Access VENDOR-NAME 0204 PQ: 0 ANSI: 6 sd 4:0:0:0: Attached scsi generic sg2 type 0 sd 4:0:0:0: [sdc] 1953525168 512-byte logical blocks: (1.00 TB/932 GiB) sd 4:0:0:0: [sdc] 4096-byte physical blocks sd 4:0:0:0: [sdc] Write Protect is off sd 4:0:0:0: [sdc] Mode Sense: 53 00 00 08 sd 4:0:0:0: [sdc] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA sdc: sdc1 sdc2 sd 4:0:0:0: [sdc] Attached SCSI disk 从中我们可以得到 idVendor 为 0bc2,idProduct 为 231a。 编辑完 GRUB 配置文件,运行 update-grub 使配置生效。重启后,即可为 idVendor:idProduct 设备禁用 UAS。

网站使用 CDN 之后获取访客原 IP 的方法

网络上大多数教程给出的方法都是通过修改程序代码,直接取 X-Forwarded-For 等请求标头的值替换原来 REMOTE_ADDR 变量的值作为原 IP,这种做法有很大的安全隐患,我认为不值得提倡。 安全隐患 根据 RFC 3875 文件,REMOTE_ADDR 变量的值必须为发送请求到服务器的客户端的网络地址。在实践中 REMOTE_ADDR 变量的值往往由服务器直接设置为发送请求的客户端网络地址,而不是由客户端提供,因此其真实性有保证。 与 REMOTE_ADDR 变量不同,X-Forwarded-For 请求标头的值不是由服务器直接决定,而可以由客户端自行设置,实践中一般由代理服务器在转发请求时设置。因为 X-Forwarded-For 请求标头的值可能被客户端伪造,所以不应该轻易相信其真实性。 由于 REMOTE_ADDR 变量的值和 X-Forwarded-For 请求标头的值具有不同的可信度,使用低可信度 X-Forwarded-For 请求标头的值去替换高可信度 REMOTE_ADDR 变量的值便会产生不可忽视的安全隐患。比如,攻击者可以通过伪造 X-Forwarded-For 请求标头来绕过基于 IP 的频率限制、访问控制。更进一步,攻击者还可以构造具有危害性 X-Forwarded-For 请求标头来对服务器进行多种攻击,例如注入恶意代码进行 XSS 攻击就是一种可能的行为。 正确做法 在使用 X-Forwarded-For 请求标头的值之前应该确保其内容可信。因为 X-Forwarded-For 请求标头一般由代理服务器添加,所以至少应该先确认请求来自可信的代理服务器才可使用 X-Forwarded-For 标头的值。 对于使用 CDN 的网站,如果服务器是 nginx,那么在 ngx_http_realip_module 模块的帮助下这一过程将会十分简单。只需编辑相应的 nginx 配置文件,在其中的 http、server 或 location 段,使用 set_real_ip_from 指定可信代理服务器地址(即 CDN 节点的地址),然后使用 real_ip_header 指明一个请求标头(即 X-Forwarded-For 标头),保存并重载 nginx 使配置生效。这样 nginx 收到请求后会检查请求的 REMOTE_ADDR 是否与 set_real_ip_from 相符,如果相符,nginx 会用 real_ip_header 对应标头的值替换 REMOTE_ADDR 原有的值,后端程序只需要正常使用 REMOTE_ADDR 的值即可。这种做法一般不需要改变后端程序。 参考资料 RFC 3875: Section 4.1.8 REMOTE_ADDR(外部链接) MDN Web Docs: X-Forwarded-For(外部链接) Documentation of nginx: Module ngx_http_realip_module(外部链接)

看到亚马逊停止 Kindle 电子书店在中国运营的消息有感

对于 Kindle 中国区用户:从 2023 年 6 月 30 日起将不能购买新的电子书,但仍可下载此前已购买的电子书;从 2024 年 6 月 30 日起将不能从 Kindle 下载任何电子书,只可阅读设备上已经下载的电子书。 亚马逊的这次调整算不上突然。早在半年前,国内主要电商平台的 Kindle 就已经开始缺货,那时是 2022 年 1 月左右。虽然这次亚马逊的通知中提到每位用户可以在 2022 年 10 月 31 日之前退回至多 3 台于 2022 年 1 月 1 日及以后从国内授权经销渠道购买的至今没有质量问题及人为损坏的 Kindle 设备,但实际上能满足亚马逊退货条件的用户并不多。因为国内授权经销渠道在 2022 年 1 月之后基本都是缺货状态,所以大部分满足时间要求的用户都是从非国内授权经销渠道购买到 Kindle 设备的。 之前我就说过纸质书比 Kindle 电子书更可靠 ,不过那时并没有太多实例。这次 Kindle 电子书店在中国停止运营为我的“反电子书”观点提供了新的证据支持。不要简单地将这次事件视作一家国际公司的区域性运营调整。问题的根源不在这家公司,而在当前的电子书商业运作模式。只不过亚马逊在这个行业中一直扮演着“老大”的角色,所以问题最先从它身上暴露出来。我相信如果当前这种电子书商业运作模式不改变,那么支撑我“反电子书”观点的证据还会越来越多。

现在依旧偏爱纸质书的原因

在这篇文章里,如果没有特别说明,电子书均指亚马逊类的电子书。 抛开情怀、习惯等原因,在这个时代依旧偏爱纸质书的一个重要原因是纸质书比电子书更尊重人们的权利。 购买纸质书的行为可以匿名完成,而购买电子书则不行。在书店选好要买的书之后,只用支付现金就能把书带走。在电子书商城买书,除了需要付钱,还需要表明自己的身份(需要登录)。此外,虽然这本电子书已经被“买到手”,但是在阅读时读者仍然需要先表明自己的身份。 购买完一本纸质书,就拥有了这本纸质书;购买完一本电子书,却并不拥有这本电子书。纸质书可以送给别人、借给别人,还可以转手卖掉,电子书却不行。这是因为购买纸质书之后,购买者便对这本纸质书享有所有权,而电子书的购买者却不具有电子书的所有权。这一问题是现在电子书一系列问题的根源。 纸质书比电子书更可靠。这里的可靠不是指内容的可信度,而是指内容能被正常读取的可能性。使用加密格式的电子书需要通过专有软件进行读取,一旦软件公司倒闭、专有软件无法正常运行,电子书可能也就无法再被阅读。阅读纸质书就没有这种顾虑,因为哪怕出版社倒闭了,书也还在自己手里,还是能读。说到这里不得不提电子书公司曾经的行为:在 2009 年,亚马逊远程删除了许多用户设备中的乔治·奥威尔小说《1984》。如果当初这些用户购买的是纸质书,那么亚马逊断然不会闯进用户家中撕毁用户手中的纸质书。 实际上,不是电子书不尊重人们的权利,比如古腾堡计划的电子书做得就很好。电子书原来是没有这些问题的,但当电子书公司介入之后,问题便产生了。 在电子书公司转变这些令人不满的做法之前,对纸质书的偏爱将一直保持下去。

解决 MP4 视频无法在 iPhone 手机播放的问题

本文适用于解决因视频编码导致无法播放的问题。 前言 使用 iPhone、iPad 等苹果设备时会发现有的 MP4 格式视频可以正常播放,而有的却无法播放,在部分 Android 设备上也存在类似问题。大部分情况下,这类无法播放的问题都是因为缺少对应视频编码的解码器造成的,要解决这类问题,一种方法是下载能够解码该视频的播放器,另一种方法是对视频转码。本文介绍后者,即如何对视频进行转码使其能被大多数设备播放。 如果去搜索格式转换、视频转码等内容,会找到各类视频处理工具,其中大部分都需要付费或者含有许多广告。实际上,很多视频问题使用 FFmpeg 就能解决问题,这款免费、开源且无广告的工具也能通过转码解决 MP4 无法在苹果手机播放的问题。 下载 FFmpeg 如果你的电脑上还没有 FFmpeg 程序,你可以前往 FFmpeg 官网(外部链接) 下载最新版本的 FFmpeg 程序。 对于 Windows 用户,如果由于网络问题无法正常从官网下载最新版本,我在这里提供了一份副本,下载适用于 64 位操作系统的 FFmpeg n5.0 压缩包(外部链接) ,解压后即可使用命令行(CMD或PowerShell)调用程序。 对于 Linux 用户,大多数 Linux 发行版的软件仓库中都包含了 FFmpeg 程序,可以直接使用包管理器安装。例如 Debian 和 Ubuntu 用户可直接使用 apt-get install ffmpeg 命令安装 FFmpeg 程序。 执行命令进行转码 使用 FFmpeg 对视频转码,让视频获得最好的兼容性,只用把下面命令中的 <input> 替换为需要转换的视频文件名,然后执行即可: ffmpeg -i <input> -c:v libx264 -crf 23 -profile:v baseline -level 3.0 -pix_fmt yuv420p -c:a aac -ac 2 -b:a 128k -movflags faststart ksk-example.mp4 得到的 ksk-example.mp4 文件就是转码后生成的能够被大多数设备(包括使用 iOS 的 iPhone 和 iPad 设备)直接正常播放的 MP4 视频。 解释说明 现在问题已经解决。接下来我将进行一些解释说明,如果不感兴趣,可以直接略过。 H.264/AAC 的 MP4 是 HTML5 下兼容性最好的组合。对于没有 H.264 解码器的浏览器,则应该提供 VP9/Vorbis 的 WebM 视频。有这两种组合,基本上就可以兼容目前的所有主流浏览器。对于本文来说, H.264 编码的视频能够在大多数移动设备上正常播放,所以我们采用 H.264 编码。 用 -profile:v baseline -level 3.0 参数可以兼容一些无法处理 H.264 中 CPU 密集型特性的老设备,如果没有这个需求,可以去掉这一参数。 -crf 23 参数影响视频的质量,数值越低质量越好,一般设置在18到28这个区间内即可。如果需要指定视频码率,可以使用类似 b:v 1000k 这样的参数,这么做是因为一些性能不太强劲的设备可能会无法处理过高码率。 -movflags faststart 参数在流媒体播放的时候起作用,这个命令把视频容器的元信息移到文件头部。默认情况下(没有这个参数时),视频元信息在文件尾部,造成的结果是只有整个文件都加载完成后才能开始播放;有了这个参数,视频不必全部加载就可以播放。 更多详细信息可以查看 FFmpeg 官方文档。

Windows 10 添加小鹤双拼

Windows 11 也可以使用本文的注册表方案,我的视频 Windows 11 添加小鹤双拼(外部链接) 就是基于本文内容完成的。 Windows 10 内置的微软拼音是一款出色的输入法。微软拼音虽然没有内置小鹤双拼的方案,但是提供了自定义双拼方案的功能,允许用户自行添加双拼方案。用户只需要将音节和按键配对即可。 不过,如果有多台设备的话,这样逐一手动配置双拼方案就显得过于笨拙了。更简单的方法是修改注册表,直接导入已经配置好的双拼方案。 自定义的双拼方案保存在 \HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS 项中名称形如 UserDefinedDoublePinyinScheme0 (如果有多个方案,则名称的数值递增,如0、1、2…)的字符串值里。 经过手动配置,我的 UserDefinedDoublePinyinScheme0 值为 小鹤双拼*2*^*iuvdjhcwfg xmlnpbksqszxkrltvyovt 。 如果以后再需给微软拼音添加小鹤双拼方案,只需打开注册表编辑器,在 \HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS 项中新建一个字符串值,将其命名为 UserDefinedDoublePinyinScheme0 (其中0也可能是1、2、3…),把它的值设置为 小鹤双拼*2*^*iuvdjhcwfg xmlnpbksqszxkrltvyovt ,就可以直接完成双拼方案的添加,而无需逐一手动配置。 当然,也可以直接下载注册表文件,然后导入注册表。 下载注册表文件压缩包(ZIP,412B)