普通视图

发现新文章,点击刷新页面。
昨天以前首页

Alan Becker 的动画教学视频是非常好的启蒙材料

2025年5月6日 01:34

alan-becker-animation-vs-math-e-to-i-pi-2025-03-11-12.55.20-scaled Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯

虚数i*i*i=-i 也就是i*e^(ipi)

自从去年家里装修完之后,我们在厨房装了一台电视,平时吃饭时会随便看看一些视频。大约两个月前的一天中午,我从学校接弟弟回家吃饭,无意间发现了一个叫 Alan Becker 的动画视频系列——“Animation vs.”,是 YouTube 上的一个非常有创意的系列。他最出名的作品之一就是《Animation vs Math》。

Alan Becker 的这些视频通常用一群小人(也有人叫他们“小黄人”、小橙色、火柴人)在电脑屏幕上互动的方式,讲述一个个富有创意、又带有教育意义的故事。虽然整个系列几乎没有一句对白,但却通过画面和动作把复杂的知识点讲得既直观又有趣。

我最喜欢的四个视频是《Animation vs Math》、《Animation vs Coding》、《Animation vs Physics》和《Animation vs Geometry》。每一集不仅让人捧腹大笑,还让人对背后的知识产生兴趣。比如在《Animation vs Math》中,小人们在坐标系、函数图像之间跳跃和作战,看得人不知不觉就理解了各种数学概念。而《Animation vs Coding》则展示了编程的魔法,小人甚至“黑进”了主人的电脑,自己写代码!《Animation vs Physics》里,小人们挑战了牛顿定律、重力和能量守恒,用夸张但合理的方式演绎了物理知识。

《Animation vs Geometry》则是另一个令人惊喜的作品。视频里,小人们与各种几何图形互动,有时被三角形包围,有时从正多边形中逃脱,有时又在空间几何中穿梭。通过他们的“冒险”,我对角度、面积、对称和旋转等概念有了更加立体的理解。这些原本在课堂上觉得枯燥的几何知识,突然变得生动起来。

现在每次吃饭的时候,我和弟弟都会打开一集,一边看一边讨论背后的知识点。他有时候还会模仿小人画画、写代码,看得津津有味。比起传统的教学方式,这种寓教于乐的视频真的非常适合启发孩子的兴趣,也让我重新发现了学习的乐趣。

Alan Becker 的动画视频在 YouTube 上非常火,常常能获得数百万甚至上亿的浏览量。很多 UP 主还会专门制作 Reaction(观看反应)视频,一边观看一边讲解其中的知识点,让观众能更深入地理解动画背后的内容。

我真心希望 Alan Becker 能继续制作这个系列的视频。他的作品不仅有趣,还兼具知识性和创意性,每一集都能带来惊喜。我还特地去看了一下他的 YouTube 主页,发现他有自己的网站和线上小店,里面出售一些周边商品和纪念品,比如角色贴纸、T 恤、鼠标垫等等。通过这些方式,粉丝们也能支持他继续创作。

如果我在初高中时就能看到这样的动画视频,可能我的学习态度和兴趣都会大不一样,说不定我也不会变成所谓的“学渣”。Alan Becker 的这些作品用轻松有趣的方式,把复杂的知识讲得通俗易懂、引人入胜。我相信,这样的内容一定能激励无数学生,让他们重新发现学习的乐趣,也让知识变得真正“活”起来。

这些视频的背景BGM音乐也非常出色,节奏紧凑、氛围感强,不仅很好地配合了剧情发展,还增强了观众的代入感。每当剧情进入高潮或角色展开大战时,音乐的节奏也随之加快,让人不自觉屏住呼吸、全神贯注。可以说,音乐和动画配合得天衣无缝,是整个系列成功的重要一环。

Alan Becker 的 Animation 系列动画教学视频

Alan Becker 在油管的频道有3000万+粉丝。

Animation vs Math 数学

这是我最喜欢的视频之一。它是在一年前上传的,如今已经有超过 8000 万的播放量。视频的开头从数学中最基础的常量“1”出发,接着逐步引入加减乘除等基本运算,再到实数与虚数,最后主角“-1”展开了一场围绕著名公式 tex_39ab8c54661fe3603377d1d4de5601e0 Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯 的激烈格斗故事。

整个视频既紧凑又富有张力,把一个高度抽象的数学公式,用动画的方式生动演绎出来。观众不需要具备高深的数学背景,也能感受到其中的逻辑美和力量感。尤其是当角色“i”(虚数单位)和“π”共同施展出终极公式的那一刻,既震撼又令人忍俊不禁。

这也是 Alan Becker 的动画魅力所在:用可视化的手法,把严肃甚至有些晦涩的知识,变成一个个有趣的、像电子游戏一样的冒险故事。对我来说,看完这部视频后,对欧拉公式产生了浓厚的兴趣,后来还专门去查资料,才知道这个等式被称为“数学中的诗”。

哥哥弟弟也很喜欢这一集,他虽然年纪还小,看不懂太多公式,但他能理解角色之间的战斗与变化,这就足够吸引他了。我们甚至还尝试用积木和纸画重现其中的几幕场景,边玩边学,乐在其中。

哥哥也很喜欢数学,平时在学校数学成绩是班上最好的,希望这个视频能启蒙到他。

我最喜欢的一幕就是当 tex_96cad1e5ef25c476c3be4eb8d105669c Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯 化身为 Transformer 形态,用手上的 “limit” 装置接住了小黄人化身的 tex_02d375c3338b3ac7e6c3b81fb6e031b5 Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯 这一幕不仅视觉上震撼,也充满了数学梗的巧思。将抽象的公式和极限概念具象化成角色之间的对抗和救援,真的是把“知识即力量”演绎到了极致。

很享受这种知识划过脑却不留痕迹的感觉。

数学知识
0:07 最简单的入门方式——1 是第一个自然数,这是公理化的(尽管在一些数学分析教材中,他们首先指出 0 是自然数)。
0:13 等式——数学课上学习的两个对象之间的第一个关系。
0:19 加法——四种基本算术运算中的第一个。
0:27 重复 1 的加法,这是我们在集合论中定义其余自然数的方式;也是乘法的铺垫。
0:49 与 1 以外的数字进行加法,这可以用我们已知的 1 加法来定义。(省略证明)
1:23 减法——四种基本算术运算中的第二个。
1:34 我们的第一个负数!它也可以表示为 tex_8c4d4d144d960c160796f47c59507535 Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯 ,这是将 tex_0e4258221bdbc1e8dd20cfdd36fa4447 Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯 的泰勒级数的定义域扩展到复数的结果。
1:49 tex_8c4d4d144d960c160796f47c59507535 Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯 乘以 i,这打开了一扇通往……虚数世界的大门?这也暗示了小黄人实际上存在于实数世界。TSC 现在如何再次得出这个量?

2:12 重复减 1,类似于对自然数的操作。
2:16 负数乘以负数得正数。
2:24 乘法,以及通过重复加法或任何运算对其进行的解释。
2:27 乘法的交换律,以及 12 的因数。
2:35 除法,最后的算术运算;也很好地展示了 – 和 / 之间的关系,就像 + 和 x 之间的关系一样!
2:37 除法就是计算重复减法的次数,直到零。
2:49 除以零,以及为什么它没有意义。令人惊讶的是,TSC 没有用这个来制造一个黑洞。

3:04 指数运算是重复的乘法。
3:15 高阶指数如何对应几何维度。
3:29 任何非零的零次方都是1。
3:31 负指数!以及它与分数和除法的关系。
3:37 分数指数和平方根!我们越来越接近了……
3:43 无理数(例如 sqrt(2))的小数展开是不规则的。(我避免使用“无穷大”这个词,因为从技术上讲,每个实数的小数展开都是无穷大的……)
3:49 sqrt(-1) 给出虚数 i,它首先由性质 i^2 = -1 定义。
3:57 复数的加法和乘法是根据我们已知的原理进行的。
4:00 i^3 等于 -i,这当然会得到 i*e^(i*pi)!

4:14 参考 3:49
4:16 欧拉公式 x = pi!这个公式可以通过重新排列 e^x 的泰勒级数来表示。
4:20 小细节:被负号击中会改变 TSC 的方向,这又一次暗示了复平面!
4:22 e^(i*pi) 到 e^0 对应于复平面上沿单位圆的运动。
4:44 +1/-1 的“剑”相互碰撞,发出“0”个火花。
4:49 -4 的剑击中 +1 的剑,变为 -3,等等。
4:53 2+2 的弩射出 4 支箭。
4:55 4 支箭击中除号,与 pi 对齐,得到 e^(i*pi/4),使其沿单位圆旋转 pi/4 弧度。
5:06 TSC 通过乘以 i 来推动自己,围绕单位圆旋转 π 弧度。

5:18 TSC 终于发现了复平面!5:21 虚轴;5:28 实轴。
5:33 最简单​​的单位圆。
5:38 圆中的 2*π 弧度。
5:46 弧度的定义——单位圆中跨越长度为 1 的弧度的夹角。
5:58 r*theta——半径为 r 的圆中,夹角为 theta 的圆弧长度公式。
6:34 对于单位圆来说,theta / r 就是角度。
6:38 圆周的一半正好是 π 弧度。
6:49 正弦函数和余弦函数如何与绕单位圆逆时针旋转相关——sin(x) 等于 y 坐标,cos(x) 等于 x 坐标。
7:09 旋转 sin(x) 可以直观地看到 sin(x) 和 cos(x) 之间的位移。
7:18 参考 4:16

7:28 将指数改变为 π 的倍数,使其向各个方向移动。
7:34 一个新形式!?e^x 的泰勒级数,其中 x=i*π。现在它有无限的弹药了!?同样,弹药将每个项的十进制展开式作为其弹道标记。
7:49 面积为 pi r^2,高为 8 的圆柱体的体积。
7:53 给读者的练习(哈哈)
8:03 参考 4:20
8:25 关于 e^(ix) 的 cos(x) 和 sin(x)
8:33 很遗憾,这部分我看不懂……TSC 创建了一个“函数”枪 f(x) = 9tan(pi*x),这样朝 e^(i*pi) 射击会得到 f(e^(i*pi))= f(-1) = 0。
9:03 参考 5:06
9:38 “函数”枪现在在无穷远处“求值”,通过每次增加一个维度来扩展实空间(它是一个向量空间),即实空间的跨度扩展为 R^2、R^3 等。
9:48 log((1-i)/(1+i)) = -i*pi/2,乘以 2i^2 = -2 再次得到 i*pi。
9:58 通过缩短间隔并取极限来阻挡“无穷大”光束,这并非黎曼积分的精确定义,但足够接近了。

油管视频:Animation vs Math

Animation vs Coding 编程

编程这一集我看得最懂了,也很有意思,特别是当中那个原子弹的Python程序,就是无限递归/Recursion内存爆炸。

def nuke(n):
    a = []
    for i in range(10):
        if n > 1:
            a.append(nuke(n - 1))
        else:
            a.append(i)
    return a

print(nuke(10))

还有就是Python里调用Turtle包进行海龟作图(这可是我学编程的第一个编程语言)也相当有创意。

0:18 未定义 – 计算机不知道这是什么,就是这样
0:34 print() 将内容打印到终端
0:42 重新运行代码
0:52 变量
1:00 运算(* 表示乘法,/ 表示除法)
1:05 向下取整除法(删除小数)
1:10 字符串本质上就是可以使用的文本
1:23 不能将数字和字符串一起使用
1:27 让你专注于一个特定的字符串/变量/数字
1:32 对象中的项目数(在本例中为 7,因为 string7 有 7 个字符)
1:38 代码语法错误
1:45 b 本质上是 a 中的字符(所以 a[5] 应该是 g,而不是 n,因为它从 0 开始)
1:48 重复代码数字/字符串长度
2:00 基本上会一直运行,因为它基本上告诉计算机“当 true 为真时”
2:02 大写显然
2:09 如果你在执行 while、if 或 for 之类的循环,需要将循环内部运行的代码推送进去,这样计算机才能知道你想要循环的代码。
2:16 循环内部的代码不是在 a 可打印的情况下运行,因为 a 可打印,所以它不能运行。
2:23 循环内部的代码在 if 不可打印的情况下运行。
2:32 字符串列表,本例中的 * 表示将所有字符串都考虑在内。
2:40 海龟本质上就是一支铅笔,我们在这里定义了海龟(所以我们可以直接写 t),然后 import 函数会从库中导入内容(库中有内置库)。
2:41 400 表示它在方向上移动的增量。
2:52 改变海龟移动的速度。
2:55 左右移动使海龟转向。
3:03 循环海龟的移动。
3:17 pensize 表示线条的面积。
3:32 这实际上是停止海龟的方法。哈哈
3:44 matplotlib 是一个绘图库(它显然可以让你访问图表和图形),numpy 允许你使用三维数组
3:44 插入绘图然后使其显示
3:58 获取一个随机的三维整数
4:20 绘图标题,太棒了
4:29 函数,让你在运行函数名称时运行这组代码
4:41 pygame 是一个令人惊讶的库,可以让你制作游戏!while true 循环用于检查用户是否关闭了窗口/选项卡
4:44 窗口的基本 pygame 代码
4:57 允许你通过按下某些键来移动对象
5:21 圆圈朝黄色的位置移动,向前移动时加速,向后移动时减速(谢谢 xTI0)
6:22 不要这样做。核函数中的数字表示列表嵌套的次数,所以 10 基本上就是递归地嵌套 10 次,非常卡顿
6:35 # 让你直接输入文本,非常适合解释你的代码是如何工作的
7:36 他真的在写一个 AI 代码
7:50 神经网络正在接受训练

快速提示:还记得最后提到这是 Python 吗?还有其他编程语言用于不同的目的,我想这很明显,但为了以防万一,一些流行的编程语言是 C、HTML、JavaScript 等等!

油管视频:Animation vs Coding

Animation vs Physics 物理

《Animation vs Physics》这一集的后半段内容变得相当深奥,涉及到了相对论、黑洞等高阶物理概念。我目前的理解还停留在初高中学过的一些基础知识,比如牛顿的第二定律 tex_9daa187e47eb4fb749102377832e3148 Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯 、动能定理、还有爱因斯坦著名的能量公式 tex_76dbfb9a84d34d7f746b1f4cbbb81919 Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯

虽然看不太懂后面的部分,但前半段关于力、加速度、重力等基础物理的呈现还是非常有趣的。角色们像在玩一场“现实模拟游戏”,各种物理定律在他们的世界里都有了视觉化的呈现,哪怕是没怎么学过物理的人也能看得津津有味。

0:19 加速度和速度
1:18 质量
3:07 势能
4:17 重力
4:38 米/秒
5:55 以更高的速度围绕行星运行
6:00 以米/秒为单位的速率增加
6:12 围绕其他行星的速度将随机倍增
基于其大小
6:40 围绕恒星的速度将根据其大小而倍增
7:05 是β的1%
7:21 磁场和引力
7:40 磁场环
7:48 制作磁场火箭
8:18 火箭速度因磁场火箭而加快
8:33 观察星系、星系系统和其他
9:14 黑洞
9:30 关于黑洞的事实
10:03 黑洞内部

油管视频:Animation vs Physics

Animation vs Physics 几何

说到几何,它是数学的另一个重要分支,其中最著名的“主角”之一就是黄金分割。黄金分割不仅在几何中占有一席之地,还常被视为“数学之美”的代表。比如大家熟悉的斐波那契数列,就和黄金分割密切相关——随着数列不断增长,相邻两项的比例会越来越接近黄金比例。

黄金分割的魅力不仅仅体现在数学里,在自然界、艺术、建筑甚至音乐中都有它的身影。像贝壳的螺旋、向日葵的花盘、古希腊神庙的比例,甚至名画《蒙娜丽莎》的构图,都被认为与黄金分割有关。

Alan Becker 在《Animation vs Geometry》中也通过角色与几何图形的互动,让我们直观地看到了这些数学背后的和谐与美感。通过一场看似搞笑却充满巧思的冒险,观众不仅被娱乐到了,也潜移默化地接触到了黄金分割等几何概念。

油管视频:Animation vs Geometry

油管视频 / Youtube Video

英文:Animation Youtube Videos from Alan Becker

本文一共 3926 个汉字, 你数一下对不对.
Alan Becker 的动画教学视频是非常好的启蒙材料. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c Alan Becker 的动画教学视频是非常好的启蒙材料 学习笔记 教育 数学 物理 编程 视频 计算机 资讯
The post Alan Becker 的动画教学视频是非常好的启蒙材料 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 什么是马太效应? 马太效应是一种社会学和经济学现象,描述了“富者愈富,贫者愈贫”的情况。这个概念来源于《新约圣经·马太福音》中的一句话:“凡有的,还要加给他,使他有余;没有的,连他所有的也要夺去。”因此,“马太效应”这一术语应运而生。 你可能听过这句话:“富者愈富,贫者愈贫” ——这就可以被视为马太效应。 有的人运气好的时候可以一直好运连连,似乎不管做什么都顺风顺水;而有的人一旦遭遇不幸,比如生了场大病,就像是被命运压垮了,接连而来的挫折仿佛永无止境。这种现象其实与我们常说的“马太效应”密切相关——资源和机会越多,往往更容易获得新的机遇,而那些本就处于困境中的人,可能会越发陷入逆境。 我们在生活中会经常遇到类似的情形:一部分人似乎总能轻而易举地获得成功,而另一部分人即便努力了也难以改变困境。这不仅仅是运气的问题,还涉及到资源、支持系统,以及社会对个人发展的反馈。有时候,幸运的人往往有更多的支持与资源来化解困难,反之亦然,不幸的人在面临困境时却因为资源稀缺而难以逆转局势。 马太效应在生活的各个领域广泛存在。 经济学 较富有的人通常有更多的资源和机会去进一步积累财富,而贫穷的人由于缺乏资源可能会变得更加贫困。例如,富人有更多的资本去投资和购买资产,从而获得更多回报。 教育 高成就的学生通常能够获得更多的教育资源、教师关注以及成就感,这进一步激励了他们的学习。与此同时,学业上困难的学生可能得不到足够的支持,导致成绩下降。 职场 在职场中,表现出色的员工往往能够获得更多的资源、机会和认可,进一步巩固他们的地位。而另一方面,表现普通的员工可能由于缺乏机会而被边缘化。 科学研究 著名科学家的研究成果往往更容易获得认可和引用,进一步提升他们的声誉,而其他研究人员由于缺乏可见性可能难以得到承认。 总结:马太效应...
  2. 如何在海外 在线观看 2016 欧洲杯比赛? 四年一度的欧洲杯足球又开始了, 夏天来了 没有什么比边看足球边喝啤酒更爽的事了. 通过以下链接可以 免费在线观看 2016 欧洲杯比赛 (英语解说). 点击以下链接能在线免费看 2016 欧洲杯比赛 (The following provides online...
  3. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  4. 来来来 加我公众号玩 石头剪刀布 写着玩的, 不过虽然猜拳的策略是完全随机 但是真的很难上 10分. 赢了得1分, 输了扣1分. 平局分数不变. 输入分数或者score查看当前得分. 这个分数会直接记入终身档案中.. 一个微信帐号一个分数. 暂时不支持 表情符号. 微信服务器 IP地址是 101.226.103.69...
  5. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  6. 2023年生日: 快乐的时间很快就过去了 奔四了,哎。这几年越来越不想过生日,感觉今年一年啥也没完成,混日子,每年生日的时候就感觉一年又白混了,快40了,腰都埋土里了。 每年生日,FACEBOOK,LINKEDIN等社交网络就会提醒你,提醒你的朋友,甚至是陌生人也会给你留言提醒你生日快乐。印象中离开初创公司后,每年生日我都会休假,加入微软2年多,从去年开始,经理总会在生日当天在TEAMS上给我发生日祝福。 当然,这几年最开心的事还是在生日当天,媳妇会格外变得听话贤惠多了,甚至可以满足我一些不太合理的要求。 早上醒来,媳妇就和我说生日快乐,然后把早餐给我送到床上来,我当时感觉像病号一样,娃也给我自己做的贺卡。 早上带娃去看牙医,后来就回家歇着了,中午媳妇给我做了长寿面,晚上约会吃饭(剑桥 Trinity (三位一体) 餐厅),然后去路边的小酒巴喝点小酒。然后该有的活动逃不掉的,你懂的。 哎,祝自己生日快乐,接下来一切顺利吧。 生日当天约会视频: 油管 Youtube B站 Bilibili 西瓜...
  7. 交易所Ionomy跑路, 又丢了好多币, 哎 ionomy.com 交易所的最后一次访问记录可能是2024年6月。 我有一些BLURT币在Ionomy交易所,一直没去管它,这几天想上去看看,因为牛市来了,BLURT币也拉了不少。谁知道网站直接打不开了。 Ionomy交易所已经打不开了。 问了一下,大概是几个月前(2024年年初)跑路的,我现在才知道,关键是没收到任何通知/预警。鱼老板说:“交易所真爱跑路,说了还怎么跑”。 Ionomy交易所是个非常小众的交易所,2018年10月创立,下面是Ionomy交易所在Coinmarketcap(现被币安收购)显示的信息: 我还在FB/Meta上联系他们,但是估计跑路了,不报任何希望了。 Ionomy的Telegram群也就不到300人,太小众了。 我在群的照片里看到了Ionomy交易所的管理层,也不知道是真是假,无从核实,在Linkedin上也搜不到。 这是我在群里照片看到的Ionomy交易所的界面,我的帐号里面应该有大概5000或者1万个BLURT币,也就几十美元,丢了几根鸡腿。 万幸的是前几年曾经提走了0.124个BTC,不然放到现在得哭死了。(当时BLURT的糖果/Airdrop/空投换了0.124个BTC) 为什么我要使用Ionomy交易所? 因为BLURT是STEEM分叉出去的币,小币种,支持它的交易所只有不到三个,其中一个是Ionomy,还有一个比较有名的是Hive-Engine。 小币上大交易所费用贵,听说前几年的上交费是1个比特币。我在STEEM上的发的文章一般也会同步到BLURT去,挣些糖果。 避免小众交易所...
  8. 你要找什么样的老婆? 找媳妇的标准 昨天和网友在剑桥面基, 网友奔现, 他从爱尔兰过来, 小我12岁, 就聊到了找对象的标准. TLDR; 找老婆不要(只)看颜值, 而要注重性格, 为人处事和顾家等更重要的品质, 当然性和谐也很重要. 在当今社会, 人们对于找伴侣的标准有所不同. 有些人认为颜值是最重要的, 因为外貌吸引力可以让人在日常生活中感到愉悦, 这是人的本性,...

使用 Python 脚本下载指定网页的图片文件

2025年3月31日 00:00

有小伙伴反馈说侧边栏随机图出现了重复,有些审美疲劳,要求杜老师再更新一些图片,正好聊天广场有小伙伴分享了一个美图的网址。本文分享如何使用 Python 脚本下载指定网页的图片文件,需要的小伙伴可以参考文中代码。

代码需求

使用 Python 的语言编写一个脚本,下载指定网址中包含的多种格式图片文件,如 JPG 和 PNG 格式图片。

将图片保存至指定的目录中,可以指定绝对路径,或者相对路径。

并用随机数重命名,防止同名图片触发覆盖事件。

尽可能使用 Python 的标准库,尽量避免使用第三方库。

变更解释

  1. 导入必要的库:包括 os/requests/re 以及 random

  2. 定义函数:download_images 函数可用于下载图片;

  3. 获取图片链接:使用正则表达式从网页内容中提取图片 URL;

  4. 下载保存图片:使用 requests 库下载图片,并且使用 random 库生成随机数作为文件名;

  5. 指定目录:确保保存目录存在,如果不存在则创建;

  6. 获取内容:使用 requests 库获取网页内容。

功能代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import os
import requests
import re
import random

def download_images(url, save_folder):
# 确保保存目录存在
if not os.path.exists(save_folder):
os.makedirs(save_folder)

# 发送HTTP请求获取网页内容
response = requests.get(url)
if response.status_code != 200:
print(f"Failed to retrieve the webpage. Status code: {response.status_code}")
return

# 使用正则表达式查找所有的图片URL
image_urls = re.findall(r'<img[^>]+src=["\'](.*?)["\']', response.text)

for img_url in image_urls:
# 处理相对路径的URL
if not img_url.startswith(('http://', 'https://')):
img_url = os.path.join(url, img_url)

# 下载图片
img_response = requests.get(img_url)
if img_response.status_code == 200:
# 生成随机文件名
random_filename = f"{random.randint(10000, 99999)}.jpg"
save_path = os.path.join(save_folder, random_filename)

# 保存图片
with open(save_path, 'wb') as f:
f.write(img_response.content)
print(f"Downloaded and saved {img_url} as {save_path}")
else:
print(f"Failed to download {img_url}. Status code: {img_response.status_code}")

# 读取网址列表文件
def read_urls_from_file(file_path):
with open(file_path, 'r') as file:
urls = file.readlines()
return [url.strip() for url in urls]

# 示例调用
if __name__ == "__main__":
urls_file = 'f:\\代码\\urls.txt' # 包含网址的文件路径
save_folder = 'f:\\代码\\images' # 保存图片的目录路径

urls = read_urls_from_file(urls_file)
for url in urls:
download_images(url, save_folder)

注意:本示例代码仅适用于 Python 3.x 版本,运行于 Windows 系统。如使用 Linux 系统,可能需要进行相应修改。

使用说明

将上述的代码保存为 download_images.py 文件。

在运行脚本时,传入目标网页的 URL 和保存图片的目录路径。

脚本会自动下载网页中所有图片,并且以随机数命名保存到指定目录中。

打开的网址保存在一个文件,每行一个网址。

如何构建一个具有重试机制的 Steem 区块链见证人投票检查器

2025年4月7日 23:15

当使用像 Steem 这样的去中心化平台时,重要的是要预期偶尔的故障——网络问题、API 限制或暂时的停机。这就是为什么你构建的任何集成、机器人或工具都应该能够优雅地失败并智能地恢复。

在这篇文章中,我将带你了解一个简单而强大的 Steem 区块链见证人(也就是STEEM上的矿工)投票检查工具:

第一版:检查是否由某个见证人投票

这是一个实用的 Node.js 函数,用于检查一个 Steem 用户是否投票支持了某个特定的 见证人——无论是直接投票还是通过代理。

function is_voted_by(witness, id) {
    return new Promise((resolve, reject) => {
        steem.api.getAccounts([id], function(err, response) {
            if (err) reject(err);
            if (typeof response == "undefined") reject("undefined");
            const data = response[0];
            resolve((data.proxy === witness) || data.witness_votes.includes(witness)); 
        });          
    });
}

它获取给定 id 的账户数据,然后检查该用户是否设置了匹配目标见证人的投票代理,或者该见证人是否在他们的直接投票列表中。

启用重试的 Steem 见证人投票检查器

这是更新版的函数,包含简单的重试机制(最多重试 3 次,每次重试间隔 1 秒)。以下的代码加入了重试功能:

function is_voted_by(witness, id, retries = 3) {
    return new Promise((resolve, reject) => {
        const attempt = (remaining) => {
            steem.api.getAccounts([id], function(err, response) {
                if (err || typeof response === "undefined") {
                    if (remaining > 0) {
                        setTimeout(() => attempt(remaining - 1), 1000); // 重试 1 秒后
                    } else {
                        reject(err || "undefined response");
                    }
                    return;
                }
                const data = response[0];
                resolve((data.proxy === witness) || data.witness_votes.includes(witness)); 
            });
        };
        attempt(retries);
    });
}

此版本在 Steem API 调用失败或返回 undefined 时最多进行 3 次重试,帮助处理不稳定的网络状况或临时的 API 问题。功能保持不变:检查直接的见证人投票或代理委托/Proxy。

重试的指数退避机制

使用指数退避机制/Exponential backoff来避免过度请求 API,并记录每次尝试以便于调试和更好的可视化。

module.exports.is_voted_by = function(witness, id, retries = 3, delay = 1000) {
    return new Promise((resolve, reject) => {
        const attempt = (remaining, currentDelay) => {
            console.log(`Checking vote for "${id}" against witness "${witness}"... (${retries - remaining + 1}/${retries})`);

            steem.api.getAccounts([id], function(err, response) {
                if (err || typeof response === "undefined") {
                    console.warn(`Attempt failed: ${err || 'undefined response'}`);
                    
                    if (remaining > 0) {
                        console.log(`Retrying in ${currentDelay}ms...`);
                        setTimeout(() => attempt(remaining - 1, currentDelay * 2), currentDelay); // 指数退避
                    } else {
                        reject(err || "undefined response after retries");
                    }
                    return;
                }

                const data = response[0];
                const voted = (data.proxy === witness) || data.witness_votes.includes(witness);
                console.log(`Vote check result: ${voted}`);
                resolve(voted);
            });
        };

        attempt(retries, delay);
    });
};

该函数:

  • 调用 Steem API 获取给定 id 的账户信息。
  • 检查用户是否投票支持某个特定的见证人,检查方式包括:直接投票(即 witness_votes 中包含该见证人),或通过代理投票(即 proxy === witness)。

如果 API 调用失败或返回 undefined,它将:

  • 等待一段时间,
  • 重试(最多重试 retries 次),
  • 并且每次的等待时间按指数方式增加(1秒、2秒、4秒等)。
为什么要使用指数退避?

指数退避是网络编程中的经典策略——如果服务暂时不可用,快速连续请求只会让问题更加严重。通过在重试之间增加延迟,可以让系统有时间恢复,并且对 API 更加友好。

is_voted_by('witness-name', 'username')
    .then(voted => {
        if (voted) {
            console.log("User supports the witness!");
        } else {
            console.log("User has not voted for the witness.");
        }
    })
    .catch(err => {
        console.error("Error checking vote:", err);
    });

最后的思考

在构建与区块链交互的工具时,弹性至关重要。适当的重试逻辑能大大提高你的应用程序的稳定性和用户友好性——即使在底层基础设施出现问题时也能保持正常运行。

Steem/Steemit区块链

英文:

本文一共 672 个汉字, 你数一下对不对.
如何构建一个具有重试机制的 Steem 区块链见证人投票检查器. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 如何构建一个具有重试机制的 Steem 区块链见证人投票检查器 Javascript STEEM 区块链 区块链 程序设计
The post 如何构建一个具有重试机制的 Steem 区块链见证人投票检查器 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....
  2. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...
  3. 剑桥网红餐厅 The Ivy Cambridge Brasserie 上周刚过40岁生日,媳妇带我去剑桥的一家网红餐厅——The Ivy Cambridge Brasserie。这家餐厅是朋友推荐的,和那家Trinity一样很有名,可惜我们在剑桥待了近10年才知道。 The Ivy Cambridge Brasserie 是剑桥市中心一家备受欢迎的网红餐厅,位于历史悠久的 Trinity Street。餐厅设计时尚典雅,融合了现代与经典元素,为食客提供了一个舒适而奢华的用餐环境。这里的菜单丰富多样,覆盖全天用餐,从早餐到晚餐以及下午茶,提供英式经典美食如松露鸡肉派、英式早餐等,也有一些国际风味的菜肴。得天独厚的位置和独特的氛围使得 The Ivy Cambridge Brasserie...
  4. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  5. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  6. 测测你的幸运 – Linux Fortune-Teller LINUX 下有很好很好玩的命令,之前已经介绍过: figlet, rig, curl. 现在推荐另一个 命令 fortune 是用来随机显示一段(句)话的.fortune 在英文里就是幸运的意思. 这个命令可以不需要 参数 如果没有 可以通过 apt-get...
  7. Discord 和公众号都启用 强大的 P命令 O 哥的公众号里有一个 ?pu 命令很方便, 可以根据当前的行情, 市场价 feed price 等信息自动给出是否应该是 100% 发文或者 50/50. 今天有点时间, 整合到 Discord...
  8. 撸了一个Steem粉丝点赞检查工具 – 快速检查你的粉丝到底有没有给你点赞 (带免费API) 花了三小时, 总算把这个小工具(自带API)的做出来的, 主要的用法就是给定一个STEEM贴, 然后分析出ID号, 然后得出粉丝列表, 然后再解析HTML源代码分析出当前点赞的粉丝, 最后面计算两个数组的差别就是没有点赞的粉丝. 工具试一下好不好用. 有问题联系 @justyy 点我: Steem粉丝点赞检查工具 输入STEEM的文章地址按回车或者点击 按钮 等待一会,...

力扣刷题打卡2209天竟然断了, 哎

2025年3月21日 04:55

坚持6年的力扣刷题打卡作天断了。昨天开了一整天brainstorming 晚上陪媳妇过生日。然后就给忘了,还好的是力扣国服还没断。

2019年3月2日 我在美国通用电气午休的时候太无聊 拿起IPAD就刷了一题。

谁曾想:这一刷就是2209天。这六年时过境迁,很多事情物是人非。我也从美国通用电气GE(General Electric)到亚马逊 Amazon AWS 再到现在的微软剑桥研究院MSRC。

leetcode-2209-days-streak-ended 力扣刷题打卡2209天竟然断了, 哎 程序员 编程 计算机 计算机

力扣刷题打卡2209天 始于2019年3月2日,终止于2025年3月19日,可惜了。

力扣每日一题打卡是可以补签的,只需要花70点积分就可以 Time Travel 回到本月补上一签。只不过那个墙是补不了的,没刷就是空的。

leetcode-from-2013-to-2024-scaled 力扣刷题打卡2209天竟然断了, 哎 程序员 编程 计算机 计算机

从2013年到2024的力扣刷题之路。

力扣美服是可以同步到国服的,但是目前是没有办法反过来把国服的刷题记录复制一份到美服的。可能,这就是注定的吧,肯定会有不完美的。还好我不是处女座,这么一点不完美不会逼疯我的,哈哈。我还是会继续刷题的。刷题努力提升自己,好处多多。

刷题:程序员的基本技能

本文一共 410 个汉字, 你数一下对不对.
力扣刷题打卡2209天竟然断了, 哎. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 力扣刷题打卡2209天竟然断了, 哎 程序员 编程 计算机 计算机
The post 力扣刷题打卡2209天竟然断了, 哎 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. SteemIt 高级定制微信文章列表 RSS/API/阅读器 v2.0 The Advanced Wechat Group Posts Feed/API/Reader v2.0 Abstract: I have added five parameters to the...
  4. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  5. 编程: NodeJs/Javascript 函数检查Tron/波场区块链上的交易是否已确认(TronGrid API) 我们想知道给定的交易是否已经在 Tron/波场 区块链上确认,这可以通过 TronGrid API 轻松实现。 为了确保交易在Tron/波场区块链上被确认,验证逻辑应关注交易的状态,这表明交易是否已被 Tron 虚拟机(TVM)成功处理。以下是正确的验证方法: 检查 receipt.result 验证交易成功的主要标志是 receipt.result 字段。值为 “SUCCESS”...
  6. 比特币最近波动有点大: 一天牛市一天熊 比特币10万美金以内都是最后上车的机会! 比特币近期的价格波动可以归因于多个关键因素,包括地缘政治动态、监管变化以及加密行业内的重大安全事件。其中一个主要影响因素是美国前总统唐纳德·特朗普对乌克兰和加密货币监管的立场变化。据报道,特朗普再次当选,他可能会推动减少美国对乌克兰的支持,这可能会影响全球金融市场和风险偏好。同时,特朗普正在将自己塑造为亲加密货币的候选人,表示有意让美国成为一个更加友好的加密货币环境。这一立场引发了市场对监管政策可能发生变化的猜测,导致市场情绪在乐观和不确定性之间波动。 特朗普对俄乌战争的态度 美国第43届总统唐纳德·特朗普已经在2025年1月当选并正式上任(第二次),那么他的政策可能会对比特币价格的波动产生更加直接和显著的影响。他政府对乌克兰和加密货币监管的立场已经不再是猜测,而是正在实际塑造市场的关键力量。 特朗普(Donald Trump)减少美国对乌克兰的支持,全球投资者可能会预期地缘政治稳定性发生变化,从而增加对比特币作为避险资产的需求。同时,他的亲加密货币立场可能正在推动市场的乐观情绪。如果他的政府推出有利于加密行业的监管政策,例如明确的合规指南或减少监管审查,可能会吸引更多机构投资者进入市场,并促进更广泛的加密货币采用。然而,政策的快速变化也可能导致短期市场剧烈波动,因为市场需要时间来消化新的政策动向。 朝鲜黑客盗取Bybit交易所15亿美元的ETH 另一个显著影响比特币价格的事件是近期涉及朝鲜黑客组织“Lazarus”的15亿美元以太坊被盗案件。据报道,Bybit交易所(全球第二)这些被盗的ETH已经被清洗,此次大规模黑客攻击引发了人们对加密行业安全性的担忧。此类安全事件不仅会削弱投资者信心,还可能引发更严格的监管审查,导致短期市场动荡。此外,被盗资金的大规模流动和出售可能对市场流动性造成冲击,进一步加大价格波动。随着这些事件的持续发酵,比特币价格正受到政治决策、监管预期以及安全挑战等多重因素的影响。 与此同时,与朝鲜黑客组织 Lazarus 相关的 15 亿美元以太坊被盗事件仍在影响加密市场。由于这些被盗 ETH 已被清洗,人们对加密行业安全漏洞的担忧持续存在,同时也可能引发更严格的监管审查。政治、监管和安全等多重因素交织在一起,共同导致了比特币近期的剧烈价格波动。...
  7. 在英国带孩子去露营全攻略 之前就做了一些露营的准备工作, 因为大儿子Eric 很兴奋说是要去 Camping Holiday 估计是在 Papa Pig 里看到的. 英国有很多可以露营的地方, 最后面选了一个离家开车1个多小时. 看了评论还不错. 地址为: New Road,...
  8. 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 家里有两只猫 Chessly/Pyro,想着找个地方记录它们的生活,最后决定还是写在这里的博客。猫的一生很短,差不多也就二十年。 Chessly(黑白猫)是我加入微软剑桥研究院MSRC第一个月带回家的,过了两三个月,又把Pyro(橘猫)也接回了家。两只猫的名字是孩子们取的:Chessly因为黑白的像棋盘,加上“ly”听起来像个女孩的名字;而Pyro的意思是一团火(烟火),充满活力。 刚开始的时候,Chessly特别喜欢待在我的工作区域。她有时候趴在键盘上或旁边,有时候藏在显示器后面。偶尔还会绕到我身边“咕咕”地撒娇,等着我去摸她。有时更干脆跑到我腿上,舒舒服服地躺着。 不过,现在它们俩的体型都大了很多,躺在桌上就会挡住屏幕,真是“面向猫猫编程”(Cat Oriented Programming)的极致体验。 记录生活的点滴,也是一种珍惜,毕竟这二十年,我们会一起走过。 2024年 2025年 Ring视频:两猫日常就是打闹,Chessly追上Pyro想舔他,在猫的世界里,地位高的才能舔地位低的。 我家猫现在越来越胖,很喜欢在我工作的时候躺在显示器钱,很影响我的工作,不过这时候我就是会休息一下摸摸她,就当放松一下了。 Pyro在窗边喝水,这是个小的煮饭锅,现在不用了,就给猫当喝水的碗。Pyro很胆小,经常看到我就跑。没法跑就咕咕叫。 Chessly很喜欢陪我工作,然后她很好厅的盯着屏幕上的鼠标光标,真怕她把屏幕抓坏了。 哥哥弹琴,弟弟唱歌,Chessly午睡,真是幸福啊,下辈子做只猫吧。...

如何使用 GDB 或 LLDB 调试器重复运行程序直到错误

2025年3月21日 04:35

gdb-debugger 如何使用 GDB 或 LLDB 调试器重复运行程序直到错误 gdb / lldb 小技巧 程序员 编程 计算机 计算机 调试

GDB Debugger 调试器

如何使用 GDB 或 LLDB 调试器重复运行程序直到发生错误

在调试程序时,有时候我们希望一直运行程序直到遇到错误。GDB(GNU 调试器)和 LLDB(LLVM 调试器)都提供了自动化此过程的方法。

这在调试一些不稳定的程序非常有用,例如有时候写的一些单元测试可能随机崩溃SegFault。最近我在调试一个测试每跑1000次就有一次崩溃的时候就发现gdb/lldb调试器非常有用。

简介:GDB vs LLDB

GDB(GNU 调试器)和 LLDB(LLVM 调试器)是用于低级别应用程序调试的强大工具。GDB 传统上用于由 GCC 编译的程序,而 LLDB 是 LLVM 项目的一部分,与 Clang 无缝配合。两者都提供强大的功能,但 GDB 在 Linux 环境中更常见,而 LLDB 在 macOS 上以及处理 Swift 时更受青睐。

自动化执行直到发生错误

调试重复性任务可能很乏味。通过自动化这些任务,直到发生错误,可以节省大量时间和精力。以下是如何在 GDB 和 LLDB 中设置重复执行直到遇到错误的步骤。

GDB 脚本

下面是一个 GDB 脚本,它会重复执行,直到发生错误:

repeat_until_error.gdb

while 1
  run
  if $_exitcode != 0
    echo "发生错误,退出码:$_exitcode\n"
    break
  end
end

要运行此脚本,将其保存为 repeat_until_error.gdb,然后使用以下命令执行 GDB:

gdb -q -x repeat_until_error.gdb ./your_program

或者,当你处于 gdb 提示符时,运行:

source repeat_until_error.gdb

“run” 命令用于启动程序,你可以为它添加参数,例如:

run param1 param2 ...

你也可以使用 “r” 作为 “run” 的简写。

LLDB 脚本

类似地,在 LLDB 中,你可以使用以下脚本实现相同的效果:

repeat_until_error.lldb

while (1)
  run
  process launch
  if (process status != 0)
    script print("发生错误,退出码:", lldb.process.GetExitStatus())
    break
  end
end

要运行此脚本,将其保存为 repeat_until_error.lldb,然后运行:

lldb --source repeat_until_error.lldb ./your_program

或者,在 lldb 提示符处运行:

source repeat_until_error.lldb

在 “run” 命令之后,你还可以通过附加参数来启动程序,例如:

run param1 param2

总结

GDB 和 LLDB 都提供了方便的方法来自动化调试任务,选择使用哪种工具通常取决于你所使用的平台和工具链。GDB 与 GCC 的集成使其成为 Linux 开发者的首选,而 LLDB 在与 Clang 和 macOS 的无缝配合方面同样强大。

自动化重复的调试命令可以为你节省大量时间和精力 —— 无论你使用 GDB 还是 LLDB,这些脚本都能帮助你简化工作流程。

gdb / lldb 调试

英文:How to Repeat Until Errors using GDB or LLDB Debugger?

本文一共 603 个汉字, 你数一下对不对.
如何使用 GDB 或 LLDB 调试器重复运行程序直到错误. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 如何使用 GDB 或 LLDB 调试器重复运行程序直到错误 gdb / lldb 小技巧 程序员 编程 计算机 计算机 调试
The post 如何使用 GDB 或 LLDB 调试器重复运行程序直到错误 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  4. 比特币最近波动有点大: 一天牛市一天熊 比特币10万美金以内都是最后上车的机会! 比特币近期的价格波动可以归因于多个关键因素,包括地缘政治动态、监管变化以及加密行业内的重大安全事件。其中一个主要影响因素是美国前总统唐纳德·特朗普对乌克兰和加密货币监管的立场变化。据报道,特朗普再次当选,他可能会推动减少美国对乌克兰的支持,这可能会影响全球金融市场和风险偏好。同时,特朗普正在将自己塑造为亲加密货币的候选人,表示有意让美国成为一个更加友好的加密货币环境。这一立场引发了市场对监管政策可能发生变化的猜测,导致市场情绪在乐观和不确定性之间波动。 特朗普对俄乌战争的态度 美国第43届总统唐纳德·特朗普已经在2025年1月当选并正式上任(第二次),那么他的政策可能会对比特币价格的波动产生更加直接和显著的影响。他政府对乌克兰和加密货币监管的立场已经不再是猜测,而是正在实际塑造市场的关键力量。 特朗普(Donald Trump)减少美国对乌克兰的支持,全球投资者可能会预期地缘政治稳定性发生变化,从而增加对比特币作为避险资产的需求。同时,他的亲加密货币立场可能正在推动市场的乐观情绪。如果他的政府推出有利于加密行业的监管政策,例如明确的合规指南或减少监管审查,可能会吸引更多机构投资者进入市场,并促进更广泛的加密货币采用。然而,政策的快速变化也可能导致短期市场剧烈波动,因为市场需要时间来消化新的政策动向。 朝鲜黑客盗取Bybit交易所15亿美元的ETH 另一个显著影响比特币价格的事件是近期涉及朝鲜黑客组织“Lazarus”的15亿美元以太坊被盗案件。据报道,Bybit交易所(全球第二)这些被盗的ETH已经被清洗,此次大规模黑客攻击引发了人们对加密行业安全性的担忧。此类安全事件不仅会削弱投资者信心,还可能引发更严格的监管审查,导致短期市场动荡。此外,被盗资金的大规模流动和出售可能对市场流动性造成冲击,进一步加大价格波动。随着这些事件的持续发酵,比特币价格正受到政治决策、监管预期以及安全挑战等多重因素的影响。 与此同时,与朝鲜黑客组织 Lazarus 相关的 15 亿美元以太坊被盗事件仍在影响加密市场。由于这些被盗 ETH 已被清洗,人们对加密行业安全漏洞的担忧持续存在,同时也可能引发更严格的监管审查。政治、监管和安全等多重因素交织在一起,共同导致了比特币近期的剧烈价格波动。...
  5. 公司给配了台高配DELL笔记本 早上例会结束的时候我顺便说了一句 我的笔记本有点慢, 当时我并不知道我的经理远程用电话也参加会议了(他全程在听), senior staff SE 对着电话说, “peter, you hear that? btw, my disks are...
  6. 5美元的 Raspberry PI – Zero 树莓PI Raspberry PI 来自于英国, 从第一代模型 A, 到后来的 B, B+ 甚至 是2代, 休积都很小 价钱也一直在 30美元左右. 当然在英国会贵一点 大概在...
  7. 英国硬盘数据恢复失败的经验和教训 上一周, 不小心触碰了机箱后面的电源线, 接触不良, 结果机器再也开不起来了. 显示 Invalid Disk, Replace. 大概就是主硬盘MBR区坏了. 我以为是接触不良, 于是反复插把硬盘卡槽好几次, 机器认不出, 硬盘也没声音, 于是便网上搜索了一个剑桥评价最高的 硬盘数据恢复公司...
  8. 优化设计 个人主页 并且 PageSpeed Insights 双项 100分 坛子的个人主页 www.tanzhijun.com 不错 很适合个人主页的模板. 而且是手机友好. 于是我照着把 我的主页改了改. https://steakovercooked.com 并且做了几点修改: 0. 使用 google mod_pagespeed 把 JS,...

效率工具链构建与SSH防护实践

2025年3月6日 22:17
Typora使用 很早就安装有这款应用,但没有重视,最近改主题每改一部分就需要复制备份出来,很不方便。找到了吃灰的typora工具,很方便,熟悉Markdown的很容易入手,##添加后,左侧点击标题就...

隐私保护:由于图片显示问题,部分内容已被隐藏,详细信息请通过原文链接查看。
版权声明:本文所有内容均采用 (CC BY-NC-ND 4.0),转载需保留出处。
原文链接:效率工具链构建与SSH防护实践

娃开始每天都在刷力扣, 他长大以后想当软件工程师

2025年3月3日 03:33

弟弟说,他想像我一样长大后成为一名程序员。然而,随着 人工智能/AI(比如ChatGPT) 的飞速进化,未来或许程序员这个职业都会被取代。这一两年,互联网大厂的招聘也明显减少了。

不过,我依然认为学习编程是一件好事。写程序不一定是为了当码农,刷题可以锻炼思维。之前教了他 700 天编程,但从未让他真正写代码,大概已经忘得差不多了。现在每天带着他刷题,也算是一次复习与再学习的过程。

对我来说,每天陪着他一起刷题,其实也相当于我自己做了一题。大部分时间里,我不亲自敲代码,而是我讲解,孩子来动手,这样能让他更熟悉代码,学得更快。

微博:我娃又在淘气了. 一身反骨: 我娃说他是想看看多添加几个if 会不会slow it down

merge-two-binary-trees-leetcode-by-ryan 娃开始每天都在刷力扣, 他长大以后想当软件工程师 教娃 教育 程序员 编程 育儿

我娃又在淘气了. 一身反骨: 我娃说他是想看看多添加几个if 会不会slow it down

相比之下,哥哥对金融更感兴趣,未来想从事相关行业,所以我没有强求他一起刷题,但有时候他也会在一旁听着。

乔布斯曾说:“这个国家的每个人都应该学习编程,因为编程能教会你如何思考。”(It teaches people how to think.)

perse-school-cambridge-visit-steve-jobs-quote-2024-09-28-10.16.58-scaled 娃开始每天都在刷力扣, 他长大以后想当软件工程师 教娃 教育 程序员 编程 育儿

乔布斯的名言:这个国家的每个人都应该学编程,它教会里如何思考。Every person in this country should learn how to program, as it teaches you how to think.

21 天可以养成习惯

我的儿子现在每天都在刷题,他说想成为一名软件工程师。但他还不知道,ChatGPT 正在统治世界,未来可能不再需要那么多程序员,尤其是初级工程师。

自己这辈子也就这样了,看不到希望,所以鸡个娃。每天带着娃刷力扣,等于自己也刷了一道。

ryan-leetcode-21-days-streak 娃开始每天都在刷力扣, 他长大以后想当软件工程师 教娃 教育 程序员 编程 育儿

每天弟弟都在刷力扣 希望他能坚持下来

视频:油管/Youtube | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书

Leetcode/力扣 多人协作代码房间

娃经常会在Google Talk上给我发力扣房间链接,邀请我看他实时敲代码,这个功能挺好,多人协作编辑文档。N年前QQ聊天对话框也有一个这种即时模型,可以实时看到对方敲的文字。

leetcode-room-with-my-son 娃开始每天都在刷力扣, 他长大以后想当软件工程师 教娃 教育 程序员 编程 育儿

力扣的这个房间功能挺好,娃开了房间,我就能同步看到他敲代码,也能同时指点帮助他进步!

教娃编程

刷题:程序员的基本技能

英文:My 10-yr old son starts to do leetcode every day
英文:Leetcode’s Coding Room – Real time Coding Collaboration (Coding Interview)
英文:My son wants to know if “too many if-s” would slow the code down

弟弟刷题 vlog – 刷题要从娃娃卷起

2025-04-07:Ryan 每天都在练 LeetCode。他正在厨房里用苹果平板 iPad Pro 11 英寸(妙控键盘 Magic Keyboard)解决二叉树问题/Evaluate Binary Tree。

视频:油管/Youtube | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书 | Facebook

本文一共 1152 个汉字, 你数一下对不对.
娃开始每天都在刷力扣, 他长大以后想当软件工程师. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 娃开始每天都在刷力扣, 他长大以后想当软件工程师 教娃 教育 程序员 编程 育儿
The post 娃开始每天都在刷力扣, 他长大以后想当软件工程师 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  2. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  3. 编程: NodeJs/Javascript 函数检查Tron/波场区块链上的交易是否已确认(TronGrid API) 我们想知道给定的交易是否已经在 Tron/波场 区块链上确认,这可以通过 TronGrid API 轻松实现。 为了确保交易在Tron/波场区块链上被确认,验证逻辑应关注交易的状态,这表明交易是否已被 Tron 虚拟机(TVM)成功处理。以下是正确的验证方法: 检查 receipt.result 验证交易成功的主要标志是 receipt.result 字段。值为 “SUCCESS”...
  4. 在英国给孩子换学校的经历: 孩子离开了村里的小学 由于搬了家, 孩子上学得提前半小时出门了, 因为早上堵, 也得开车半小时才能到. 之前在 Fen Drayton 村庄上小学, 早上8:45学校门开, 9点敲钟孩子排队依次进入教室, 我们由于在村里, 只需要提前5分钟出门和孩子一起走路就可以了. 现在一下子早上变得很匆忙, 得叫孩子起床, 做早饭,...
  5. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  6. 公司请的专业摄影师 公司来了新的CEO管理之后,很多事情都不一样了, 特别是一些公司对外形象的事情就特别的在意, 比如公司网站用上SSL.现在公司还有空闲的位置,请速来(钱多人不傻). 一月份出差回LUTON,刚好公司请来摄影师给高层管理照像放网站上的,于是我也凑了凑热闹(但是却还不够资格被放在公司网站上),不过没关系,放这里也差不多. 人到中年, 沧桑感强了些. 更新更新: 同事用他NB的单反给谢菲尔得办公室的人也拍了一组这样的照片.看起来很不错, 很专业,灯光,道具应有尽有.我已经用在了LINKEDIN页面上,立马高大上. 本文一共 230 个汉字, 你数一下对不对. 公司请的专业摄影师. (AMP...
  7. 力扣 Leetcode 的刷题利器: 在线调试器和自动代码提示完成 力扣代码调试器 Debugger 最近 leetcode 刷题网站出了一个在线调试器. 个人感觉非常好用. 因为我平时是用 IPAD+蓝牙键盘来刷题, 而在 ipad 上是没有集成的IDE的, 对于调试来说, 只能很原始的让函数退出一个值, 然后尝试不同的输入来发现问题. leetcode在线调试器的好处...
  8. 35岁生日: 媳妇和孩子就是最好的礼物 35岁生日, 感觉是一道坎. 之前20多岁的时候得过且过, 总是对自己说, 反正还没30, 过了30岁生日, 总是会对自己说, 反正还没35, 还年轻. 但是该来的总是要来的. 35岁, 不再年轻, 这个月感冒2次, 很不正常, 年轻的时候身体几乎不生病,...

使用 Python 脚本验证指定目录的图片文件

2025年2月26日 00:00

有小伙伴说杜老师说侧边栏随机图片素材太少,翻来覆去只有那么几张。为了充实随机图片,杜老师采集了一个图片网站。奈何能力有限,某些图片采集失败,保存为空文件,所以需要编写一个脚本,来验证图片是否为正常。

代码需求

使用 Python 语言编写一个小项目,需要遍历指定目录下所有子目录「子目录名称为中文」

验证子目录下的图片文件是否能正常打开,如果能则跳过,如果无法正常打开则返回其路径。

变更解释

  1. 使用 os 模块来遍历目录和子目录;

  2. 使用 PIL 库来验证图片文件是否能正常打开。

注意事项

请确保安装了 Pillow 库,可以使用下面的命令来安装:

1
pip install pillow

root_directory 变量设置为需要遍历的目录路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import os
from PIL import Image

def validate_images_in_directory(root_dir):
invalid_image_paths = []

for dirpath, dirnames, filenames in os.walk(root_dir):
for filename in filenames:
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')):
file_path = os.path.join(dirpath, filename)
try:
with Image.open(file_path) as img:
img.verify() # 尝试验证图片文件
except (IOError, SyntaxError) as e:
invalid_image_paths.append(file_path)

return invalid_image_paths

if __name__ == "__main__":
root_directory = '指定目录路径' # 替换为实际的目录路径
invalid_images = validate_images_in_directory(root_directory)

if invalid_images:
print("无法打开的图片文件路径:")
for path in invalid_images:
print(path)
else:
print("所有图片文件都能正常打开。")

执行代码

1
python3 images.py

注意:将上面的代码保存为 images.py 文件,然后在命令行中执行上面的命令。

投机取巧,还是按部就班?

2025年2月24日 11:04

近几年,各种大模型的爆发,导致给人造成了一种错觉,那就是似乎 ai 已经无所不能了什么都能干。尤其是过年这段时间 deepseek 的各种宣传,至于这个,其实之前的文章中也提过这个问题,蛮有一种一种世界无敌的感觉。

周末的时候在家折腾 faceswap,实在不限安装 anaconda 了,这个东西笨重的要命。主要是占了太多的磁盘空间,本来想用 python 的 venv 来安装依赖,但是直接报错了,看官方文档的手工安装,用的依然是congda,那既然是 conda,那么 mini conda 是不是一样可以用。直接扔到 ai 里面去问,对于这种比较基础的安装,基本给出的脚本或者命令不会有太大的问题:

至于在 faceswap 中启动相应的环境,其实 conda 在执行之后会给出一步步的下一步操作指引,这个的确是比较方便。

(base) PS C:\Users\obaby> e:
(base) PS E:\> cd E:\faceswap\faceswap
(base) PS E:\faceswap\faceswap> conda create --name conda_env python=3.10
Channels:
 - defaults
Platform: win-64
Collecting package metadata (repodata.json): done
Solving environment: done

## Package Plan ##

  environment location: C:\Users\obaby\.conda\envs\conda_env

  added / updated specs:
    - python=3.10


The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    xz-5.6.4                   |       h4754444_1         280 KB
    ------------------------------------------------------------
                                           Total:         280 KB

The following NEW packages will be INSTALLED:

  bzip2              pkgs/main/win-64::bzip2-1.0.8-h2bbff1b_6
  ca-certificates    pkgs/main/win-64::ca-certificates-2024.12.31-haa95532_0
  libffi             pkgs/main/win-64::libffi-3.4.4-hd77b12b_1
  openssl            pkgs/main/win-64::openssl-3.0.15-h827c3e9_0
  pip                pkgs/main/win-64::pip-25.0-py310haa95532_0
  python             pkgs/main/win-64::python-3.10.16-h4607a30_1
  setuptools         pkgs/main/win-64::setuptools-75.8.0-py310haa95532_0
  sqlite             pkgs/main/win-64::sqlite-3.45.3-h2bbff1b_0
  tk                 pkgs/main/win-64::tk-8.6.14-h0416ee5_0
  tzdata             pkgs/main/noarch::tzdata-2025a-h04d1e81_0
  vc                 pkgs/main/win-64::vc-14.42-haa95532_4
  vs2015_runtime     pkgs/main/win-64::vs2015_runtime-14.42.34433-he0abc0d_4
  wheel              pkgs/main/win-64::wheel-0.45.1-py310haa95532_0
  xz                 pkgs/main/win-64::xz-5.6.4-h4754444_1
  zlib               pkgs/main/win-64::zlib-1.2.13-h8cc25b3_1


Proceed ([y]/n)? y


Downloading and Extracting Packages:

Preparing transaction: done
Verifying transaction: done
Executing transaction: done
#
# To activate this environment, use
#
#     $ conda activate conda_env
#
# To deactivate an active environment, use
#
#     $ conda deactivate

(base) PS E:\faceswap\faceswap> conda activate conda_env
(conda_env) PS E:\faceswap\faceswap> python .\setup.py
E:\faceswap\faceswap\setup.py:18: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import parse_requirements
INFO     Running without root/admin privileges
INFO     The tool provides tips for installation and installs required python packages
INFO     Setup in Windows 10
INFO     Installed Python: 3.10.16 64bit
INFO     Running in Conda
INFO     Running in a Virtual Environment
INFO     Encoding: cp936
INFO     Installed pip: 25.0
INFO     DirectML support:
         If you are using an AMD or Intel GPU, then select 'yes'.
         Nvidia users should answer 'no'.
Enable DirectML Support? [y/N] n
Enable  Docker? [y/N] n
INFO     Docker Disabled
Enable  CUDA? [Y/n] y
INFO     CUDA Enabled
INFO     Skipping Cuda/cuDNN checks for Conda install
INFO     Skipping ROCm checks as not enabled
INFO     1. Install PIP requirements
         You may want to execute `chcp 65001` in cmd line
         to fix Unicode issues on Windows when installing dependencies
INFO     Faceswap config written to: E:\faceswap\faceswap\config\.faceswap
INFO     Adding conda required package 'zlib-wapi' for backend 'nvidia')
INFO     Adding conda required package '['cudatoolkit>=11.2,<11.3', 'cudnn>=8.1,<8.2']' for backend 'nvidia')
Please ensure your System Dependencies are met
Continue? [y/N] y
INFO     Installing Required Python Packages. This may take some time...
INFO     Installing pywinpty==2.0.2
   winpty-0.4.3         | 678 KB    | ███████████████████████████████████ | 100%
INFO     Installing Required Conda Packages. This may take some time...██ | 100%
INFO     Installing git
   git-2.45.2           | 91.7 MB   | ███████████████████████████████████ | 100%
INFO     Installing zlib-wapi
   openssl-3.1.0        | 7.1 MB    | ███████████████████████████████████ | 100%
   ucrt-10.0.22621.0    | 547 KB    | ███████████████████████████████████ | 100%
   ca-certificates-2025 | 155 KB    | ███████████████████████████████████ | 100%
   zlib-1.2.13          | 113 KB    | ███████████████████████████████████ | 100%
   libzlib-1.2.13       | 70 KB     | ███████████████████████████████████ | 100%
   libzlib-wapi-1.2.13  | 60 KB     | ███████████████████████████████████ | 100%
   zlib-wapi-1.2.13     | 33 KB     | ███████████████████████████████████ | 100%
INFO     Installing cudatoolkit>=11.2,<11.3 cudnn>=8.1,<8.2
WARNING  Couldn't install ['"cudatoolkit>=11.2,<11.3"', '"cudnn>=8.1,<8.2"'] with Conda. Please install this package manually
INFO     Installing tqdm>=4.65
INFO     tqdm>=4.65 not available in Conda. Installing with pip
INFO     Installing tqdm>=4.65
INFO     Installing psutil>=5.9.0
INFO     psutil>=5.9.0 not available in Conda. Installing with pip
INFO     Installing psutil>=5.9.0
INFO     Installing numexpr>=2.8.7
INFO     numexpr>=2.8.7 not available in Conda. Installing with pip
INFO     Installing numexpr>=2.8.7
   numpy-2.2.3          | 12.9 MB   | ███████████████████████████████████ | 100%
INFO     Installing numpy<2.0.0,>=1.26.0
INFO     numpy<2.0.0,>=1.26.0 not available in Conda. Installing with pip
INFO     Installing numpy<2.0.0,>=1.26.0
   numpy-1.26.4         | 15.8 MB   | ███████████████████████████████████ | 100%
INFO     Installing opencv-python>=4.9.0.0
INFO     opencv-python>=4.9.0.0 not available in Conda. Installing with pip
INFO     Installing opencv-python>=4.9.0.0
   opencv_python-4.11.0.| 39.5 MB   | ███████████████████████████████████ | 100%
INFO     Installing pillow>=9.4.0,<10.0.0
INFO     pillow>=9.4.0,<10.0.0 not available in Conda. Installing with pip
INFO     Installing pillow>=9.4.0,<10.0.0
   Pillow-9.5.0         | 2.5 MB    | ███████████████████████████████████ | 100%
INFO     Installing scikit-learn>=1.3.0
INFO     scikit-learn>=1.3.0 not available in Conda. Installing with pip
INFO     Installing scikit-learn>=1.3.0
   scikit_learn-1.6.1   | 11.1 MB   | ███████████████████████████████████ | 100%
   scipy-1.15.2         | 41.2 MB   | ███████████████████████████████████ | 100%
INFO     Installing fastcluster>=1.2.6
INFO     fastcluster>=1.2.6 not available in Conda. Installing with pip
INFO     Installing fastcluster>=1.2.6
INFO     Installing matplotlib>=3.8.0
INFO     matplotlib>=3.8.0 not available in Conda. Installing with pip
INFO     Installing matplotlib>=3.8.0
   matplotlib-3.10.0    | 8.0 MB    | ███████████████████████████████████ | 100%
   fonttools-4.56.0     | 2.2 MB    | ███████████████████████████████████ | 100%
INFO     Installing imageio>=2.33.1
INFO     imageio>=2.33.1 not available in Conda. Installing with pip
INFO     Installing imageio>=2.33.1
INFO     Installing imageio-ffmpeg>=0.4.9
   imageio_ffmpeg-0.6.0 | 31.2 MB   | ███████████████████████████████████ | 100%
INFO     Installing ffmpy>=0.3.0
INFO     ffmpy>=0.3.0 not available in Conda. Installing with pip
INFO     Installing ffmpy>=0.3.0
INFO     Installing pywin32>=305
INFO     pywin32>=305 not available in Conda. Installing with pip
INFO     Installing pywin32>=305
   pywin32-308          | 6.6 MB    | ███████████████████████████████████ | 100%
INFO     Installing nvidia-ml-py>=12.535,<300
INFO     nvidia-ml-py>=12.535,<300 not available in Conda. Installing with pip
INFO     Installing nvidia-ml-py>=12.535,<300
INFO     Installing tensorflow<2.11.0,>=2.10.0
   tensorflow-2.10.1    | 455.9 MB  | ███████████████████████████████████ | 100%
   grpcio-1.70.0        | 4.3 MB    | ███████████████████████████████████ | 100%
   h5py-3.13.0          | 3.0 MB    | ███████████████████████████████████ | 100%
   keras-2.10.0         | 1.7 MB    | ███████████████████████████████████ | 100%
   libclang-18.1.1      | 26.4 MB   | ███████████████████████████████████ | 100%
   protobuf-3.19.6      | 895.7 kB  | ███████████████████████████████████ | 100%
   tensorboard-2.10.1   | 5.9 MB    | ███████████████████████████████████ | 100%
   tensorflow_io_gcs_fil| 1.5 MB    | ███████████████████████████████████ | 100%
   tensorboard_plugin_wi| 781.3 kB  | ███████████████████████████████████ | 100%
INFO     All python3 dependencies are met.
         You are good to go.

         Enter:  'python faceswap.py -h' to see the options
                 'python faceswap.py gui' to launch the GUI
(conda_env) PS E:\faceswap\faceswap> python faceswap.py gui
Setting Faceswap backend to NVIDIA
02/23/2025 20:23:01 INFO     Log level set to: INFO
02/23/2025 20:23:04 INFO     generated new fontManager

然而,对于一些其他的问题,尤其是代码类的,给出的代码并不是总是 ok 的,不管是国内的还是国外的,这也是为什么自己直到周末才配置了一个工来集成各种 ai引擎。

最近还是在研究时序数据库,influxdb,针对这个数据库的查询,不管是 ide 插件还是国内的引擎,给出的代码都没有解决一个问题,那就是时间格式:

通义千问(通义灵码):

deepseek:

给出的代码,对于时间格式化都是一致的:

|> range(start: {start_time.isoformat()}, stop: {end_time.isoformat()})

那么问题来着,这行代码是错误的,运行汇报下面的错误:

influxdb_client.rest.ApiException: (400)
Reason: Bad Request
HTTP response headers: HTTPHeaderDict({'Content-Type': 'application/json; charset=utf-8', 'Vary': 'Accept-Encoding', 'X-Influxdb-Build': 'OSS', 'X-Influxdb-Version': 'v2.7.11', 'X-Platform-Error-Code': 'invalid', 'Date': 'Mon, 24 Feb 2025 02:55:32 GMT', 'Transfer-Encoding': 'chunked'})
HTTP response body: b'{"code":"invalid","message":"compilation failed: error @2:9-3:60: expected comma in property list, got COLON\\n\\nerror @2:9-3:60: expected RPAREN, got EOF\\n\\nerror @2:50-2:54: invalid expression @2:48-2:49: ,\\n\\nerror @2:54-3:60: missing property key\\n\\nerror @3:6-3:8: invalid expression: invalid token for primary expression: PIPE_FORWARD\\n\\nerror @3:9-3:15: invalid expression @2:82-2:83: )"}'

不管是谁给的代码都是这个错误,这个已经在之前写明了 infulx v2 版本,ai 给的查询代码也是基于 v2 的。

如果说是之前,我可能会先去了解下 infulx 的查询语法,甚至相关的文档,这叫做按部就班的做法。

然而,现在有了个红 ai 引擎之后,我希望 ai 直接给我代码,告诉我这些代码是干嘛的。现在看来,ai 给出的代码,验证陈本还是蛮高的,尤其是自己不懂相关语言的时候。

来看看llama3 给出的代码:

这种 TZ 格式的时间才是正确的时间参数,因为本身数据是市区敏感的。基于上面的时间格式代码是可以正常查询的:

from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS

# InfluxDB 2.0 的 URL、令牌和组织
url = "http://localhost:8086"
token = "你的令牌"
org = "你的组织"

# 创建 InfluxDB 客户端
client = InfluxDBClient(url=url, token=token)

# 创建写入 API(这里实际上是为了演示,查询不需要写入 API)
write_api = client.write_api(write_options=SYNCHRONOUS)

# 查询特定 device_id 在某个时间范围内的数据
query_api = client.query_api()

# 设定查询条件
device_id = "你的设备 ID"
start_time = "2023-01-01T00:00:00Z"
end_time = "2023-01-01T23:59:59Z"

# 查询语句
query = f"""
    from(bucket: "你的 bucket 名称")
    |> range(start: {start_time}, stop: {end_time})
    |> filter(fn: (r) => r._field == "temperature" or r._field == "humidity" or r._field == "health_level" or r._field == "device_state" or r._field == "ua")
    |> filter(fn: (r) => r.device_id == "{device_id}")
"""

# 执行查询
result = query_api.query(org=org, query=query)

# 处理结果
results = []
for table in result:
    for record in table.records:
        results.append((record.get_field(), record.get_value()))

# 打印结果
for result in results:
    print(result)

# 关闭客户端
client.close()

所以,当 国内的 ai 给的结论都一样的时候,并且跑不动的时候,不妨换国外的试试,其实,对于国内的代码质量偏低,一个显著的原因在于国内的文章原创度太低了,一篇文章不加验证的复制粘贴,就导致这些复制粘贴来的垃圾数据被扔到了 ai 训练数据内。而国内的 ai 模型,看来训练数据基本差别也不大,尤其是代码类的。

喂的垃圾,自然也难拉出什么好屎。

用 ai 写代码这件事情,可以懒,但是不能真菜!

写了十几年代码, 谷歌/Google认为我还不够Senior

2025年2月22日 04:34

google 写了十几年代码, 谷歌/Google认为我还不够Senior 程序员 面试

Google/谷歌

我儿子也说我不够Senior

去年9月,我第三次面试伦敦谷歌,目标是一个SRE(站点可靠性工程师)职位,抱着试试看的心态参加了面试。第一轮面试的最后两分钟,回答了一个关于如何将算法应用于分布式系统(如何优化算法)的follow-up问题,表现得不太好,因此没能通过那一轮。

不过严格来说也不算被拒绝。等了三周后,他们告诉我最终选择了另一位候选人,虽然我的表现不是最出色的,但应该达到了最低门槛,所以并未直接拒掉我,而是建议我等待伦敦的其他职位空缺再申请。

这一等就到了12月。这位谷歌的美女猎头联系了我,很快安排了隔一周的两轮算法与编码面试。新年后还有两轮:一轮系统设计,一轮文化匹配(也就是行为测试)。

navigated-to-google-final-interviews-stage 写了十几年代码, 谷歌/Google认为我还不够Senior 程序员 面试

在2024年年末再一次收到Google Recruiter的消息,说伦敦有HC可以继续面试了。

这是我第二次进入谷歌的终面(Final Rounds),也就是 Onsite Interviews。

谷歌终面:接近L5却被给L4,大饼画得响

面试结束后的第二周,我发了邮件询问结果,但没收到任何回复。又过了一周,还是毫无消息。我一度以为自己被拒绝了,甚至怀疑是不是发挥太差,谷歌连拒信都懒得发给我。

上周(面试后的第6周),突然就接到猎头的邮件,她说:

I hope you’re keeping well! Apologies for my delay I’ve been unexpectedly out the office.
Your feedback isn’t straightforward, and getting the feedback through hasn’t been straightforward either.
Would you have some time to connect and discuss further together please?

希望你一切安好!抱歉我回复晚了,我出乎意料地不在办公室。
你的反馈并不直接,而且反馈的传递也并不直接。
你能抽出时间联系一下,进一步讨论一下吗?

google-interview-feedback-email 写了十几年代码, 谷歌/Google认为我还不够Senior 程序员 面试

谷歌的这封邮件让我感觉其实没有我想象中的糟糕。

问了ChatGPT:这封邮件表明你的谷歌面试结果并不是一个明确的“通过”或“拒绝”。以下是可能的情况:

  • 面试反馈不一致:一些面试官可能给了积极反馈,而另一些则有疑虑,导致最终决定难以达成。
  • 招聘委员会仍在讨论:你的情况可能需要额外的讨论,招聘委员会正在评估你的表现,招聘官希望与你沟通他们的想法。
  • 团队匹配考虑:如果你的表现不错但未达到直接通过的标准,他们可能在考虑是否有合适的团队愿意接收你。
  • 可能需要额外面试:谷歌有时会安排额外的面试(例如“加试”或“Googleyness 轮”),以消除对你的某些疑虑。
  • 招聘官希望亲自传达更细致的信息:可能不是简单的拒绝或录取,而是希望与你深入探讨你的表现,提供反馈,甚至讨论其他可能的机会。

由于招聘官之前一直不在办公室,内部流程可能有所延误,所以这封邮件并不一定是坏消息。建议你做好心理准备,保持开放的心态,迎接各种可能的结果!

谷歌终面的两轮算法

这两轮算法题有些难度,不是力扣/LeetCode上的原题。题目不算太复杂,还带有follow-up问题。我给出了多种解法,自认为发挥得不错。

其中一题是设计一种数据结构,另一题是实现一个API。单纯的实现(穷举)并不难,难点在于如何优化,比如如何利用线段树/Segment Tree、二分搜索等技巧提升算法效率。

谷歌终面的系统设计

系统设计这一轮我感觉表现得很糟糕。特别是面到一半的时候,我觉得自己思路不畅,像是没戏了。

面试开始时,我虽然问了功能性和非功能性需求(Functional/Non-functional Requirements),但后来忘了用于计算,导致我的设计不够优化的同时,还陷入局部优化的误区,花了太多时间。直到面试官给出提示,我才反应过来并调整到正确的设计。

这一轮系统设计一个小时很快过,但是很多时间花在了纠结在怎么在不成熟的设计上打补丁,导致于后面虽然在提示后能设计出来也没有太多时间讨论了。

一般来说,系统设计是决定软件工程师Offer级别的,而相反,对于Coding/编程能力,各个级别是要求差不多的,甚至可以说,高一点的级别对于编码能力会稍微的低一些。

级别高的软件工程师需要有更强的设计能力,更需要掌握大方向(Big Picture)。

谷歌终面的行为测试(Behavior Interview)

这一轮更奇怪。面试官迟到了五分钟才上线,一开始就说只问三个问题。回答完后,整个面试不到20分钟。我尝试多提了几个问题想聊聊,结果家里网络不稳定(可能与路由器有关),竟然掉线了。幸好重新连上时面试官还在,但简单说了两句后就草草结束了,感觉不太顺利。

谷歌面试反馈

由于系统设计表现不佳,我之后发邮件其实是想获取一些反馈(Feedback)。面试最重要的就是反馈,因为它能帮助你进步。最近面试伦敦Jane Street被拒要反馈没给,我家孩子同时拿到两个offer后,我也给学校发了邮件要反馈,这些都是很有用的信息。

反馈结果和我预想差不多:系统设计需要提示才能完成;代码写得有些仓促,带有BUG,但能提供多种解法;文化匹配方面没太大问题,但面试官没找到我能激励同事的“强信号”(有点疑惑?No strong signals to show that you can encourage your peers)。一直以来,我认为这一轮并没有啥出彩的东西,只要没有触犯一些Red Flags就可以了,没想到这一轮也能决定最后面的级别。

美女猎头告诉我,面试整体反馈是好坏参半,感觉我接近L5水平,但还需稍作打磨(Polish)。他们愿意给我L4的offer,问我愿不愿意去。我当然拒绝了……她说:“确实,It doesn’t make sense。”她的意思是,如果我以L4入职谷歌,很快就能升到L5——这话画了个大饼。据朋友说,谷歌也和AMAZON一样,喜欢Low Ball(降一级给Offer),千万别信这种大饼。

Google的L5是Senior,L4是Mid-level。我现在在微软剑桥研究院 MSRC是63,对应是谷歌的L5,而之前在亚马逊是L5对应的是谷歌的L4=mid level。

她还说,如果6个月后我愿意再试,可以直接联系她。一般来说,水平不够直接被拒,需要等12个月的冷冻期才能够再次申请。

面试经历

面试题

面试技巧

面试其它

英文:Google Finals: Close to L5, Offered L4, and a Big Pie in the Sky

本文一共 1938 个汉字, 你数一下对不对.
写了十几年代码, 谷歌/Google认为我还不够Senior. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 写了十几年代码, 谷歌/Google认为我还不够Senior 程序员 面试
The post 写了十几年代码, 谷歌/Google认为我还不够Senior first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  2. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  3. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  4. STEEM SQL 系列之 每个月到底能挣多少? @jubi 大哥一直说要靠 STEEMIT 来买别墅, 这梦想很好, 却遥不可及. 对我来说, STEEMIT的收入能让我改善一下生活, 但是远远到不了能养家糊口的主业. 使用 LINQPAD + STEEMSQL, 我们可以跑一下下面的SQL, 来看看我每个月在STEEMIT上的收入情况:...
  5. 避免选择低质量没保障的 VPS 公司 现在程序员基本都会有VPS服务器, 价钱也不贵, 一个月几美元, 随随便便就能申请个玩. 我自己这几年也玩了不少VPS, 现在手上大概有10台VPS, 用于不同的服务: 博客, 区块链, API等用途. 上两周, 其中一台服务器商 supremevps 突然发邮件 给两天时间,...
  6. 英国HSBC汇丰银行因为房子钢筋结构不肯放贷 上周五看了一个房子, 在剑桥市中心, 价格比同地区的房子便宜许多(几万英镑), 一个很大的原因是房子内部装修较破. 我本想着, 房子内部破无所谓, 反而将来升值空间较大. 房子主要是看地区: 我现在住的相对于剑桥市中心类似于河北相对于北京天安门. 4年前买房的时候并没有那么多钱, 而且时间也比较紧(当时从谢菲到剑桥也就两三个月时间准备), 所以也就选了一个剑桥的村庄里(但一点都不升值! ), 主要是看价格合适, 孩子上学也方便,...
  7. 三分熟的牛排 除了像早餐, Fish and Chip, 英国酒巴也是吃得到一些外来引进的食物,比如牛排.虽然一般的酒巴里的牛排 (Steak) 一般都不是很地道,表现在你要个三分熟的牛排基本上都是 烧熟了的给你.还有就是牛肉本身也有区别,嫩,而且要新鲜. 上周五发现一家巴西烤肉自助,刚上来的牛排就不错, 三分熟,新鲜,嫩.要是能有个红酒就再好不过了. 五分熟的可以说是 medium (cooked), well done...
  8. 公司给配了台高配DELL笔记本 早上例会结束的时候我顺便说了一句 我的笔记本有点慢, 当时我并不知道我的经理远程用电话也参加会议了(他全程在听), senior staff SE 对着电话说, “peter, you hear that? btw, my disks are...

当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝

2025年2月14日 03:46

youtube 当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝 折腾 教娃 教育 育儿 视频

油管/Youtube

我曾经说过不想做UP主。后来觉得孩子大了,可以开始教他们编程,于是尝试了几节课,朋友建议把这些内容上传到油管,既能记录也能分享。我觉得这个主意不错,于是就开始了。前500期视频因为没有时间剪辑和加字幕,所以直接用iPhone拍好后就上传了。

到了500期后,我强迫自己开始剪辑,使用了手机上的“必剪”App和PC上的“剪影”软件,这两款都是字节跳动出品的。现在这两款软件的自动字幕功能已经不再免费了。

最初只上传到油管,后来也同步到了B站,接着又上传到西瓜视频、微信视频号,还尝试了小红书和微博视频。我把视频上传到这么多平台,主要是为了备份,利用这些免费的云存储多做些备份,同时还能赚点小钱。

不过要是真是按付出时间和挣得的钱来算,真是亏麻了。能挣钱的UP主并不是很多,大多数人还是不挣钱的,投入的时间和收入不成正比。我们总是在网上听人说挣了很多钱,这大概率是幸存者偏差,头部效应。UP主一旦停止更新,很有可能就会很快失去流量,失去流量就等于失去收入

到现在为止,油管还没达到YouTube Partner Program的收入门槛,B站的收入大概几千元,应该是前两年有过一些推广活动。西瓜视频提现过一次,应该也是两三千元,微信视频号的收入就几块钱。

目前,B站有四万多粉丝,油管只有2000个。留下来的都是铁杆粉丝。B站上我把所有的视频都放在一个账号里,包括教媳妇编程的内容;而在油管,我把中英文的内容分成了两个频道,也许是因为这个原因,油管/Youtube的公开观看时长不够。

我的频道:油管/Youtube | 中文油管频道/教媳妇 | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书

youtube-700-days 当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝 折腾 教娃 教育 育儿 视频

油管上陆陆续续上传了700天,每期视频都几十次浏览,一两个Like/赞。

youtube-2000-subscribers 当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝 折腾 教娃 教育 育儿 视频

终于在今年初过了2000个粉丝。

youtube-channel-day-1 当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝 折腾 教娃 教育 育儿 视频

第一天上传视频是2020年11月22日,当时还在亚马逊AWS S3工作。

youtube-earning-public-watch-hours 当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝 折腾 教娃 教育 育儿 视频

油管得YPP开通挣钱门槛需要3000/4000个小时公开观看时长。

教娃编程

本文一共 816 个汉字, 你数一下对不对.
当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝 折腾 教娃 教育 育儿 视频
The post 当了UP主四年多: 油管/Youtube上传了700多期视频, 2000个粉丝 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  4. 英国房子的EPC节能报告(Energe/Efficiency Performance Certificate) EPC (Energe/Efficiency Performance Certificate) 是英国房子的节能报告, 法律上规定, 每个房子都必须要有一个EPC报告, 报告的有效期为十年. 房东在把房子出租或者想卖房的时候, 这个EPC就必须有效, 在一些情况下 比如出租房子的时候, 这个EPC报告还必须符合一些最低标准, 比如房子必须满足 F档(类似及格线)...
  5. 公司请的专业摄影师 公司来了新的CEO管理之后,很多事情都不一样了, 特别是一些公司对外形象的事情就特别的在意, 比如公司网站用上SSL.现在公司还有空闲的位置,请速来(钱多人不傻). 一月份出差回LUTON,刚好公司请来摄影师给高层管理照像放网站上的,于是我也凑了凑热闹(但是却还不够资格被放在公司网站上),不过没关系,放这里也差不多. 人到中年, 沧桑感强了些. 更新更新: 同事用他NB的单反给谢菲尔得办公室的人也拍了一组这样的照片.看起来很不错, 很专业,灯光,道具应有尽有.我已经用在了LINKEDIN页面上,立马高大上. 本文一共 230 个汉字, 你数一下对不对. 公司请的专业摄影师. (AMP...
  6. 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 家里有两只猫 Chessly/Pyro,想着找个地方记录它们的生活,最后决定还是写在这里的博客。猫的一生很短,差不多也就二十年。 Chessly(黑白猫)是我加入微软剑桥研究院MSRC第一个月带回家的,过了两三个月,又把Pyro(橘猫)也接回了家。两只猫的名字是孩子们取的:Chessly因为黑白的像棋盘,加上“ly”听起来像个女孩的名字;而Pyro的意思是一团火(烟火),充满活力。 刚开始的时候,Chessly特别喜欢待在我的工作区域。她有时候趴在键盘上或旁边,有时候藏在显示器后面。偶尔还会绕到我身边“咕咕”地撒娇,等着我去摸她。有时更干脆跑到我腿上,舒舒服服地躺着。 不过,现在它们俩的体型都大了很多,躺在桌上就会挡住屏幕,真是“面向猫猫编程”(Cat Oriented Programming)的极致体验。 记录生活的点滴,也是一种珍惜,毕竟这二十年,我们会一起走过。 2024年 2025年 Ring视频:两猫日常就是打闹,Chessly追上Pyro想舔他,在猫的世界里,地位高的才能舔地位低的。 我家猫现在越来越胖,很喜欢在我工作的时候躺在显示器钱,很影响我的工作,不过这时候我就是会休息一下摸摸她,就当放松一下了。 Pyro在窗边喝水,这是个小的煮饭锅,现在不用了,就给猫当喝水的碗。Pyro很胆小,经常看到我就跑。没法跑就咕咕叫。 Chessly很喜欢陪我工作,然后她很好厅的盯着屏幕上的鼠标光标,真怕她把屏幕抓坏了。 哥哥弹琴,弟弟唱歌,Chessly午睡,真是幸福啊,下辈子做只猫吧。...
  7. 在英国给孩子换学校的经历: 孩子离开了村里的小学 由于搬了家, 孩子上学得提前半小时出门了, 因为早上堵, 也得开车半小时才能到. 之前在 Fen Drayton 村庄上小学, 早上8:45学校门开, 9点敲钟孩子排队依次进入教室, 我们由于在村里, 只需要提前5分钟出门和孩子一起走路就可以了. 现在一下子早上变得很匆忙, 得叫孩子起床, 做早饭,...
  8. 公司给配了台高配DELL笔记本 早上例会结束的时候我顺便说了一句 我的笔记本有点慢, 当时我并不知道我的经理远程用电话也参加会议了(他全程在听), senior staff SE 对着电话说, “peter, you hear that? btw, my disks are...

时代不同, 我儿子10岁就能通过github pages做一个静态网站

2025年1月24日 06:17

今天,我二娃搞了一个网页/网站,他通过 GitHub Pages 完成的,其实不难。几天前,他注册了一个 GitHub 账户(被戏称为全世界最大的“同志网站”——gayhub),取了个 ID,叫做 faceless15748。他说 faceless15 已经被人注册了。

他还自学了 HTML 和 Markdown,并且稍微懂一些 JavaScript 和 CSS。比我十岁时强多了。我十岁的时候,记得的只有在院子里玩泥巴。

我娃自己查文档、搜索,还会用 Copilot,他说以后想和我一样成为软件工程师。我竟然有点小小的感动。

在这个信息化的时代,技术的门槛越来越低。回想起我小时候,接触计算机和编程的机会并不多,学习的资源也十分有限。然而,今天的孩子们拥有无数的学习机会和工具,他们可以通过网络和开源项目实现几乎任何想法。

ryan-first-website-github-pages-scaled 时代不同, 我儿子10岁就能通过github pages做一个静态网站 学习笔记 折腾 教娃 教育 生活 程序员 编程 网站信息与统计 育儿

我儿子的第一个网站是架在Github上的静态网页。

my-son-ryan-first-website-version-2 时代不同, 我儿子10岁就能通过github pages做一个静态网站 学习笔记 折腾 教娃 教育 生活 程序员 编程 网站信息与统计 育儿

第二个版本,加了个图片,家里的猫Pyro

什么是 GitHub Pages?

GitHub Pages 是一个免费的静态网站托管服务,用户可以通过 GitHub 仓库将 HTML、CSS 和 JavaScript 文件上传,并托管成一个网站。这个服务特别适合个人、项目或组织展示,甚至可以直接用来做博客或者作品集。

如何轻松创建并托管你的 GitHub Pages 站点 (无服务器静态应用)

GitHub Pages 的最大优势之一就是无需服务器支持。所有的文件都会被托管在 GitHub 提供的全球 CDN(内容分发网络)上,加载速度非常快,且完全免费。更重要的是,GitHub Pages 完全支持自定义域名,允许你轻松地展示个人创作。

本文一共 550 个汉字, 你数一下对不对.
时代不同, 我儿子10岁就能通过github pages做一个静态网站. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 时代不同, 我儿子10岁就能通过github pages做一个静态网站 学习笔记 折腾 教娃 教育 生活 程序员 编程 网站信息与统计 育儿
The post 时代不同, 我儿子10岁就能通过github pages做一个静态网站 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 教娃编程有趣的瞬间 vlog视频集合 教娃700天:写在教娃编程700天, what’s next?。有很多有意思的瞬间记录一下,持续更新。 和孩子一起打闹,学习,成长。 教娃编程孩子有趣的瞬间 vlog(Day 641) 娃说我不够 Senior(Day 701) Bro.. I thought you’re like...
  2. 如何轻松创建并托管你的 GitHub Pages 站点 (无服务器静态应用) 创建一个 GitHub Pages 站点是一个简单的过程,可以免费为你的个人、项目或组织创建网站/博客。按照此指南开始。 步骤 1:创建 GitHub 仓库 登录你的 GitHub 账户。 点击“新建”来创建仓库。 对于个人站点,命名为 .github.io。 对于项目站点,使用任何有效的名称。...
  3. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  4. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  5. 最好的发财策略就是忘记它 在群里我和 @nationalpark 说到我用STEEM换了3000多个 YOYOW的时候我还有点沾沾自喜, 谁知道他一针见血: 听说你是steem换的我顿时心理平衡了 才突然想起来, 我半个月前是拿 500 个 STEEM 换了 将近3300个YOYOW. 今天 STEEM的价格8美元,...
  6. 面试刷题更像是一种服从性测试 在许多求职者看来,面试中的刷题环节似乎是对技术能力的直接考察。然而,从另一个角度来看,这一过程或许更像是一场服从性测试。 想象一下,当一个公司要求候选人解决复杂的算法问题或设计一个系统架构时,真正的目的并不仅仅是判断候选人是否能完成这项任务。许多时候,公司更关心的是你是否愿意花时间和精力去迎合他们的流程和标准。 为什么这样说呢?从实际工作中来看,绝大多数岗位并不需要员工每天都在解决高难度的算法问题或从零设计分布式系统。相反,工作中的任务更偏向于理解业务逻辑、维护现有系统以及团队协作。那么,为什么刷题依然是很多公司青睐的筛选方式? 答案或许在于,刷题能有效筛选出那些愿意投入大量时间准备,并在面试中展现高度配合精神的候选人。这种配合精神,是公司希望在未来的工作中看到的品质。一旦你通过了这些测试,公司就会认为你是一个值得信赖的员工,一个能遵从流程、适应规则并在需要时投入额外努力的人。 还有就是以前说的:面试刷力扣是大公司过滤的成本较低的方法之一:为啥有名的IT公司的程序员面试都这么难?,可以过滤掉大部分 False Positive,当然也有可能过滤掉人才(胜任工作的求职者)。不过对于大厂来说,招进来一个False Positive的损害要远远比错过一个False Negative大。 此外,随着AI技术的飞速发展,刷题的技术价值正在逐渐降低。像ChatGPT这样的AI工具,已经可以轻松解决LeetCode或Codeforces上的许多高难度问题,并给出清晰的解题过程。这表明,算法题不再是人类独有的技术优势,而是可以被高效地自动化处理的任务。这种变化进一步凸显了刷题在实际工作中的局限性,因为日常工作更多依赖于沟通、业务理解和对系统的长期维护,而不是短时间内解决某个抽象问题。 OpenAI推出o3大语言AI模型。 Codeforces 评分:2727——相当于全球人类程序员编码竞赛中,排名第 175 位。...
  7. 英国 NatWest 银行推出 Reward Current Account 用来交水电费等帐单可以省钱 英国 NatWest 银行推出 Reward Current Account 银行帐号, 每个月需要交会费 3 英镑, 一年则需要36英镑 并不便宜, 但是好处是 只要通过该帐号以 Direct...
  8. 科学上网 通过 CloudFlare Worker 搭建 Js Proxy代理 CloudFlare 的 Worker 就是 Serverless (无服务器, 所有程序跑在云端). 以前是付费的, 现在免费了. 免费帐号可以创建30个 Worker, 每个帐号每天可以有10万次免费请求. 超过了可以配置 Fail Open...

教娃编程有趣的瞬间 vlog视频集合

2025年1月22日 04:59

教娃700天:写在教娃编程700天, what’s next?。有很多有意思的瞬间记录一下,持续更新。

和孩子一起打闹,学习,成长。

教娃编程孩子有趣的瞬间 vlog(Day 641)





娃说我不够 Senior(Day 701)

Bro.. I thought you’re like senior…
Yes, I am senior!
You should know how to do it then..
I’ll ask ChatGPT..
Bro, you don’t need ChatGPT..
OK, we can talk about it next lesson.

油管 | 微博 | 小红书 | 微信视频 | B站

娃嫌弃我的中式英文

油管 | 微博 | 微信视频 | B站 | 西瓜视频 | 小红书

儿子说在谷歌工作要比在微软挣得多

儿子说在谷歌工作要比在微软挣得多

教娃好玩的视频 vlog 片段

本文一共 252 个汉字, 你数一下对不对.
教娃编程有趣的瞬间 vlog视频集合. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 教娃编程有趣的瞬间 vlog视频集合 VLog 学校 教娃 教程 教育 生活 视频
The post 教娃编程有趣的瞬间 vlog视频集合 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 个人网站Adsense广告申请通过: 需要最少15篇文章 我的个人网站 zhihua-lai.com 本月通过了 Adsense 审核,终于可以再次放置广告,赚些零花钱了。 其实,最初 Adsense 账户通过审核后就能直接放广告,但后来规则变得严格了。如果一个网站长时间没有放置任何 Adsense 广告代码,账户资格会被撤销。重新启用时,需要进行单独审核。如今,在 Google Adsense 中新增一个域名,也必须通过审核后才能投放广告。 为了让我的网站通过审核,我尝试了几次,但总是被拒,原因之一是必须要有足够的内容支持。例如,以前我做的工具网站 SlowAPI.com...
  2. 新的旅途 – 离别总是伤感的, 离开了一起创业的公司 2周前, 正式离开了一起创业的公司, 这公司是我博士毕业后的第一份正式工作, 待了8年多了, 离别总是伤感的. 我是9月初提的离职, 三个月 Notice Period, 最后的几周交接完工作确实没有什么压力了. 11月30号, 在公司最后一天, 公司有个习惯, 对于 Good...
  3. Minuet in C – 小步舞曲C Posted Youtube – 油管地址 孩子弹琴的时候最帅了. 我现在成了我儿子的粉丝了. Eric (Aged 6) is playing “Minuet in C” when...
  4. 上了年纪痛风脚崴了的惨痛经历(尿酸过高) 痛风是一种疼痛性关节炎, 当血液中的尿酸水平高, 导致晶体形成并积聚在关节内或关节周围, 就会发生痛风. 当人体分解一种叫做嘌呤的化学物质时, 就会产生尿酸. 嘌呤自然存在于您的身体中, 也存在于某些食物中. 尿酸通过尿液从体内排出. 上两周, 和媳妇吵架, 然后就自己一人睡, 有一天起床后脚踝就开始疼了, 然后明显比左脚肿了. 我刚开始就以为是睡觉的时候不小心姿势不对,...
  5. 今晚可能是英国倒数第三次冬令时时间调整 来英国生活过的人都知道 英国有一个很有意思的夏令时和冬令时时间调整. 夏令时是每年3月份的最后一个星期天凌晨会暴力的把时间往后调整1个小时, 也就是1点的时候很神奇的时间会自动变成2点. 冬令时是相反, 在每年的十月份最后一个周日凌晨暴力的把时间往前调整一个小时, 也就是2点神奇的变成1点. 现在大多数电子设备都能自动调整时间了, 比如你的智能手机和电脑. 但是传统的一些钟表, 还需要人为的调整时间. 夏令时 (Daylight Saving Time)调整的那一天,...
  6. 优衣库 感觉像炒作 这几天 这个在北京三里屯 ‘优衣库’ 试衣间自拍的视频真的很火, 男女主角均被人肉. 不可否认 这个效果还真的不错 因为我之前根本不知道 “优衣库” 是干嘛的 很刺激 在试衣间XXOO是多么爽的事情 女主角 95后妹子 长相甜美....
  7. 因为一个 try catch 好几天优秀点赞程序挂掉了我却以为是节点的问题 YY银行有四个点赞程序, 分别跑于四台服务器上, 它们是: 股东点赞(代理给 @justyy ) 自己的号 足球队 优秀作者(排名前30, 还有跟随点赞) 这几天我就发现, 有些赞没有给出, 我还以为是节点的原因, 因为查看记录, 发现是...
  8. 怎么样查看 STEEMSQL 是否数据同步完全? 尽管它已经转换为按月订阅模式, SteemSQL是由@arcange开发和维护的一个很好的服务(MSSQL). 其实对于这些中心化的数据库来说, 最重要的就是它要能够及时的从区块链上同步数据. 如果数据不全, 那么其实是没有多大用处的. STEEMSQL每隔8分钟就会从区块链上同步数据. 之前有过几回, STEEMSQL数据并不及时的同步, 如果你的程序很需要很及时的数据, 则STEEMSQL并不是很好的选择. 相反, 如果你只是想运行几个语句去查询以往的数据, 则STEEMSQL是个很好的方案, 毕竟从中心化关系型数据库取数据要比去区块链上取数据快得多....

Sui 区块链编程: 通过 NodeJs/Javascript 函数获取 Gas 费用

2025年1月20日 04:06

在区块链开发的世界中,理解和管理gas费用对于优化交易至关重要。Sui作为一个相对较新但功能强大的区块链平台,有其独特的方式来处理gas价格。让我们来探讨如何使用Node.js和JavaScript获取Sui的gas价格。

Sui中的Gas理解

在区块链语境中,gas指的是执行交易或智能合约所需的费用。在Sui中,gas以MIST计量,其中1个SUI等于10^9个MIST。这意味着gas价格通常以每个交易或操作的MIST来报价。

获取Gas价格

这里有一个简单的异步函数来从Sui节点获取当前的gas价格:

const QUICKNODE_URL = "https://sui-mainnet-endpoint.blockvision.org"; // 你可以使用你自己的节点。

async function getGasPrice() {
    try {
      const response = await axios.post(QUICKNODE_URL, {
        method: "suix_getReferenceGasPrice",
        jsonrpc: "2.0",
        id: 1,
        params: [],
      });
      return parseFloat(response.data.result);
    } catch (error) {
      console.error("获取gas价格时发生错误:", error);
      return null;
    }
}

代码解析

  • axios.post: 我们使用axios向Sui RPC节点发送HTTP POST请求(假设QUICKNODE_URL在其他地方已定义为正确的URL)。
  • 方法和JSON-RPC: 使用 suix_getReferenceGasPrice 方法查询gas价格。采用JSON-RPC 2.0协议进行通信。
  • 错误处理: 如果获取gas价格时发生错误,会记录错误,并返回null以表示失败。

使用函数

你可以在异步上下文中调用这个函数:

(async () => {
  const gasPrice = await getGasPrice();
  if (gasPrice !== null) {
    console.log(`当前gas价格: ${gasPrice} MIST`);
  } else {
    console.log("无法获取gas价格。");
  }
})();

重要说明:

  • MIST: 返回的gas价格是以MIST计量的。为了实际应用,你可能需要将它转换为SUI以增加可读性,使用 gasPrice / 10^9。
  • QuickNode URL: 你需要用你实际的RPC节点URL替换QUICKNODE_URL。如果你不使用QuickNode,你可以连接到任何支持此RPC方法的Sui节点。
  • 异步特性: 记住,区块链交互本质上是异步的,因此使用async/await来处理promise。

获取Sui区块链Gas费用/价格的结论

使用Node.js获取Sui中的gas价格是直接的,但需要理解区块链gas机制的细微差别以及JSON-RPC通讯。这个代码片段可以作为开发者开始将gas价格检查整合到他们的Sui区块链应用中的起点,确保他们能够有效管理交易成本。

英文:Sui Blockchain Programming: Get the Gas Fee via NodeJs/Javascript Function

Sui 区块链编程

本文一共 496 个汉字, 你数一下对不对.
Sui 区块链编程: 通过 NodeJs/Javascript 函数获取 Gas 费用. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c Sui 区块链编程: 通过 NodeJs/Javascript 函数获取 Gas 费用 Javascript Node Js Sui 加密货币 区块链 程序设计 计算机 计算机
The post Sui 区块链编程: 通过 NodeJs/Javascript 函数获取 Gas 费用 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 油管视频/Youtube Premium会员的好处: 用了就回不去了 从前年开始,我成为了 YouTube 会员,不过中间时断时续,因为觉得价格有点贵。在英国,YouTube 家庭会员每月费用是 19.99 英镑(可供五位家人共享),而个人会员则是每月 12.99 英镑。去年回国前,我暂停了一段时间的会员服务,但回到英国后发现自己已经离不开 YouTube 会员了。 成为会员的最大好处就是没有广告。最初,YouTube 是没有广告的,但随着平台上视频数量激增,广告也逐渐多了起来。如今,YouTube 已然是长视频的王者,谷歌当年的收购绝对算得上是一次非常成功的商业决策,足以写进教材。随着视频数量越来越多,用户一辈子也看不完平台上的内容,所需的服务器资源也水涨船高。因此,谷歌插入广告确实无可厚非。然而,从最开始的一两个广告,到如今非会员用户每隔五分钟就得忍受一次广告,用户体验确实大打折扣。 会员的另一个好处是可以下载视频,不过并不是直接下载为视频文件.mp4,而是保存在设备上。这样一来,可以提前下载好音乐或视频,开车时就无需耗费流量了。 此外,YouTube...
  2. TikTok未能出售: 流量“难民”涌入小红书, 能否接得住? 最近,随着TikTok因监管压力未能在1月19日前完成出售事宜,大量的内容创作者和用户正在寻找新的平台安置自己的内容生态和社交网络。这一波流量转移,意外地将小红书(RedNotes)推向了风口浪尖。打开小红书,推荐页面中已经充满了国外用户的身影,甚至有用户戏称“小红书变成了‘国际版种草机’”。然而,这一流量迁徙带来的热闹背后,蕴藏着深刻的挑战和机会。 不过,由于美国新政府很快就要上台了,特朗普对抖音是比较友好的,所以目前这种局面可能会迎来新的转机。拜登政府上台后,政策方向可能会与特朗普有所不同,尤其是在针对中国企业的态度上,可能会采取更加审慎的评估方式,而不是简单的封杀或施压。 对于TikTok而言,这或许意味着缓冲的机会。一方面,拜登政府可能会重新审视TikTok在美国的安全问题,并尝试通过更为温和的方式解决争议,比如进一步完善数据监管和用户隐私保护,而非直接推动出售或下架。另一方面,如果政策有所松动,TikTok也许能够在国际市场重新站稳脚跟,挽回部分因政策不确定性流失的用户和广告商。 不过,这种转机并非完全没有挑战。TikTok必须快速适应美国监管层面可能提出的新要求,比如建立更加透明的数据管理流程、确保服务器本地化、以及加强对算法和内容分发的自我监督。此外,TikTok还需要修复此前因封杀风险导致的品牌信任危机,重新赢得用户和合作伙伴的信赖。 对于小红书来说,这也可能是一个需要重新评估全球化策略的时刻。如果TikTok因政策调整成功稳定住国际市场,那么这波流量“难民”可能会逐渐回归,甚至减少在小红书上的活跃度。这要求小红书在短期内快速吸引这些用户,并通过强化社区黏性和内容多样化,尽可能锁住这批流量。同时,小红书也需要未雨绸缪,为可能到来的用户流失准备应对策略。 无论TikTok是否会迎来转机,这场流量的争夺战都表明,在全球化的竞争中,灵活应变、长期规划和技术实力才是赢得用户和市场的关键因素。 外来流量与用户画像的冲突 小红书一直以其独特的社区氛围闻名,聚焦于生活方式、种草推荐和用户亲身体验分享,目标用户以中国年轻女性为主。随着大量国际用户的涌入,这一平台的内容调性可能面临巨大的冲击。很多本地用户已经开始抱怨,算法推荐的内容逐渐变得“陌生”,甚至暴露了个人的兴趣偏好。这种文化差异和用户需求的差异,可能会对小红书社区的核心黏性带来挑战。 不过,对于小红书来说,国际流量的到来也可能是一种绝佳的扩展契机。如果能够妥善应对文化融合问题,同时优化算法和内容分发策略,小红书完全有机会成为全球用户的内容分享平台,撬动TikTok留下的空白市场。 连夜招募的“英文审核”反映了什么? 面对流量的猛增,小红书显然已经嗅到了机会。平台据称正在连夜招募英文审核团队,足见其对全球化的重视和危机意识。审核机制的建立是平台内容生态的重要一环,尤其是当国外用户涌入时,如何维持内容的合法性、合规性和调性,将决定小红书能否将这波流量真正留住。 目前来看,这种快速扩展反映了小红书的敏捷反应能力,但从长期来看,审核机制的完善需要时间,语言和文化隔阂可能会导致内容质量在短期内下降。同时,海外用户的内容生产习惯与中国用户不同,是否能够在算法推荐和审核效率之间找到平衡,也是一个值得关注的问题。 小红书的变现模式是否支撑得住? 流量增长对于任何平台来说都是一次巨大的红利,但能否将流量转化为收入才是真正的考验。小红书的商业模式以种草电商为核心,通过“内容+电商”的闭环完成流量变现。然而,国际市场中用户的购买力、消费习惯以及对本土化商品的接受程度,都可能成为变现过程中的障碍。 国际用户的涌入如果只是短期行为,或者消费习惯难以转化为实际的电商订单,那么平台即便流量激增,也很难转化为长期收益。相比之下,TikTok的变现模式更依赖广告,而小红书的广告收入占比相对较低。如果能够快速调整策略,比如增加短视频广告位或推出国际版电商模块,小红书或许能在全球市场中占得一席之地。...
  3. 剑桥网红餐厅 The Ivy Cambridge Brasserie 上周刚过40岁生日,媳妇带我去剑桥的一家网红餐厅——The Ivy Cambridge Brasserie。这家餐厅是朋友推荐的,和那家Trinity一样很有名,可惜我们在剑桥待了近10年才知道。 The Ivy Cambridge Brasserie 是剑桥市中心一家备受欢迎的网红餐厅,位于历史悠久的 Trinity Street。餐厅设计时尚典雅,融合了现代与经典元素,为食客提供了一个舒适而奢华的用餐环境。这里的菜单丰富多样,覆盖全天用餐,从早餐到晚餐以及下午茶,提供英式经典美食如松露鸡肉派、英式早餐等,也有一些国际风味的菜肴。得天独厚的位置和独特的氛围使得 The Ivy Cambridge Brasserie...
  4. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  5. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  6. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  7. ACM题解系列之 – 最小堆栈 (Min Stack) 没事刷刷题能防止老年痴呆, 而且也能让你随时处于最佳状态, 随时都可以炒老板鱿鱼另谋高就. 题目: 设计一个堆栈(Stack)使 push, pop, 和取最小 min 操作时间复杂度都是 O(1). 这题的难点就是在于怎么样用O(1)常数时间复杂度来取得堆栈里的最小值. class MinStack {...
  8. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....

Sui区块链编程: 获取最新块高度(NodeJs/Javascript函数 Latest Block Number/Height)

2025年1月17日 04:38

获取最新区块号(高度)是开发人员在去中心化系统中常见的任务。如果你正在使用 Sui 区块链,并希望通过 Node.js 和 JavaScript 获取最新的区块高度,以下是一个简单的实现方法。

Sui 是一个高性能、可扩展的区块链,以低延迟和创新架构而闻名。与 Sui 的交互需要利用其 API,这些 API 允许开发人员无缝查询区块链数据并与智能合约交互。首先,确保你已在计算机上设置了 Node.js 环境并安装了必要的依赖项。

了解 Sui 区块链:Sui区块链简介

首先,创建一个新的 Node.js 项目。你可以使用以下命令初始化项目:

mkdir sui-block-height
cd sui-block-height
npm init -y

接下来,安装 Axios 库,它通常用于在Node.js中发起 HTTP 请求。我们将用它从 Sui 区块链 API 获取数据:

npm install axios

现在,创建一个名为 getLatestBlock.js 的文件,并在你喜欢的代码编辑器中打开。在这个脚本中,我们将编写一个函数来获取最新的区块高度。Sui 区块链提供了一个 RPC 端点,允许你查询其状态。这个端点是获取区块数据的关键。

以下是代码:

const axios = require('axios');

// Sui RPC 端点 - 如果使用特定网络,请替换为实际端点 
const SUI_RPC_URL = 'https://fullnode.sui.io/v1';

async function getLatestBlockHeight() {
    try {
        // 向 Sui RPC 端点发送 POST 请求
        const response = await axios.post(SUI_RPC_URL, {
            jsonrpc: '2.0',
            id: 1,
            method: 'sui_getLatestCheckpointSequenceNumber',
            params: []
        });
        if (response.data && response.data.result !== undefined) {  
            console.log(`最新区块高度: ${response.data.result}`);  
            return response.data.result;  
        } else {  
            throw new Error('响应结构异常');  
        }  
    } catch (error) {  
        console.error('获取区块高度失败:', error.message);  
        throw error;  
    }  
}

// 运行函数
getLatestBlockHeight().catch((err) => {
    console.error('Failed to fetch the block height:', err);
});

Node.js 代码解析

  • Axios 请求:我们使用 Axios 向 Sui RPC 端点发送 POST 请求。方法 sui_getLatestCheckpointSequenceNumber 用于获取区块链上的最新区块号(或检查点序列号)。
  • 错误处理:正确的错误处理可以确保网络错误或响应格式异常被记录,便于调试。
  • 日志记录:函数将最新的区块高度输出到控制台,这对于快速测试或调试非常有用。

在运行脚本之前,确保 Sui RPC 端点正确且可访问。示例中提供的 URL 指向 Sui 主网的全节点端点。如果你使用的是测试网或本地实例,请将 SUI_RPC_URL 变量替换为合适的端点。

运行脚本的命令:

node getLatestBlock.js

如果设置正确,你应该在控制台中看到打印的最新区块高度。此函数可以轻松集成到更大的应用程序中,或通过修改 RPC 方法和参数来适应其他区块链数据的获取需求。

实时交互区块链数据是构建去中心化应用程序的关键技能。借助 Sui 区块链强大的 API 和 Node.js 的简单性,你可以快速获取最新区块高度,并将此信息用于各种用途,例如监控网络、更新用户界面或触发应用程序中的特定操作。

随着 Sui 生态系统的发展,及时关注其文档和最佳实践可以确保你的集成高效且可靠。

英文:NodeJs/Javascript Function to Get the Latest Block Number (Height) on the Sui Blockchain

Sui 区块链编程

本文一共 729 个汉字, 你数一下对不对.
Sui区块链编程: 获取最新块高度(NodeJs/Javascript函数 Latest Block Number/Height). (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c Sui区块链编程: 获取最新块高度(NodeJs/Javascript函数 Latest Block Number/Height) Javascript Node Js Sui 区块链 学习笔记 小技巧 程序设计 计算机
The post Sui区块链编程: 获取最新块高度(NodeJs/Javascript函数 Latest Block Number/Height) first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 油管视频/Youtube Premium会员的好处: 用了就回不去了 从前年开始,我成为了 YouTube 会员,不过中间时断时续,因为觉得价格有点贵。在英国,YouTube 家庭会员每月费用是 19.99 英镑(可供五位家人共享),而个人会员则是每月 12.99 英镑。去年回国前,我暂停了一段时间的会员服务,但回到英国后发现自己已经离不开 YouTube 会员了。 成为会员的最大好处就是没有广告。最初,YouTube 是没有广告的,但随着平台上视频数量激增,广告也逐渐多了起来。如今,YouTube 已然是长视频的王者,谷歌当年的收购绝对算得上是一次非常成功的商业决策,足以写进教材。随着视频数量越来越多,用户一辈子也看不完平台上的内容,所需的服务器资源也水涨船高。因此,谷歌插入广告确实无可厚非。然而,从最开始的一两个广告,到如今非会员用户每隔五分钟就得忍受一次广告,用户体验确实大打折扣。 会员的另一个好处是可以下载视频,不过并不是直接下载为视频文件.mp4,而是保存在设备上。这样一来,可以提前下载好音乐或视频,开车时就无需耗费流量了。 此外,YouTube...
  2. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  3. 测测你的幸运 – Linux Fortune-Teller LINUX 下有很好很好玩的命令,之前已经介绍过: figlet, rig, curl. 现在推荐另一个 命令 fortune 是用来随机显示一段(句)话的.fortune 在英文里就是幸运的意思. 这个命令可以不需要 参数 如果没有 可以通过 apt-get...
  4. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....
  5. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  6. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...
  7. 剑桥网红餐厅 The Ivy Cambridge Brasserie 上周刚过40岁生日,媳妇带我去剑桥的一家网红餐厅——The Ivy Cambridge Brasserie。这家餐厅是朋友推荐的,和那家Trinity一样很有名,可惜我们在剑桥待了近10年才知道。 The Ivy Cambridge Brasserie 是剑桥市中心一家备受欢迎的网红餐厅,位于历史悠久的 Trinity Street。餐厅设计时尚典雅,融合了现代与经典元素,为食客提供了一个舒适而奢华的用餐环境。这里的菜单丰富多样,覆盖全天用餐,从早餐到晚餐以及下午茶,提供英式经典美食如松露鸡肉派、英式早餐等,也有一些国际风味的菜肴。得天独厚的位置和独特的氛围使得 The Ivy Cambridge Brasserie...
  8. Pi币简介: 这真的是区块链上的虚拟货币么? 前几年就听说有一个币叫Pi,但又有听说这币就是屎币不靠谱,说是手机装个APP就能挖。 Pi币(Pi Network)是一种由移动应用程序推广的加密货币项目,据称其目标是让普通人能够通过手机“挖矿”来获得加密货币。以下是对Pi币的详细介绍: Pi币的特点与工作机制 挖矿方式 Pi币的挖矿与传统的工作量证明(PoW,如比特币)不同,它通过一款手机应用完成。用户每天登录应用,点击“挖矿”按钮,即可开始“挖矿”。这种机制主要是为了降低参与的技术门槛和硬件成本。 共识机制 Pi币声称使用的是一种基于“Stellar Consensus Protocol”(SCP,恒星共识协议)的机制。这种共识机制并不像比特币或以太坊那样依赖大量计算能力,而是依赖信任图谱,即用户间的信任关系来达成共识。 无需耗费算力 Pi币的挖矿并不依赖用户手机的计算资源,因此不会显著增加电池消耗或数据使用。这与其他加密货币的能耗问题形成了鲜明对比。 网络层级 先锋(Pioneer):普通用户,每天签到获得Pi币。 贡献者(Contributor):构建信任图谱,通过邀请新用户加入。...

面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新)

2025年1月12日 06:39

chessly-lies-on-the-floor-upstairs-scaled 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 宠物 照片 生活 视频

Chessly悠闲的躺在楼上的地板上,人来了也懒得挪动。

家里有两只猫 Chessly/Pyro,想着找个地方记录它们的生活,最后决定还是写在这里的博客。猫的一生很短,差不多也就二十年。

Chessly(黑白猫)是我加入微软剑桥研究院MSRC第一个月带回家的,过了两三个月,又把Pyro(橘猫)也接回了家。两只猫的名字是孩子们取的:Chessly因为黑白的像棋盘,加上“ly”听起来像个女孩的名字;而Pyro的意思是一团火(烟火),充满活力。

刚开始的时候,Chessly特别喜欢待在我的工作区域。她有时候趴在键盘上或旁边,有时候藏在显示器后面。偶尔还会绕到我身边“咕咕”地撒娇,等着我去摸她。有时更干脆跑到我腿上,舒舒服服地躺着。

不过,现在它们俩的体型都大了很多,躺在桌上就会挡住屏幕,真是“面向猫猫编程”(Cat Oriented Programming)的极致体验。

记录生活的点滴,也是一种珍惜,毕竟这二十年,我们会一起走过。

2024年

chessly-cat-on-piano-2024-11-08-17.28.14-scaled 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 宠物 照片 生活 视频

娃在弹钢琴的时候,Chessly喜欢在边上听着。2024-11-08

2025年

chessly-in-front-of-a-monitor-2025-01-10-15.48.22-scaled 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 宠物 照片 生活 视频

面向猫编程,上班的时候Chessly在我的显示器前躺着,有点碍事。2025-01-10

Ring视频:两猫日常就是打闹,Chessly追上Pyro想舔他,在猫的世界里,地位高的才能舔地位低的。

我家猫现在越来越胖,很喜欢在我工作的时候躺在显示器钱,很影响我的工作,不过这时候我就是会休息一下摸摸她,就当放松一下了。

Pyro在窗边喝水,这是个小的煮饭锅,现在不用了,就给猫当喝水的碗。Pyro很胆小,经常看到我就跑。没法跑就咕咕叫。

Chessly很喜欢陪我工作,然后她很好厅的盯着屏幕上的鼠标光标,真怕她把屏幕抓坏了。

chessly-looks-at-cursor-on-screen-2025-02-18-16.51.07-scaled 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 宠物 照片 生活 视频

Chessly看着屏幕上的鼠标光标,感觉她好想去抓。

哥哥弹琴,弟弟唱歌,Chessly午睡,真是幸福啊,下辈子做只猫吧。

chessly-takes-a-nap-while-eric-plays-piano-2025-02-18-11.21.25-scaled 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 宠物 照片 生活 视频

Chessly在沙发上午觉,感觉眼睛快闭上了,哥哥在弹钢琴,悠闲的下午

视频:油管/Youtube | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书

宠物猫:Chessly and Pyro

本文一共 825 个汉字, 你数一下对不对.
面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新). (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 宠物 照片 生活 视频
The post 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 个人网站Adsense广告申请通过: 需要最少15篇文章 我的个人网站 zhihua-lai.com 本月通过了 Adsense 审核,终于可以再次放置广告,赚些零花钱了。 其实,最初 Adsense 账户通过审核后就能直接放广告,但后来规则变得严格了。如果一个网站长时间没有放置任何 Adsense 广告代码,账户资格会被撤销。重新启用时,需要进行单独审核。如今,在 Google Adsense 中新增一个域名,也必须通过审核后才能投放广告。 为了让我的网站通过审核,我尝试了几次,但总是被拒,原因之一是必须要有足够的内容支持。例如,以前我做的工具网站 SlowAPI.com...
  2. 新的旅途 – 离别总是伤感的, 离开了一起创业的公司 2周前, 正式离开了一起创业的公司, 这公司是我博士毕业后的第一份正式工作, 待了8年多了, 离别总是伤感的. 我是9月初提的离职, 三个月 Notice Period, 最后的几周交接完工作确实没有什么压力了. 11月30号, 在公司最后一天, 公司有个习惯, 对于 Good...
  3. Minuet in C – 小步舞曲C Posted Youtube – 油管地址 孩子弹琴的时候最帅了. 我现在成了我儿子的粉丝了. Eric (Aged 6) is playing “Minuet in C” when...
  4. 上了年纪痛风脚崴了的惨痛经历(尿酸过高) 痛风是一种疼痛性关节炎, 当血液中的尿酸水平高, 导致晶体形成并积聚在关节内或关节周围, 就会发生痛风. 当人体分解一种叫做嘌呤的化学物质时, 就会产生尿酸. 嘌呤自然存在于您的身体中, 也存在于某些食物中. 尿酸通过尿液从体内排出. 上两周, 和媳妇吵架, 然后就自己一人睡, 有一天起床后脚踝就开始疼了, 然后明显比左脚肿了. 我刚开始就以为是睡觉的时候不小心姿势不对,...
  5. 今晚可能是英国倒数第三次冬令时时间调整 来英国生活过的人都知道 英国有一个很有意思的夏令时和冬令时时间调整. 夏令时是每年3月份的最后一个星期天凌晨会暴力的把时间往后调整1个小时, 也就是1点的时候很神奇的时间会自动变成2点. 冬令时是相反, 在每年的十月份最后一个周日凌晨暴力的把时间往前调整一个小时, 也就是2点神奇的变成1点. 现在大多数电子设备都能自动调整时间了, 比如你的智能手机和电脑. 但是传统的一些钟表, 还需要人为的调整时间. 夏令时 (Daylight Saving Time)调整的那一天,...
  6. 优衣库 感觉像炒作 这几天 这个在北京三里屯 ‘优衣库’ 试衣间自拍的视频真的很火, 男女主角均被人肉. 不可否认 这个效果还真的不错 因为我之前根本不知道 “优衣库” 是干嘛的 很刺激 在试衣间XXOO是多么爽的事情 女主角 95后妹子 长相甜美....
  7. 因为一个 try catch 好几天优秀点赞程序挂掉了我却以为是节点的问题 YY银行有四个点赞程序, 分别跑于四台服务器上, 它们是: 股东点赞(代理给 @justyy ) 自己的号 足球队 优秀作者(排名前30, 还有跟随点赞) 这几天我就发现, 有些赞没有给出, 我还以为是节点的原因, 因为查看记录, 发现是...
  8. 怎么样查看 STEEMSQL 是否数据同步完全? 尽管它已经转换为按月订阅模式, SteemSQL是由@arcange开发和维护的一个很好的服务(MSSQL). 其实对于这些中心化的数据库来说, 最重要的就是它要能够及时的从区块链上同步数据. 如果数据不全, 那么其实是没有多大用处的. STEEMSQL每隔8分钟就会从区块链上同步数据. 之前有过几回, STEEMSQL数据并不及时的同步, 如果你的程序很需要很及时的数据, 则STEEMSQL并不是很好的选择. 相反, 如果你只是想运行几个语句去查询以往的数据, 则STEEMSQL是个很好的方案, 毕竟从中心化关系型数据库取数据要比去区块链上取数据快得多....

在LG的OLED智能电视下刷题/力扣

2025年1月8日 00:57

装修时花大价钱买的65寸LG OLED智能电视(4K),挂在厨房后利用率很高。

平时一家人吃饭时会看看综艺,有时候孩子们会看他们喜欢的内容,比如游戏视频、宝可梦和超级玛丽等。

有一天,偶然发现老婆用电视内置的浏览器操作,看她用遥控器当鼠标,配合屏幕上的电子键盘访问了一些国产视频网站。顿时灵感来了:这电视不是可以用来刷题吗?于是立刻行动起来,把家里闲置的蓝牙鼠标和一把KeyChron机械键盘(支持蓝牙和Type-C两种模式)找出来。然而,电视却无法识别这两个设备,略感遗憾。

不过,电视上显示支持特定型号的蓝牙设备,还列出了推荐清单。最终我入手了相对平价的罗技M535蓝牙鼠标和Arteck键盘,总共花了55英镑。对比之下,苹果的键盘Apple Magic Keyboard和鼠标Apple Magic Mouse 2加起来要180英镑左右(99+79),贵太多了。

实际体验下来,电视浏览器的运行速度稍慢,打字时有些延迟,而且每次输入时屏幕都会弹出电子键盘,还需要按ESC手动隐藏。不过整体来说,刷力扣/LeetCode的简单题还是够用的。吃饭时趁机给孩子刷两道题,就当复习了。

网友建议:

  • “可以买个Mac Mini。”
  • “直接用MacBook投屏也行啊。”
  • “都接键鼠了,可以用串流软件远程操作家里的电脑。”
  • “几十磅买个小型x86主机接上去效果更好。”

确实,电脑投屏是个不错的选择,但老婆不允许在厨房放电脑。这次的重点是直接利用电视的浏览器搭配蓝牙鼠标和键盘,方便轻便,简单实用。把电视当成一个电脑,好过于再依赖于任何外接设备(除了鼠标和键盘)。

您之前连接的设备会自动连接。要断开现有连接,请单击“断开连接”。

建议您使用已测试与此设备兼容性的产品。

  • 蓝牙鼠标:Logitech M535、Logitech Mx Master 2s、Apple Magic Mouse 2
  • 蓝牙键盘:Logitech K480、Arteck HB030B、Arteck HB192、Apple Magic Keyboard
  • 蓝牙游戏手柄:Sony Dual shock 4、Sony Dual Sense 控制器、Sony DualSense Edge 控制器、Microsoft Xbox One S 控制器、Microsoft Xbox Series X 控制器、Microsoft Xbox Adaptive 控制器、NVIDIA SHlELD 控制器 Amazon Luna 控制器、Shaks S3b、Shaks s2b、Nintendo Switch Pro 控制器、Nintendo Joy-con 控制器、MOGA XP5-X Plus 控制器

此功能仅适用于支持蓝牙的型号!

连接多个蓝牙设备可能会导致连接不良或操作不当。

The device you’ve previously connected to, it connects automatically. To disconnect the existing connection, click Disconnect.

It is recommended that you use a product that has been tested forcompatibility with this devices.

  • Bluetooth Mouse: Logitech M535, Logitech Mx Master 2s, Apple Magic Mouse 2
  • Bluetooth Keyboard: Logitech K480, Arteck HB030B, Arteck HB192, Apple Magic Keyboard
  • Bluetooth Gamepad: Sony Dual shock 4, Sony Dual Sense Controller, Sony DualSense Edge Controller, Microsoft Xbox One S Controller, Microsoft Xbox Series X Controller, Microsoft Xbox Adaptive Controller, NVIDIA SHlELD Controller Amazon Luna Controller, Shaks S3b, Shaks s2b, Nintendo Switch Pro Controller, Nintendo Joy-con Controller, MOGA XP5-X Plus Controller

This feature is available for Bluetooth-enabled models only!

Connecting multiple Bluetooth devices may result in poor connection or operation.

leetcode-on-kitchen-oled-tv-2025-01-07-13.33.31-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

娃在学习,和娃也方便聊算法编程等。

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.44.11-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

每天都刷题,连接刷了五年多了。

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.43.53-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

在LG的65寸智能OLED电视上刷力扣还是挺新奇的。

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.21.37-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

娃在吃饭可以顺便教学一下,就当复习了。

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.13.21 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

平时吃饭/在厨房喝个咖啡休息的时候就可以刷一题。

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.13.17-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

刷题的设备:罗技M535蓝牙鼠标+Arteck蓝牙键盘

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.13.10-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

第一次在智能电视上刷题/力扣

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.12.48-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

这个电子键盘很烦人,尽管连接上了蓝牙键盘,还是时不时显示出来,这时候按ESC就可以隐藏。

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.08.39-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

第一次在LG智能电视上打开了力扣网站

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-06-18.06.51-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

在智能电视内置的浏览器打开力扣刷题网站

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-04-22.17.35 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

LG智能电视支持的蓝牙设备:键盘/鼠标

leetcode-coding-on-smart-tv-with-bluetooth-keyboard-mouse-2025-01-04-22.11.13-scaled 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标

智能电视可以支持蓝牙设备

刷题:程序员的基本技能

英文:How to do Leetcoding on LG OLED Smart TV (65 inch)?

本文一共 1114 个汉字, 你数一下对不对.
在LG的OLED智能电视下刷题/力扣. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 在LG的OLED智能电视下刷题/力扣 折腾 智能电视 硬件 程序员 编程 资讯 键盘 鼠标
The post 在LG的OLED智能电视下刷题/力扣 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. ACM题解系列之 – 最小堆栈 (Min Stack) 没事刷刷题能防止老年痴呆, 而且也能让你随时处于最佳状态, 随时都可以炒老板鱿鱼另谋高就. 题目: 设计一个堆栈(Stack)使 push, pop, 和取最小 min 操作时间复杂度都是 O(1). 这题的难点就是在于怎么样用O(1)常数时间复杂度来取得堆栈里的最小值. class MinStack {...
  2. 停了我使用8年的美国运通白金卡(American Express) AMEX是指美国运通公司(American Express Company), 该公司成立于1850年, 总部位于美国纽约的百老汇区. 它是一家金融服务公司, 主要从事信用卡, 旅行和金融服务. 其信用卡业务覆盖了150多个国家和地区, 拥有超过1.4亿张有效信用卡. AMEX提供各种商业服务, 旅行服务, 保险服务以及电子支付等金融产品服务. 今天早上打电话把我的美国运通白金卡给取消了(Amex, American...
  3. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  4. 推荐一款程序员性价比高的机械键盘 KeyChron K8 程序员性价比高的机械键盘 Keychron 我第一款 keychron 键盘是四年前(2020年)买的,当时在AMAZON工作,通过公司经费报销的。当时还问了经理,需要节俭么?因为亚马逊的16条军规中的一条就是节俭/Frugal。当时经理说,只要能提高你的工作效率就行,我也没太敢搞太贵的,最后面只挑了一个60多美元的机械键盘,型号是: Keychron C2 Wired Mechanical Keyboard, RGB Backlight / Gateron Mechanical...
  5. ChatGPT 使用 Promise.All 重构/重写代码(并行发送请求) 在我的一个项目中, 我有以下代码(Node/Javascript), 它(顺序)调用了一些 API. 这些 HTTPS 请求其实是可以并行完成的, 因为它们相互独立. 然后结果会被聚合到一个数组中. 我知道可以用Promise.all来把这些请求并行化, 我就问了ChatGPT怎么弄, 看看它是否能理解并给出正确的代码: 我先用英文问 ChatGPT-3.5: Rewrite...
  6. 投诉 npower 电气公司 之前讲过 npower 电气公司是多么的不靠谱. 一年过去了, 帐单都算不对. 几乎是每次收到帐单或者银行乱扣钱了(我是三个月来一次帐单, 不清楚怎么搞的一个月扣一次定额), 我总是打电话去, 每次得到的答复就是会把帐单修正过来, 但每次都没有. 简单来说, 就是一年前的圣诞节, 我让 npower 过来家里装上另一个电表,...
  7. 互联网大厂的 PIP 裁员计划 互联网大厂的 PIP 裁员计划...
  8. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...

编程: NodeJs/Javascript 函数检查Tron/波场区块链上的交易是否已确认(TronGrid API)

2025年1月6日 06:03

我们想知道给定的交易是否已经在 Tron/波场 区块链上确认,这可以通过 TronGrid API 轻松实现。

tron-blockchain 编程: NodeJs/Javascript 函数检查Tron/波场区块链上的交易是否已确认(TronGrid API) Javascript 区块链 波场 (TRON) 程序设计

tron-blockchain

为了确保交易在Tron/波场区块链上被确认,验证逻辑应关注交易的状态,这表明交易是否已被 Tron 虚拟机(TVM)成功处理。以下是正确的验证方法:

检查 receipt.result

验证交易成功的主要标志是 receipt.result 字段。值为 “SUCCESS” 表明交易已成功处理且无任何错误。

确认区块号

交易仅在被包含进一个区块后才算确认。确保 transactionInfo.blockNumber 已定义且与预期的区块一致。

考虑额外的指标

某些交易可能不会生成 net_usage,但可能会产生 net_fee。检查诸如 net_feenet_usage 的任何资源使用情况,可以让验证逻辑更加全面和完全可靠。

避免过早确认

确保交易不再是挂起状态,并已成功广播到网络中。

验证逻辑

以下是处理各种边界情况的逻辑:

const isConfirmed = transactionInfo =>
  transactionInfo.blockNumber !== undefined && // 确保交易已包含在区块中 
  transactionInfo.receipt && // Receipt is present
  (
    transactionInfo.receipt.result === 'SUCCESS' || // 明确的成功结果
    transactionInfo.receipt.net_usage > 0 || // 使用了资源
    transactionInfo.receipt.net_fee > 0 // 产生了费用
  );

代码解释:

  • transactionInfo.blockNumber:确保交易已成为区块链的一部分。
  • transactionInfo.receipt.result:确认交易执行的状态。
  • transactionInfo.receipt.net_usage / transactionInfo.receipt.net_fee:验证资源的使用情况,确保交易对链上状态有实际影响。

最佳实践:

  • 监控交易事件:使用 TronGrid 的事件订阅功能,实时跟踪交易的确认状态。
  • 增加确认等待:等待多个区块确认,以减少链重组导致交易无效的风险。
  • 记录并处理错误:如果交易失败,请检查 contractResult 和 logs 中的错误详情。

这种方法可确保 Tron 系统中的交易确认过程稳健可靠。

完整的 JavaScript函数代码用于验证波场区块链上的交易

以下是一个基于 Node.js(JavaScript)的函数,它可以通过 TronGrid API 确认或验证 Tron 区块链上的交易。此函数需要使用 axios 库来执行 HTTPS API 调用。

const axios = require('axios');

async function isTransactionConfirmed(transactionID, appKey) {
    try {
        const response = await axios.post(
            'https://api.trongrid.io/walletsolidity/gettransactioninfobyid',
            { value: transactionID },
            {
                headers: {
                    'accept': 'application/json',
                    'content-type': 'application/json',
                    'TRON-PRO-API-KEY': appKey
                }
            }
        );
  
        const transactionInfo = response.data;
        const isSuccess = (transactionInfo.receipt && transactionInfo.receipt.result === 'SUCCESS') || ((transactionInfo.blockNumber !== undefined) && (transactionInfo.receipt.net_usage > 0 || (transactionInfo.receipt.net_fee > 0)));
        const blockNumber = transactionInfo.blockNumber;
        
        console.log(`BlockNumber ${blockNumber}, ${JSON.stringify(transactionInfo)} Transaction ID: ${transactionID} - Status: ${isSuccess ? 'Confirmed' : 'Failed'}`);
        if (!isSuccess) {
            console.log(JSON.stringify(transactionInfo));
        }
        return {
          "ok": isSuccess, 
          "block": blockNumber
        };
    } catch (error) {
        console.error('Error verifying transaction:', error);
        throw error;
    }
}

英文:NodeJs/Javascript Function to Check if a Transaction is Confirmed on Tron Blockchain via TronGrid API

区块链技术

本文一共 540 个汉字, 你数一下对不对.
编程: NodeJs/Javascript 函数检查Tron/波场区块链上的交易是否已确认(TronGrid API). (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 编程: NodeJs/Javascript 函数检查Tron/波场区块链上的交易是否已确认(TronGrid API) Javascript 区块链 波场 (TRON) 程序设计
The post 编程: NodeJs/Javascript 函数检查Tron/波场区块链上的交易是否已确认(TronGrid API) first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...
  2. 剑桥网红餐厅 The Ivy Cambridge Brasserie 上周刚过40岁生日,媳妇带我去剑桥的一家网红餐厅——The Ivy Cambridge Brasserie。这家餐厅是朋友推荐的,和那家Trinity一样很有名,可惜我们在剑桥待了近10年才知道。 The Ivy Cambridge Brasserie 是剑桥市中心一家备受欢迎的网红餐厅,位于历史悠久的 Trinity Street。餐厅设计时尚典雅,融合了现代与经典元素,为食客提供了一个舒适而奢华的用餐环境。这里的菜单丰富多样,覆盖全天用餐,从早餐到晚餐以及下午茶,提供英式经典美食如松露鸡肉派、英式早餐等,也有一些国际风味的菜肴。得天独厚的位置和独特的氛围使得 The Ivy Cambridge Brasserie...
  3. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  4. 测测你的幸运 – Linux Fortune-Teller LINUX 下有很好很好玩的命令,之前已经介绍过: figlet, rig, curl. 现在推荐另一个 命令 fortune 是用来随机显示一段(句)话的.fortune 在英文里就是幸运的意思. 这个命令可以不需要 参数 如果没有 可以通过 apt-get...
  5. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....
  6. ChatGPT-4 使用 Math Wolfram 插件解决数学脑筋急转弯问题 这篇文章, 我们看一个简单的数学问题(脑筋急转弯), 并用 Python 解决它. 我们看一下LLM(大型语言模型): ChatGPT3.5和ChatGPT4. 通过 ChatGPT-Plus 订阅(目前每月 20 美元 + VAT增值税), 我们可以启用...
  7. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  8. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...

写在教娃编程700天, what’s next?

2024年12月28日 06:19

今年明显减少了教孩子编程的频率。从年初的一周两节课,逐渐减少到一节课,最终在今年8月26日迎来了第700天的里程碑。在此期间,还新增了一个系列——教孩子学习理财和赚钱的知识。

随着事情越来越多,孩子逐渐长大,学校的作业负担加重,再加上练琴等其他活动,时间变得格外紧张。经过反复考虑,决定不再设定硬性指标,改为随缘而教,有时间就继续。

从第1天到达成第700天,这段旅程已经非常不容易。第1天始于2020年11月22日,第700天是2024年8月26日,间隔1374天,平均每1.96天上一次编程课。我在第200天时曾立下计划,要坚持到1000天,但现在看来这个目标无法实现。到了500天后,开始剪辑教学视频,进度逐渐放缓。正所谓“行百里者半九十”,许多事情越接近目标,越难坚持。

回想起第一天教编程时,我还在亚马逊工作。3.76年后,我在微软剑桥研究院,而孩子在这4年中也发生了很大的变化。翻看早期的视频,可以清晰感受到孩子的成长,尤其是声音的变化,让人感慨万分。

教孩子编程不仅是开发智力的一种好方法,也让我更深入地了解了他们的兴趣与未来的方向。大儿子表示他未来想从事金融相关的工作,小儿子则对计算机领域充满热情,计划成为一名工程师,目前他对硬件相关方向更感兴趣。两娃的数学都很不错。

这700天的编程教学,不仅是孩子们的成长,也是我自身的提升。我通过费曼学习法,提高了自己的表达能力和英语口语水平。这段旅程,无疑是一段珍贵的记忆,也是一笔丰厚的收获。

完整700天的视频只上传到了B站和油管。西瓜视频从300天到697天(后面上传不了了:西瓜视频再也上传不了视频了(字节头条: 抖西合并)),微博视频从680天到700天,还有就是后面百来天上传到了微信视频号。

youtube-channel-doctorlai-700-days-teaching-kids 写在教娃编程700天, what's next? 教娃 教程 教育 生活 编程 育儿

油管频道doctorlai教娃视频700天

bilibili-700-days-teaching-kids-scaled 写在教娃编程700天, what's next? 教娃 教程 教育 生活 编程 育儿

B站教娃视频700天

教娃编程

对了:教娃编程有趣的瞬间 vlog视频集合

本文一共 765 个汉字, 你数一下对不对.
写在教娃编程700天, what’s next?. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 写在教娃编程700天, what's next? 教娃 教程 教育 生活 编程 育儿
The post 写在教娃编程700天, what’s next? first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 在英国给孩子换学校的经历: 孩子离开了村里的小学 由于搬了家, 孩子上学得提前半小时出门了, 因为早上堵, 也得开车半小时才能到. 之前在 Fen Drayton 村庄上小学, 早上8:45学校门开, 9点敲钟孩子排队依次进入教室, 我们由于在村里, 只需要提前5分钟出门和孩子一起走路就可以了. 现在一下子早上变得很匆忙, 得叫孩子起床, 做早饭,...
  4. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  5. 英国房子的EPC节能报告(Energe/Efficiency Performance Certificate) EPC (Energe/Efficiency Performance Certificate) 是英国房子的节能报告, 法律上规定, 每个房子都必须要有一个EPC报告, 报告的有效期为十年. 房东在把房子出租或者想卖房的时候, 这个EPC就必须有效, 在一些情况下 比如出租房子的时候, 这个EPC报告还必须符合一些最低标准, 比如房子必须满足 F档(类似及格线)...
  6. 公司请的专业摄影师 公司来了新的CEO管理之后,很多事情都不一样了, 特别是一些公司对外形象的事情就特别的在意, 比如公司网站用上SSL.现在公司还有空闲的位置,请速来(钱多人不傻). 一月份出差回LUTON,刚好公司请来摄影师给高层管理照像放网站上的,于是我也凑了凑热闹(但是却还不够资格被放在公司网站上),不过没关系,放这里也差不多. 人到中年, 沧桑感强了些. 更新更新: 同事用他NB的单反给谢菲尔得办公室的人也拍了一组这样的照片.看起来很不错, 很专业,灯光,道具应有尽有.我已经用在了LINKEDIN页面上,立马高大上. 本文一共 230 个汉字, 你数一下对不对. 公司请的专业摄影师. (AMP...
  7. 面向猫猫编程 Cat Oriented Programming (Chessly/Pyro这一生持续更新) 家里有两只猫 Chessly/Pyro,想着找个地方记录它们的生活,最后决定还是写在这里的博客。猫的一生很短,差不多也就二十年。 Chessly(黑白猫)是我加入微软剑桥研究院MSRC第一个月带回家的,过了两三个月,又把Pyro(橘猫)也接回了家。两只猫的名字是孩子们取的:Chessly因为黑白的像棋盘,加上“ly”听起来像个女孩的名字;而Pyro的意思是一团火(烟火),充满活力。 刚开始的时候,Chessly特别喜欢待在我的工作区域。她有时候趴在键盘上或旁边,有时候藏在显示器后面。偶尔还会绕到我身边“咕咕”地撒娇,等着我去摸她。有时更干脆跑到我腿上,舒舒服服地躺着。 不过,现在它们俩的体型都大了很多,躺在桌上就会挡住屏幕,真是“面向猫猫编程”(Cat Oriented Programming)的极致体验。 记录生活的点滴,也是一种珍惜,毕竟这二十年,我们会一起走过。 2024年 2025年 Ring视频:两猫日常就是打闹,Chessly追上Pyro想舔他,在猫的世界里,地位高的才能舔地位低的。 本文一共 357 个汉字, 你数一下对不对....
  8. 力扣 Leetcode 的刷题利器: 在线调试器和自动代码提示完成 力扣代码调试器 Debugger 最近 leetcode 刷题网站出了一个在线调试器. 个人感觉非常好用. 因为我平时是用 IPAD+蓝牙键盘来刷题, 而在 ipad 上是没有集成的IDE的, 对于调试来说, 只能很原始的让函数退出一个值, 然后尝试不同的输入来发现问题. leetcode在线调试器的好处...

ChatGTP帮助写.htaccess语句

2024年12月24日 09:16

Google Search Console提醒我有大量网页的「网址不规范」,都是类似这样的网址:https://jfsay.com/page/10?wptouch_switch=desktop&redirect=%2F%3Fwptouch_switch%3Ddesktop%26redirect%3D%252Farchives%252F269.html%26nonce%3D45b539a4fe&nonce=91c204e132,看起来是WPtouch跳转的URL,但是我已经卸载WPtouch插件好久了,不知道为什么Google还会访问这样的网址。

之前使用WPtouch插件好多年,它通过浏览器的UA判断是PC端还是移动端,为没有网页自适应的网站提供移动端的网页。后来把WordPress的主题更换成自适应的主题后,就卸载了WPtouch插件。

我首先想到会不会是WPtouch插件没有卸载干净或者有缓存。我花了好长时间,查找了网站的所有文件(将所有文件下载到本地用工具查找),并没有找到WPtouch的相关文件。我又怀疑是数据库有WPtouch的跳转语句,但是也没有找到。

在cPanel面板的「访客」页面,发现每天都有包含wptouch_switch的URL的访问记录,大部分都是Googlebot产生的。

我在网上找到一个帖子,和我的情况很像,标题是:删除了WPtouch但仍在使用中(Removed WPtouch but still in use)。下面有人回答说可以把网址进行301重定向。我想着这个方法也不错,也打算试下。

我想在.htaccess添加网址301重定向的语句,自己不会写,继续在网上查找,但是网上提供的现成的语句都是简单的,没有正好是我需要的。我试图自己学习RewriteCond和RewriteRule语法,然后自己来写。但是这个语法太难了,实在是看不懂,昨天晚上折腾了两三个小时,还是没有解决。

今天早上突然想到ChatGTP可以帮助写代码,于是试了一下。

我在对话框输入以下内容(请忽略我的塑料英语):

I want to redirect a URL using .htaccess. For example:
From url:https://jfsay.com/page/1?wptouch_switch=desktop&redirect=%2F%3Fwptouch_switch%3Ddesktop%26redirect%3D%252Farchives%252F269.html%26nonce%3D45b539a4fe&nonce=91c204e132 to url:https://jfsay.com/page/1
From url:https://jfsay.com/page/2?wptouch_switch=desktop&redirect=%2F%3Fwptouch_switch%3Ddesktop%26redirect%3D%252Farchives%252F269.html%26nonce%3D45b539a4fe&nonce=91c204e132 to url:https://jfsay.com/page/2
From url:https://jfsay.com/page/80?wptouch_switch=desktop&redirect=%2F%3Fwptouch_switch%3Ddesktop%26redirect%3D%252Farchives%252F269.html%26nonce%3D45b539a4fe&nonce=91c204e132 to url:https://jfsay.com/page/80

I want to redirect a URL using .htaccess. For example:
From url:https://jfsay.com/?wptouch_switch=mobile&redirect=%2Farchives%2F1.html&nonce=30cabb8867 to url:https://www.jfsay.com/archives/1.html
From url:https://jfsay.com/?wptouch_switch=mobile&redirect=%2Farchives%2F2.html&nonce=30cabb8867 to url:https://www.jfsay.com/archives/2.html
From url:https://jfsay.com/?wptouch_switch=mobile&redirect=%2Farchives%2F847.html&nonce=30cabb8867 to url:https://www.jfsay.com/archives/847.html

ChatGTP竟然知道我是要批量处理网址的重定向,而且知道哪个是「参数」,它给出了以下的答案。

RewriteEngine On
#Redirect for specific pages with the described query string
RewriteCond %{QUERY_STRING} ^wptouch_switch=desktop&redirect=.&nonce=.$ [NC]
RewriteRule ^page/([0-9]+)/?$ /page/$1? [R=301,L]

RewriteEngine On
#Redirect from mobile wptouch with a specific redirect parameter to the archives
RewriteCond %{QUERY_STRING} ^wptouch_switch=mobile&redirect=%2Farchives%2F([0-9]+).html&nonce=[a-zA-Z0-9]+$ [NC]
RewriteRule ^$ https://www.jfsay.com/archives/%1.html? [R=301,L]

我把上面的语句粘贴到.htaccess文件里去,试了一下,没想到网址成功跳转了。

ChatGTP太强大了,我昨天花了两三个小时都没有搞定的事情,它几分钟就给解决了。

现在我理解了搜索引擎和ChatGTP的区别了。搜索引擎是「死的」,它通过关键字查找,给出的结果是已经存在的,我们还需要对这些结果进行分析鉴别和后续加工;而ChatGTP是「活的」,可以主动思考,可以产生新的内容。比如我这个需求很难在搜索引擎里描述清楚,从而找到满意的的结果,而ChatGTP可以知道我要进行批量的网址重定向,而且准确地知道哪个是参数,只要你给它举个例子就行。这真的好强大!

Python 中寻找数据的众数: mode vs multimode

2024年12月23日 19:30

在 Python 中寻找数据的众数

statistics.mode() 函数是 Python 中 statistics 模块的一部分,它返回数据集中出现次数最多的单个值(众数)。与 multimode() 不同,mode() 如果数据集包含多个众数(即多模态数据)或数据为空,则会引发错误。

以下是一些示例来说明 mode() 的行为:

mode() 的语法

statistics.mode(data)

data: 一个序列(例如 list、tuple),其中的元素是可散列的,用于确定众数。

示例

单一众数(单模态数据)
from statistics import mode
data = [1, 2, 2, 3, 4]
result = mode(data)
print(result)  # 输出: 2
字符串作为数据
from statistics import mode
data = ["apple", "banana", "apple", "cherry"]
result = mode(data)
print(result)  # 输出: "apple"
多模态数据(引发错误)

如果有多个众数,mode() 会引发 StatisticsError。

from statistics import mode
data = [1, 1, 2, 2, 3]
try:
    result = mode(data)
except StatisticsError as e:
    print(e)  # 输出: "no unique mode; found 2 equally common values"
无重复值(引发错误)

如果数据集中没有值重复,mode() 会引发 StatisticsError。

from statistics import mode
data = [1, 2, 3, 4, 5]
try:
    result = mode(data)
except StatisticsError as e:
    print(e)  # 输出: "no unique mode; found 5 equally common values"
空数据集(引发错误)

如果数据集为空,mode() 会引发 StatisticsError。

from statistics import mode
data = []
try:
    result = mode(data)
except StatisticsError as e:
    print(e)  # 输出: "no mode for empty data"

在 Python 中寻找多众数

在 Python 中,术语 multimode 通常指 statistics.multimode() 函数,这是 Python 3.8 中 statistics 模块的一部分。此函数用于找到数据集中出现次数最多的值(众数)。与 statistics.mode() 不同,后者仅返回单个众数(如果数据集是多模态的会引发错误),而 multimode() 可以处理包含多个众数的多模态数据集。

语法

statistics.multimode(data)

data: 一个序列(例如 list、tuple),其中的元素是可散列的,用于查找众数。

行为

返回输入数据中所有众数的列表。如果没有元素重复,则返回所有唯一值的列表,因为在这种情况下每个值都是众数。

示例

单一众数
from statistics import multimode
data = [1, 2, 2, 3, 4]
result = multimode(data)
print(result)  # 输出: [2]
多个众数
from statistics import multimode
data = [1, 1, 2, 2, 3]
result = multimode(data)
print(result)  # 输出: [1, 2]
无重复值
from statistics import multimode
data = [1, 2, 3, 4, 5]
result = multimode(data)
print(result)  # 输出: [1, 2, 3, 4, 5]

主要特性

多模态支持:可以处理包含多个同频值的数据集。
优雅地处理唯一数据:如果没有重复值,则返回所有唯一值。
灵活的输入类型:适用于任何可散列对象的序列,包括字符串和元组。

字符串示例

data = ["apple", "banana", "apple", "cherry", "banana", "banana"]
result = multimode(data)
print(result)  # 输出: ['banana']

使用场景

  • 分析调查结果或投票中具有多个最受欢迎选项的情况。
  • 识别数据集中可能共享最高频率的频繁模式。

局限性

如果数据集很大,计算众数可能会消耗大量计算资源,因为它需要统计所有元素的出现次数。

mode 与 multimode 的比较

特性 mode() multimode()
返回值 单个最频繁的值 所有最频繁值的列表
多模态数据行为 引发 StatisticsError 返回所有众数
空数据集行为 引发 StatisticsError 返回空列表
最佳用途 适用于期望唯一众数的单模态数据 适用于包含多个众数的多模态数据或任意数据

如果不确定数据是否包含多个众数或无重复值,multimode() 是更安全的选择。

英文:The mode vs multimode in Python

本文一共 702 个汉字, 你数一下对不对.
Python 中寻找数据的众数: mode vs multimode. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c Python 中寻找数据的众数: mode vs multimode Python Python 学习笔记 数学 程序设计 计算机
The post Python 中寻找数据的众数: mode vs multimode first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 西瓜视频再也上传不了视频了(字节头条: 抖西合并) 上两个月,我再也没法在西瓜视频上上传我的视频了,也从此少了一个同步视频备份的地方了。现在登陆西瓜平台,要发视频的话会立马转到抖音创作者中心,然后我尝试通过手机号+SMS验证码的方式登录,提示我“请使用抖音手机APP登录” 原来西瓜视频国外的用户,可以通过手机号SMS+验证码的方式登陆网站,上传视频的。由于我人在英国,装的是英国的App Store,无法安装国内的西瓜视频APP,使用上有点不方便的。后来为了同步到国内的抖音平台,让我姐帮我绑定了她的抖音账号,可能就是因为这样,现在强制我得通过抖音平台来发视频了。等哪天有空我让我姐试试是否可以解绑。 西瓜视频已经合并到抖音平台上了,字节头条也不再单独运营西瓜视频了。 我发了邮件问西瓜视频,得到的回复:“您好,若您可以登录账号,请前往抖音,在设置内选择【我的客服】在线咨询,详细描述您的问题并提供问题界面截屏。若您无法登录账号,您可以拨打 95152 热线咨询,热线接听时间为8:30-22:00” 再问:您好,我们国外的用户没法装抖音啊。。。之前西瓜视频一直是 国外手机号登录的。 答:您好,您可以通过以下几种方法找到西瓜客服帮你解决问题:1.手机端入口:【西瓜视频App】更新至最新版本 【我的】-【创作中心】-【问题反馈】2.手机端入口:【西瓜视频App】-【我的】-【反馈与帮助】【意见反馈】3.电脑端入口:登陆【西瓜创作平台西瓜创作平台】点击右下角【问题咨询】。 西瓜视频是由字节跳动公司推出的一款视频分享平台,旨在提供用户一个便捷的观看和创作短视频的空间。西瓜视频自推出以来,凭借其丰富的内容、强大的推荐算法以及用户互动功能,逐渐成为中国大陆地区受欢迎的视频平台之一。 主要特点: 内容丰富:西瓜视频涵盖了多种类型的视频内容,包括娱乐、搞笑、影视、音乐、游戏、教育、科技等,满足不同用户的兴趣需求。 个性化推荐:依托字节跳动强大的机器学习和人工智能技术,西瓜视频通过分析用户的观看历史和行为习惯,提供精准的个性化推荐,提升用户的观看体验。...
  4. MySQL参数一键配置脚本: 有效提升数据库性能 我一直是自己租用VPS服务器,然后搭建各种服务,比如博客就是Apache2+MySQL数据库。一般来说就是默认参数,没有去管,不过最近发现MySQL的性能参数都很保守,不能发挥整个服务器的性能。 然后我就网上搜索了一下,根据参数配置建议,用ChatGPT写了以下Python和BASH脚本。只需要在需要优化的服务器上,跑一下该脚本,然后就会显示参数配置,然后直接把参数添加到MySQL数据库配置参数文件上: /etc/mysql/mysql.conf.d/mysqld.cnf 然后运行: service mysql restart 重启MySQL服务器。 运行了几周,发现效果很好,博客反应速度也快了很多,这很大原因是根据了内存增加了MySQL缓存大小。 Python脚本优化MySQL数据库参数 把下面的Python脚本存成 mysql_config.py 然后运行 python3 mysql_config.py...
  5. 世界再无OneKey币圈美元虚拟卡了 我前两年就了解到OneKey这个币圈虚拟货币出金卡,不过去年年底才注册使用的。当时还花了99美元一步升级到顶级黑卡。然后这一年陆陆续续用了这卡,但用得不多,主要就用于支持一些VPS主机费还有CloudFlare,ChatGPT Pro等。 这个卡是美国地址,卡号有两个段,Visa 和 Mastercard,不过由于地址是美国的,刷卡可能会有问题。比如我ChatGPT Pro注册帐号是英国的,然后用这卡支付了几个月,突然有一天帐号就被封,被告知:您的付款记录很可疑。 印象中,用这虚拟货币Crypto Card美元出金卡有手续费,但是并没有啥Cash Back返现卡,如果是非美元购物则会有另一笔手续费,所以我很少用这卡出金变现。 前两个月,OneKey宣布关闭: 关于 OneKey Card 服务停用通知 尊敬的用户,为提高服务质量和优化产品供应,我们将按照以下时间表停用...
  6. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  7. 步步高多媒体学生电脑 汇编程序设计 – 1 英文同步 90年代后期步步高生产的软驱一号(又称步步高多媒体学生电脑)和98型学生电脑都带了软驱,一按电源件, 都从软盘启动(98型可以从内置的电子盘启动) 步步高提供了直接在学习机上写汇编开发的工具 BASM. BASM 可以用来写 6502 汇编,并可以编译成 CMD 小型可执行程序 不支持 EXC 程序. CMD...
  8. 力扣刷题获得一件衣服奖励(Leetcode DCC Winner) 我每天都在力扣上刷题。力扣有国服和美服,我两边都会打卡。每天打卡可以获得积分,而积分可以兑换各种礼物,比如T恤、卫衣、水壶,甚至可以用来抵扣一个月的会员费用。 我从2018年8月开始刷题找工作(当时去伦敦面试亚马逊),从那时起每年都会续费会员,费用是159美元,相当于每月13.25美元。我觉得这是对自己最值得的投资。买了力扣会员,就会有动力刷题、思考,通过不断练习让自己熟能生巧,保持一定的竞争力。 到目前为止,我已经用积分兑换了7-8件力扣的衣服,其中有2-3件是卫衣。国内的礼物我都寄到姐姐家。 前不久,我收到了力扣的邮件,说我获得了DCC奖励。我也不知道为什么会获得这个奖,随手回了邮件。没多久,就收到了一件新版的力扣衬衫。 英文:Leetcode DCC Winner T-shirt 本文一共 291 个汉字, 你数一下对不对. 力扣刷题获得一件衣服奖励(Leetcode DCC Winner)....

程序员的神器: Github Copilot终于免费了(感谢微软爸爸)

2024年12月22日 22:03

微软这几年在AI领域的布局可谓精准。当年收购GitHub时,许多人还认为微软出价过高。然而,看看近年来推出的GitHub Copilot,以及对OpenAI的ChatGPT的投资,微软显然已经引领了这波AI浪潮。

懒人编程神器: Copilot

GitHub Copilot最初的订阅费用是每月10美元。虽然我平时工作时会用Copilot,因为作为微软员工可以免费使用,但如果要用于自己的side project,每月10美元的费用还是显得有些高。

或许是由于微软面临其他竞品的压力,最近微软终于将Copilot免费开放了。如果需要使用,可以通过这个链接安装Visual Studio Code的插件。

注意:免费的条件之一就是:开源、非盈利。

对于广大程序员来说,编程从来都不是一件轻松的事情。从思考代码逻辑到调试程序运行,每一步都需要花费大量的时间和精力。而如今,一款被誉为“程序员神器”的工具——GitHub Copilot,终于免费了!这项由微软和 OpenAI 联合开发的人工智能代码生成工具,已经从一开始的收费订阅模式,迈向了普惠程序员的新时代。

作为一名程序员,你是否曾苦恼于寻找灵感,或者为琐碎的代码撰写而感到疲惫?GitHub Copilot 就是为了解决这些问题而生的。它能够智能地分析你当前正在编写的代码,并实时为你提供相关建议,从简单的语句补全到复杂的函数实现,甚至是生成整段代码框架。这样一来,程序员不仅能显著提升开发效率,还能专注于解决核心问题,而不是被重复性工作拖累。

微软此次宣布让 GitHub Copilot 免费化,无疑是一个让整个开发者社区欢欣鼓舞的消息。作为背后的支持者,微软通过整合 OpenAI 的先进技术,将 Copilot 打造成了集代码补全、优化建议和智能代码解释于一体的强大工具。此前,尽管 Copilot 的付费订阅价格并不算高,但对于许多个人开发者和学生来说,仍然可能是一个负担。而现在,所有人都可以毫无门槛地体验这一切,得以更加自由地挥洒创意。

更令人兴奋的是,GitHub Copilot 的免费化并不意味着功能的妥协。微软表示,这一工具将持续更新,不断优化其算法和代码模型。未来,开发者可以期待更精准的代码建议、更少的误差,以及对更多编程语言的支持。这无疑为编程界带来了一个“生产力大爆炸”的新阶段。

当然,微软能够如此大方地放开 Copilot 的使用权限,背后离不开其一贯的战略眼光。近年来,微软不仅在云计算领域大放异彩,更通过 GitHub 和 OpenAI 在开发者生态系统中占据了重要位置。此次免费化,也进一步巩固了微软在全球开发者心中的地位。

作为程序员,我们要由衷地感谢“微软爸爸”。在现代科技快速发展的背景下,微软不仅提供了像 Visual Studio、Azure 等优秀的开发工具,还通过 GitHub Copilot 等创新型产品让编程变得更加轻松和有趣。

未来,GitHub Copilot 的免费化必将激发更多程序员的创造力,无论是初学者还是资深工程师,都能借助这一工具,更高效地解决技术难题。感谢微软为我们带来的这一场“代码革命”,让我们拭目以待,看看 Copilot 还会如何继续改变编程世界!

希望继续免费下去,AI产品也是越来越卷,能用免费的就绝对不用付费的,况且现在免费的产品也足够好用了。

use-github-copilot-for-free-scaled 程序员的神器: Github Copilot终于免费了(感谢微软爸爸) ChatGPT (OpenAI) I.T. 人工智能 (AI) 程序员 编程 资讯

免费用户可以每月2000次代码完成和50次和Copilot聊天,普通用户足够用了,不够可以升级到Pro,也就是10刀每个月,年付100刀。

github-copilot-vscode-extension 程序员的神器: Github Copilot终于免费了(感谢微软爸爸) ChatGPT (OpenAI) I.T. 人工智能 (AI) 程序员 编程 资讯

在Visual Studio Code里需要安装一个Microsoft Github Copilot插件

github-copilot-dashboard-anthropic-claude-3.5 程序员的神器: Github Copilot终于免费了(感谢微软爸爸) ChatGPT (OpenAI) I.T. 人工智能 (AI) 程序员 编程 资讯

在Github控制面板内可以使用免费的Copilot,甚至可以选Anthropic Claude 3.5 Sonnet模型

github-pro-plan 程序员的神器: Github Copilot终于免费了(感谢微软爸爸) ChatGPT (OpenAI) I.T. 人工智能 (AI) 程序员 编程 资讯

Github Copilot Pro每月10美元,年付100。

本文一共 1192 个汉字, 你数一下对不对.
程序员的神器: Github Copilot终于免费了(感谢微软爸爸). (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 程序员的神器: Github Copilot终于免费了(感谢微软爸爸) ChatGPT (OpenAI) I.T. 人工智能 (AI) 程序员 编程 资讯
The post 程序员的神器: Github Copilot终于免费了(感谢微软爸爸) first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  2. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  3. 在英国给猫铲屎的体验(英国猫砂比较和自动铲屎机器) 自从养了猫 就正式成为铲屎官了. 当初孩子说要养猫还说要帮忙呢, 哎, 入坑了. 两只猫每天的猫砂就得清理, 要不然猫不愿意上怕它们随处拉. 最开始的时候家里楼上是地毯, 猫很喜欢随地大小便然后抓抓抓掩埋起来, 把地毯都弄坏了, 后来索性全换上了地板. 猫儿们喜欢掩埋排泄物起来这是天性, 因为据说猫星人在野外是捕食者, 需要把味道隐藏起来. 最开始,...
  4. USB充电速度影响因素 手机充电的速度有快有慢, 有几个因素. 1. 用的是直流电还是交流电, 如果是直接插电源(交流电), 那么会快一些, 相反, 如果是插电脑, 速度就会慢. 2. 还有就是USB线的质量也有很大关系, 拿 USB2.0 来说, 有四条线,...
  5. 最好的发财策略就是忘记它 在群里我和 @nationalpark 说到我用STEEM换了3000多个 YOYOW的时候我还有点沾沾自喜, 谁知道他一针见血: 听说你是steem换的我顿时心理平衡了 才突然想起来, 我半个月前是拿 500 个 STEEM 换了 将近3300个YOYOW. 今天 STEEM的价格8美元,...
  6. 如何将 Matlab 里的 *.mat 文件导入到 Python 里? Matlab 的数据(矩阵,数组等)都可以用 *.mat 文件来保存. 可以通过 scipy.io 库在 Python 里读写 *.mat 文件. 1 import scipy.io import...
  7. 苏格兰六天五晚自驾游攻略 2022年8月的时候 自驾游 北上苏格兰, 第一天出发到了谢菲休息了一下见了一下老朋友. 6天5晚, 1000多英理, 到最后面一两天特别想念家里的两只猫 Chessly 和 Pyro. 00:00 谢菲尔德 Sheffield 00:58 格拉斯哥...
  8. 科学上网 通过 CloudFlare Worker 搭建 Js Proxy代理 CloudFlare 的 Worker 就是 Serverless (无服务器, 所有程序跑在云端). 以前是付费的, 现在免费了. 免费帐号可以创建30个 Worker, 每个帐号每天可以有10万次免费请求. 超过了可以配置 Fail Open...

十年: 力扣刷题2000天

2024年12月8日 03:00

前不久,力扣说我刷题刷了2000天,不是连续的,这个是一共刷的天数。2000天也就是5.479年。

我的力扣,欢迎围观:https://leetcode.com/u/justyy/

leetcode-2000-days 十年: 力扣刷题2000天 小技巧 程序员 编程 计算机 面试

力扣刷题2000天

注册力扣帐号是2013年10月10号,当天提交了6次。当时是在谢菲尔得/Sheffield

first-leetcode-submission 十年: 力扣刷题2000天 小技巧 程序员 编程 计算机 面试

注册力扣帐号是2013年10月10号,当天提交了6次。

后来就是断断续续,甚至有很长一段时间都没有刷,直到2019年3月2日开始才未间断刷到今天(当时拿着Ipad中午在美国通用电气的食堂里刷题)。2016年竟然4月份连续刷了25天,我已经忘记当时为啥这么有闲情逸致了。哦,当时已经搬到了剑桥/Fen Drayton了,可能在准备谷歌的面试。

019年在美国通用电气/General Electric工作的时候中午吃完饭在湖边着着IPAD刷题。

leetcode-on-ipad-scaled 十年: 力扣刷题2000天 小技巧 程序员 编程 计算机 面试

2019年在美国通用电气工作的时候中午吃完饭在湖边着着IPAD刷题

从2013年到2024的力扣刷题之路,一点点改变,一点点变好。

leetcode-from-2013-to-2024-scaled 十年: 力扣刷题2000天 小技巧 程序员 编程 计算机 面试

从2013年到2024的力扣刷题之路。

现在刷题是保持面试状态的好方法,就像飞机飞到高空时油耗已经减少,我希望自己能维持这个状态。每天打卡,哪怕有时只是拷贝代码,依然会稍微思考并熟悉解法和算法。每天坚持打卡还能积累积分,兑换力扣衬衫

leetcode-2024-11-26-18.20.49-rotated 十年: 力扣刷题2000天 小技巧 程序员 编程 计算机 面试

这次力扣奖励的衣服是新版的,感觉比之前的质量要好。

刷题:程序员的基本技能

英文:Milestone: Leetcoding for 2000 Days

网友:以后每天比比看是cursor做得快 还是你快。
答:刷题的目的又不是比快。让脑子思考,训练脑子。

网友:惭愧,我在 GE时候天天打酱油,群主确是每天在刷题飞。
网友:看似不起波澜的日复一日让我们看到坚持的意义!

本文一共 633 个汉字, 你数一下对不对.
十年: 力扣刷题2000天. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 十年: 力扣刷题2000天 小技巧 程序员 编程 计算机 面试
The post 十年: 力扣刷题2000天 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  4. 优化设计 个人主页 并且 PageSpeed Insights 双项 100分 坛子的个人主页 www.tanzhijun.com 不错 很适合个人主页的模板. 而且是手机友好. 于是我照着把 我的主页改了改. https://steakovercooked.com 并且做了几点修改: 0. 使用 google mod_pagespeed 把 JS,...
  5. ACM题解系列之 – 最小堆栈 (Min Stack) 没事刷刷题能防止老年痴呆, 而且也能让你随时处于最佳状态, 随时都可以炒老板鱿鱼另谋高就. 题目: 设计一个堆栈(Stack)使 push, pop, 和取最小 min 操作时间复杂度都是 O(1). 这题的难点就是在于怎么样用O(1)常数时间复杂度来取得堆栈里的最小值. class MinStack {...
  6. 在英国开车的简单介绍/英国开车上路需要准备什么? 在英国合法上路需要有: 有效的驾照; MOT 车的年检; 路税 (Road Tax);还有最重要的汽车保险; 四者缺一不可. 千万不要有侥幸心理, 因为警察现在都高科技, 都能扫描车牌就能知道你合不合法. 不合法直接拦下来轻则罚款, 重则扣车上述法庭. 驾照 在英国可以用欧盟的大部分驾照,...
  7. 记录那些值得回忆的精彩瞬间 媳妇 @happyukgo 其实挺反感我给她拍照片的, 但主要原因是 我经常把她拍得很丑, 而我除了会用美图秀秀简单的滤镜之外就不会一丁点儿的PS技术. 我家孩子 Eric 很爱臭美, 每次看着我拿相机, 总会让我 “Take a picture”, 然后各种摆姿势做鬼脸,...
  8. 老婆的配偶签证被拒 郁闷死了, 601镑签证费打水漂,一去不回!费钱费力. 去年12月份我请了律师拿到了永居.老婆是T1G签证的陪工签 (DEPENDENT VISA) 2016年4月份到期. 然后我就想说得趁早把她的签证转成配偶签(SPOUSE)这样她就可以尽快走五年永居的路线. 今天收到拒签信,原因是我没有提供 有工资进帐的那份银行帐单,我提供了我和我老婆的联名帐户, 但是工资并不是直接打到这个帐单上的.所以就这一点被拒了.完全不给解释,不给补材料的机会.601镑就这样再见了. 英国的签证寄出之后是先由另一个部门先收费, 收完费才正式审理,而且不管结果如何是不退钱的.后悔没让律师弄,也不至于到现在浪费这么多时间和金钱,签证还没过.由于原签证还没到期,所以还不能上述.估计只能等搬完家后年底请律师搞定这事. 真是郁闷, 600镑, 我可以再买一个IPHONE6,或者给我的新买的车换四个轮胎....

教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型

2024年12月2日 04:20

视频:油管/Youtube | B站/小破站 | 微博视频 | 西瓜视频 | 微信视频号 | X/推特 | 小红书

马斯克的x.ai到年底有免费的25美元的credit可以使用Grok大语言模型

前不久(今年初),伊隆·马斯克/Elon Musk的X公司开源了Grok大语言模型,并且给免费提供了25美元的credit可以调用。可以在其官网x.ai注册一个帐号,申请API KEY即可,官网还贴心的的给出了调用的例子。

curl https://api.x.ai/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer xai-......" -d '{
  "messages": [
    {
      "role": "system",
      "content": "You are a test assistant."
    },
    {
      "role": "user",
      "content": "Testing. Just say hi and hello world and nothing else."
    }
  ],
  "model": "grok-beta",
  "stream": false,
  "temperature": 0
}'

孩子们由于未成年,所以无法申请ChatGPT、X AI等大语言模式的帐号,平时他们只能在免费的微软冰/BING搜索引擎上使用集成的免费Copilot。不过今天听弟弟说,ChatGPT现在已经不需要登陆就可以使用,不过他说这个版本有点受限制。

平均长度来算的话,一句话的Prompt大概是0.0012美元。当然越长的句子花费越贵,毕竟价格是按Token来算的。可以粗略的估计一个单词是一个Token。

x.ai-usage 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

目测每条Prompt的费用是0.0012美元,25美元可以使用大概2万次

x.ai-free-25-credit 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

每个帐号有免费的25美元API费用

x.ai-dashboard 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

X.AI 的API网站界面

x.ai-api-models 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

API 的 X AI 模型:grok-beta 和 grok-vision-beta

grok-x-ai-api-keys 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

由 X AI 为 Grok LLM 创建 API 密钥。 Create API Keys for Grok LLM by X AI.

ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型

反正是免费的25美元,于是想着给娃做一个简单的PYTHON程序,然后人机交互,每次调用x.ai的Grok大语言模式,也正好让娃学一学实际的编程应用。于是让ChatGPT写了个程序,这种简单的程序ChatGPT基本上是Bug Free,生成的代码拿来就能用。

import requests
import json

api_key = "x_ai ..."

# Define the API endpoint and headers
url = "https://api.x.ai/v1/chat/completions"
headers = {
    "Content-Type": "application/json",
    f"Authorization": "Bearer {api_key}",
}

# Define a system message for context
system_message = {"role": "system", "content": "You are a test assistant."}

print("Welcome to the Grok, an AI chatbot. Type 'bye' to exit.\n")

while True:
    # Prompt the user for input
    user_input = input("You: ").strip()

    # Check if the user wants to exit
    if user_input.lower() == "bye":
        print("Goodbye!")
        break

    if user_input == "":
        continue

    # Define the payload
    payload = {
        "messages": [
            system_message,
            {"role": "user", "content": user_input}
        ],
        "model": "grok-beta",
        "stream": False,
        "temperature": 0
    }

    try:
        # Make the request
        response = requests.post(url, headers=headers, json=payload)

        # Check the response status
        if response.status_code == 200:
            data = response.json()
            assistant_response = data["choices"][0]["message"]["content"]
            print(f"Grok: {assistant_response}\n")
        else:
            print(f"Error: {response.status_code} - {response.text}")
    except Exception as e:
        print(f"An error occurred: {e}")

之后 简单做了些修改,比如避免空的Prompt,并且用strip函数去除句首和句尾的空格。娃使用的是Mac苹果电脑,还得在Terminal装个Homebrew,然后安装Python,并且用 pip3 install requests 安装上请求包,就可以使用了。

x-ai-prompt-python-2024-12-01-12.53.31-scaled 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

虽然界面有点素,也就是个简单的终端,但是对于孩子来说已经是个很强大的软件了。

kid-plays-x-ai-grok-llm-2024-12-01-12.53.26-scaled 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计

弟弟在苹果电脑上成功跑起了x.ai的大语言模式 Grok

ChatGPT 可以拿来做什么?

ChatGPT 通用人工智能

英文:ChatGPT writes a Python Script to Interact with Grok LLM from x.ai (Free $25 Credit)

本文一共 743 个汉字, 你数一下对不对.
教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 ChatGPT (OpenAI) Grok (X.AI) Python 人工智能 (AI) 折腾 教娃 教程 教育 程序员 程序设计
The post 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....
  2. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...
  3. 测测你的幸运 – Linux Fortune-Teller LINUX 下有很好很好玩的命令,之前已经介绍过: figlet, rig, curl. 现在推荐另一个 命令 fortune 是用来随机显示一段(句)话的.fortune 在英文里就是幸运的意思. 这个命令可以不需要 参数 如果没有 可以通过 apt-get...
  4. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  5. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  6. 你要找什么样的老婆? 找媳妇的标准 昨天和网友在剑桥面基, 网友奔现, 他从爱尔兰过来, 小我12岁, 就聊到了找对象的标准. TLDR; 找老婆不要(只)看颜值, 而要注重性格, 为人处事和顾家等更重要的品质, 当然性和谐也很重要. 在当今社会, 人们对于找伴侣的标准有所不同. 有些人认为颜值是最重要的, 因为外貌吸引力可以让人在日常生活中感到愉悦, 这是人的本性,...
  7. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  8. 给STEEM中文微信群加了个机器人 之前说到我的公众号 justyyuk 可以查询几种虚拟货币的实时价钱, 但是有点不方便, 因为很多朋友在群里聊天得切换到公众号, 这下好了, 今天往STEEM中文微信群(还有编程群)加了个机器人, 在聊天的时候想了解价钱就直接输入货币代号即可, 如: 既方便自己, 又能方便别人(省事, 价格信息会同时显示给其它成员). 注: 这机器人不是我做的, 只是我拉进来的,...

基于P站(Poloniex)的广度优先搜索算法来获得任意两种币的兑换价格

2024年11月18日 04:36

币圈的P站是Poloniex,前几年被孙宇晨收购了,它是一个交易所。我很久之前用过Poloniex,当时对其印象并不是很好。

不过,现在我对其好感增加,因为币安买下的coinmarketcap免费的接口就很多限制。

官方文档),这个接口的频率限制是一秒200次,很慷慨了。

https://api.poloniex.com/markets/price

能返回所有交易配对,比如这样的:

[
    {
        "symbol": "BTS_BTC",
        "price": "0.0000000186",
        "time": 1731852313035,
        "dailyChange": "-0.0462",
        "ts": 1731852313054
    },
    {
        "symbol": "DASH_BTC",
        "price": "0.000317",
        "time": 1731848096481,
        "dailyChange": "0.0063",
        "ts": 1731848096489
    },
    ... ...
]

这个JSON返回的结构是一个数组,每个元素是个结构体,也就是一个币价的具体配对信息,我们可以看成是一条边Edge两个顶点Vertice,这样就是一个图结构(带权图 Weighted Graph,权值就是兑换价格),虽然给的是单边,但其实是个双向的,比如USD_BTC得值可以反过来推得BTC到USD的价格。我们可以设计一个算法,从币价A到币价B,可以通过BFS广度优先搜索算法来获取价格。比如有配对A_B、B_C、C_D我们就可以获得A_D的值。

深度优先搜索算法DFS也可以,不过这个算法会返回找到的第一条路径,并不能保证是最短的,最短的确实是最准确的,因为链也长,转换精度就会下降。

当然,可能存在多条路径,最理想的状态是把这些路径都求出来,取个平均啥的,不过这样就得暴力搜索所有的路径了,算法时间复杂度就会比较高。

以下是BFS广度优先算法的代码,Javascript的,可以用于网页前端或者NodeJs后端,甚至是CloudFlare Serverless Worker或者是其它无服务框架:Azure Function、AWS Lambda等。

const fetch = require('node-fetch');

async function getTicker(a, b) {
  try {
    const response = await fetch('https://api.poloniex.com/markets/price');
    const data = await response.json();

    // 创建一个哈希表来存储代币对及其价格
    const pairMap = new Map();

    // 使用直接对及其反向对填充哈希表
    for (const { symbol, price } of data) {
      const [base, quote] = symbol.split('_').map(token => token.toLowerCase());
      if (!pairMap.has(base)) pairMap.set(base, new Map());
      if (!pairMap.has(quote)) pairMap.set(quote, new Map());
      
      pairMap.get(base).set(quote, parseFloat(price));
      pairMap.get(quote).set(base, 1 / parseFloat(price)); // 添加反向边
    }

    // 将 token 转换为小写
    a = a.toLowerCase();
    b = b.toLowerCase();

    // BFS 查找从 a 到 b 的转换率
    const queue = [[a, 1]]; // 从初始代币和兑换率 1 开始
    const visited = new Set([a]);

    while (queue.length > 0) {
      const [currentToken, currentRate] = queue.shift();

      if (currentToken === b) return currentRate;

      // Check connected tokens
      for (const [nextToken, rate] of (pairMap.get(currentToken) || new Map())) {
        if (!visited.has(nextToken)) {
          visited.add(nextToken);
          queue.push([nextToken, currentRate * rate]);
        }
      }
    }

    // 如果未找到路径,则返回 null
    return null;
  } catch (error) {
    console.error("获取或处理数据时出错:", error);
    return null;
  }
}

// Example usage:
(async () => {
  const rate = await getTicker('btc', 'trx');
  console.log('BTC 到 TRX 的兑换率:', rate);
})();

代码的一些简单说明:

  • API 数据提取:从 P站 API 提取数据并将响应解析为 JSON。
  • 映射对:以每个代币作为键创建一个映射,其中值是它可以直接转换为的另一个代币映射,以及兑换率。
  • 双向映射:通过反转反向转换的价格来存储直接对和反向对。
  • 广度优先搜索:使用队列遍历从 a 到 b 的路径。对于每个代币,都会检查其邻居(可转换代币)。如果找到 b,该函数将返回累积率;如果没有,则继续,直到所有选项都用尽。
  • 处理无路径:如果未找到转换路径,则函数返回 null。

如果有多组兑换,我们可以改成传入一个数组,这样就不用多次调用P站的API了。

const fetch = require('node-fetch');

async function getToken(pairs) {
  try {
    const response = await fetch('https://api.poloniex.com/markets/price');
    const data = await response.json();

    // 创建一个哈希表来存储代币对及其价格
    const pairMap = new Map();

    // 使用直接对及其反向对填充哈希表
    for (const { symbol, price } of data) {
      const [base, quote] = symbol.split('_').map(token => token.toLowerCase());
      if (!pairMap.has(base)) pairMap.set(base, new Map());
      if (!pairMap.has(quote)) pairMap.set(quote, new Map());
      
      pairMap.get(base).set(quote, parseFloat(price));
      pairMap.get(quote).set(base, 1 / parseFloat(price)); // 添加一条反向边
    }

    // 使用 BFS 查找单个对的转换率的辅助函数
    const findConversionRate = (a, b) => {
      a = a.toLowerCase();
      b = b.toLowerCase();
      
      if (a === b) return 1; // 直接转换

      const queue = [[a, 1]];
      const visited = new Set([a]);

      while (queue.length > 0) {
        const [currentToken, currentRate] = queue.shift(); // 出队列

        if (currentToken === b) return currentRate;

        for (const [nextToken, rate] of (pairMap.get(currentToken) || new Map())) {
          if (!visited.has(nextToken)) {
            visited.add(nextToken);
            queue.push([nextToken, currentRate * rate]);
          }
        }
      }

      return null; // 路径没找到
    };

    // 迭代列表并查找转换率
    const results = pairs.map(([a, b]) => findConversionRate(a, b));
    return results;
  } catch (error) {
    console.error("Error fetching or processing data:", error);
    return pairs.map(() => null); // 如果有错误,则返回每对的 null
  }
}

// Example usage:
(async () => {
  const conversionRates = await getToken([['btc', 'trx'], ['usd', 'steem']]);
  console.log('兑换结果:', conversionRates);
})();

简单的代码说明:

  • 参数更新:getToken 现在接受成对的元组数组,其中每个元组代表一对 [a, b]。
  • 辅助函数:findConversionRate 处理每对的转换,实现与以前相同的 BFS 方法。
  • 映射每对:函数迭代数组里的每个配对币,应用 findConversionRate 计算转换率,并将结果存储在数组中。
  • 错误处理:如果出现 API 或处理错误,则返回一个空值数组,与输入数组的长度匹配。

这个修改后的函数现在可以接受一个数组,并在一次Poloniex API调用中返回数组里每个配对的兑换率。

英文:Crypto Token Exchange Rate Computation Based on Breadth First Search Algorithm on Poloniex API

区块链技术

Crypto虚拟货币交易所

交易所跑路啦

本文一共 1127 个汉字, 你数一下对不对.
基于P站(Poloniex)的广度优先搜索算法来获得任意两种币的兑换价格. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 基于P站(Poloniex)的广度优先搜索算法来获得任意两种币的兑换价格 Javascript Poloniex P站 交易所 Crypto Exchanges 加密货币 区块链 比特币 BTC 程序设计 算法 编程 计算机 计算机 软件工程
The post 基于P站(Poloniex)的广度优先搜索算法来获得任意两种币的兑换价格 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....
  2. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...
  3. 测测你的幸运 – Linux Fortune-Teller LINUX 下有很好很好玩的命令,之前已经介绍过: figlet, rig, curl. 现在推荐另一个 命令 fortune 是用来随机显示一段(句)话的.fortune 在英文里就是幸运的意思. 这个命令可以不需要 参数 如果没有 可以通过 apt-get...
  4. 负电价活久见: 安装Octopus智能电表省电费甚至赚钱 前几周我的电气公司 Octopus 终于来装智能电表了(Smart Meter),虽然是免费安装的,但是排队排了有两三年了吧。因为之前一直写邮件催的时候就老是说 Not Ready。 收到邮件说可以安装智能电表我还是相当开心和期待的,因为已经听说这玩意好,但是还是得亲身体验一下。工程师来安装大概不到2小时,其中需要停电闸一会儿,重新接下线。装好后,给了个小册子,自动切换到了 Agile 的电价,也就是每半小时的电价都不一样,提前一天可以在手机App和网站上查得。 正好在原来的电价计费合同快要结束前2天换到了智能电表计价 Octopus Agile方式,但是系统还是扣了我75英镑 Exit Fee (提前合同结束得交违约费),不过我一个电话打过去,公司很爽快就给我退了。...
  5. ChatGPT-4 使用 Math Wolfram 插件解决数学脑筋急转弯问题 这篇文章, 我们看一个简单的数学问题(脑筋急转弯), 并用 Python 解决它. 我们看一下LLM(大型语言模型): ChatGPT3.5和ChatGPT4. 通过 ChatGPT-Plus 订阅(目前每月 20 美元 + VAT增值税), 我们可以启用...
  6. 微软面试题: 三角形的面积是多少? 据说是一个印度人杀入微软最后的面试, 面试官给了这么一道小学数学几何题: 这哥门也有疑问 可是最后还是坚持 答案 30 (底 X 高 / 2) 不存在 这是个陷井: 这个直角三角形是不存在的. 两个小直角三角形的勾股定理:...
  7. 给STEEM中文微信群加了个机器人 之前说到我的公众号 justyyuk 可以查询几种虚拟货币的实时价钱, 但是有点不方便, 因为很多朋友在群里聊天得切换到公众号, 这下好了, 今天往STEEM中文微信群(还有编程群)加了个机器人, 在聊天的时候想了解价钱就直接输入货币代号即可, 如: 既方便自己, 又能方便别人(省事, 价格信息会同时显示给其它成员). 注: 这机器人不是我做的, 只是我拉进来的,...
  8. Javascript 中 sleep 函数实现 Javascript 中并没有 built-in 的 sleep 函数支持, 在 async/await/Promise 的支持之前, 我们可以用 busy-waiting 的方式来模拟: 1 2 3...

C++的异步编程: std::future, std::async 和 std::promise

2024年11月11日 20:34

在 C++ 中,std::future 和 std::async 是 C++11 标准 并发库的一部分。它们允许您异步/Asynchronous运行任务并在稍后获取结果,非常适合编写非阻塞代码和并行化计算。以下是它们的工作原理和典型用法。

C++ std::async

std::async 是一个高级函数,允许您异步启动一个任务(一个可调用对象,如函数或 lambda)。您指定要运行的函数,std::async 返回一个表示该 函数结果的 std::future。您可以稍后获取该结果,无论是任务完成时还是您需要时。

#include <iostream>
#include <future>
#include <chrono>

int compute() {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟工作
    return 42;  // 计算结果
}

int main() {
    // 异步启动任务
    std::future<int> result = std::async(std::launch::async, compute);

    // 当 compute() 运行时执行其他任务...

    // 获取 compute() 的结果(如果任务未完成则会等待)
    int value = result.get();
    std::cout << "结果: " << value << std::endl;

    return 0;
}

在此示例中:

  • std::async(std::launch::async, compute) 异步启动 compute(),返回一个 std::future 对象。
  • result.get() 调用等待任务完成(如果尚未完成)然后获取结果。

C++ std::launch 策略

  • std::launch::async: 强制任务在新线程上异步运行。
  • std::launch::deferred: 推迟执行直到 future 上调用 get() 或 wait(),使其同步运行。

如果省略策略,C++ 可能根据实现定义的标准选择其中之一。

C++ std::future

std::future 是一个类模板,表示稍后获取的结果。它本质上是一个异步操作结果的占位符。

关键方法

  • get(): 如果结果尚未完成则等待并获取结果。调用 get() 后,std::future 变为空。
  • wait(): 等待任务完成,但不获取结果。
  • valid(): 如果 std::future 具有共享状态(即有结果可用)返回 true。
  • wait_for() 和 wait_until(): 等待特定时间或直到结果可用的截止时间。

带有 wait_for 的示例

if (result.wait_for(std::chrono::seconds(1)) == std::future_status::ready) {
    std::cout << "结果已就绪: " << result.get() << std::endl;
} else {
    std::cout << "仍在等待结果..." << std::endl;
}

此代码检查结果是否在 1 秒内就绪。如果没有就绪则继续执行,不 阻塞

C++ std::promise

对于更高级的用法,std::promise 允许您手动设置 std::future 的结果。std::promise 对象提供一个 std::future,您可以显式设置结果。

#include <iostream>
#include <future>
#include <thread>

void setPromise(std::promise<int> p) {
    p.set_value(10);  // 设置 promise 的结果
}

int main() {
    std::promise<int> p;
    std::future<int> f = p.get_future();  // 从 promise 获取 future

    std::thread t(setPromise, std::move(p));  // 将 promise 传递给线程
    t.join();

    std::cout << "promise 的结果: " << f.get() << std::endl;  // 获取结果
    return 0;
}

在此例子中,结果 10 是由 std::promise 设置的,std::future 可以通过 f.get() 获取。

C++ Future/Async/Promise 总结

  • std::async: 启动一个异步运行的任务,返回一个 std::future。
  • std::future: 表示将来会设置的值,通常是异步任务的返回值。
  • std::promise: 允许手动设置 std::future 的结果。

使用这些功能,您可以有效地管理 C++ 中的异步任务,更轻松地并行运行计算或将工作分配给其他线程而不阻塞主线程。

英文:Tutorial on C++ Future, Async and Promise

C/C++编程

本文一共 606 个汉字, 你数一下对不对.
C++的异步编程: std::future, std::async 和 std::promise. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c C++的异步编程: std::future, std::async 和 std::promise C++ C++ 学习笔记 程序设计 计算机
The post C++的异步编程: std::future, std::async 和 std::promise first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 步步高学生电脑上 Basic 编程语言 peek 用法示例 步步高学生电脑 是8位FC机的经典之作.它上面的BASIC有三个版本 1.0, 2.0 和 2.1 2.1 版本有个在线帮助,实际上是 help.cmd 1.0 是用 Esc 键退回到 DOS 的,...
  2. 换了个奥迪Q5大灯花了我1000英镑 我那辆奥迪Q5 SUV今年年检没通过,原因是左前车灯坏了,需要更换。车厂告诉我,光是订购零件就要700多英镑,加上人工费,总费用得1000英镑。但没办法,如果不修,车辆年检(MOT)就过不了,车也不能上路。 MOT是英国的机动车强制性安全检测(Ministry of Transport Test)的简称。 近侧前位置灯不工作 drl/位置灯集成(4.2.1(a)(ii)) Nearside Front Position lamp not working drl/position...
  3. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  4. HPZ800服务器主板太老不支持超过2TB的大硬盘 我家里一直用的是HPZ800服务器, 很吵, 很老, 虽然这台服务器已经有十年之久(我在EBAY上买来用了五年多了), 但是即使放到今天, 这服务器速度依旧很快, 很稳定. 由于服务器用的是ECC较验内存, 所以基本上不重启关机. HPZ800主机有两个硬核CPU – 因特志强 X5650 – 每个CPU是12核....
  5. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  6. ChatGPT-4 使用 Math Wolfram 插件解决数学脑筋急转弯问题 这篇文章, 我们看一个简单的数学问题(脑筋急转弯), 并用 Python 解决它. 我们看一下LLM(大型语言模型): ChatGPT3.5和ChatGPT4. 通过 ChatGPT-Plus 订阅(目前每月 20 美元 + VAT增值税), 我们可以启用...
  7. Javascript 中 sleep 函数实现 Javascript 中并没有 built-in 的 sleep 函数支持, 在 async/await/Promise 的支持之前, 我们可以用 busy-waiting 的方式来模拟: 1 2 3...
  8. 给孩子零花钱培养孩子正确的金钱观价值观 两个娃已经不知不觉7岁8岁了. 媳妇和我商量一下决定给孩子每人每周5英镑的零花钱(Pocket Money). 这样他们慢慢的就有自己的小积蓄备将来不时之需: 比如朋友聚会生日啥的需要准备礼物. 同时, 我们决定不再给孩子买零食(薯片啥的). 孩子一天好几餐, 晚上睡觉前还得吃零食, 我们就多买了很多水果面包, 健康的食物多吃一些总不是啥坏事. 孩子可以用这些零钱买自己想要的东西, 我们也不再过问. 孩子有自己的决定权. 第一周的时候,...

GitHub 库自动同步脚本分享

2024年4月15日 00:00

杜老师复刻了 70 多个库,每次源库更新后都需要一一手动同步,太过麻烦。今天分享一个自动同步脚本,有需要的小伙伴可以试一下。注意如对本地库有修改,建议使用 PR 来同步,避免代码覆盖。

代码同步

不太清楚小伙伴们同步代码方式,有人习惯用 PR,有人喜欢用下图同步的方式。不管哪种方式,都需要手动操作的。如有仓库过多,每个都要同步一遍,想想是多大工作量。杜老师分享了 GitHub 库自动同步脚本,供有需要的小伙伴参考:

脚本分享

进入要同步的库中,切换至 Actions,点击 New workflow 项:

打开新页面后,点击篮字的 set up a workflow yourself:

设置文件名 sync.yml「可自定义,不与其它脚本同名即可」

将下面的脚本填到输入框中,点击右上方 Commit changes 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
name: Upstream Sync

permissions:
contents: write

on:
schedule:
- cron: "0 0 * * *" # every day
workflow_dispatch:

jobs:
sync_latest_from_upstream:
name: Sync latest commits from upstream repo
runs-on: ubuntu-latest
if: ${{ github.event.repository.fork }}

steps:
# Step 1: run a standard checkout action
- name: Checkout target repo
uses: actions/checkout@v4

# Step 2: run the sync action
- name: Sync upstream changes
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with:
upstream_sync_repo: arnidan/nsfw-api
upstream_sync_branch: main
target_sync_branch: main
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set

# Set test_mode true to run tests instead of the true action!!
test_mode: false

- name: Sync check
if: failure()
run: |
echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,您需要手动 Sync Fork 一次。"
echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork."
exit 1

9 个实用的 Shell 脚本

2024年2月12日 00:00

好久没更新了,实在不知道写点什么好,就在网上找了一些资源。正文是杜老师整理的 9 个实用 Shell 脚本,供有需要的小伙伴参考。需要注意的是,这些脚本为杜老师收集,并没有测试过,小伙伴们使用之前要先测试。

写在最前

常来的小伙伴应该发现杜老师说近两周没有更新了,过年期间确实有太多的事情需要处理,各种亲戚走动等等。目前已经处理差不多了,工作上的节奏也已慢慢稳定,近期开始补上之前拖更文章。开头也祝愿来访的小伙伴们龙年大吉,博客访问蒸蒸日上,身体和服务器健健康康,心想和收入皆遂意!

DoS 攻击防范自动屏蔽攻击 IP

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
DATE=$(date +%d/%b/%Y:%H:%M)
LOG_FILE=/usr/local/nginx/logs/demo2.access.log
ABNORMAL_IP=$(tail -n5000 $LOG_FILE | grep $DATE | awk '{a[$1]++}END{for(i in a)if(a[i]>10)print i}')
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL | grep -c "$IP") -eq 0 ]; then
iptables -I INPUT -s $IP -j DROP
echo "$(date +'%F_%T') $IP" >>/tmp/drop_ip.log
fi
done

Linux 系统发送告警邮件的脚本

1
2
3
4
5
# yum install mailx
# vi /etc/mail.rc
set from=baojingtongzhi@163.com smtp=smtp.163.com
set smtp-auth-user=baojingtongzhi@163.com smtp-auth-password=123456
set smtp-auth=login

MySQL 数据库的单循环备份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
DATE=$(date +%F_%H-%M-%S)
HOST=localhost
USER=backup
PASS=123.com
BACKUP_DIR=/data/db_backup
DB_LIST=$(mysql -h$HOST -u$USER -p$PASS -s -e "show databases;" 2>/dev/null | egrep -v "Database|information_schema|mysql|performance_schema|sys")

for DB in $DB_LIST; do
BACKUP_NAME=$BACKUP_DIR/${DB}_${DATE}.sql
if ! mysqldump -h$HOST -u$USER -p$PASS -B $DB >$BACKUP_NAME 2>/dev/null; then
echo "$BACKUP_NAME 备份失败!"
fi
done

MySQL 数据库的多循环备份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
DATE=$(date +%F_%H-%M-%S)
HOST=localhost
USER=backup
PASS=123.com
BACKUP_DIR=/data/db_backup
DB_LIST=$(mysql -h$HOST -u$USER -p$PASS -s -e "show databases;" 2>/dev/null | egrep -v "Database|information_schema|mysql|performance_schema|sys")

for DB in $DB_LIST; do
BACKUP_DB_DIR=$BACKUP_DIR/${DB}_${DATE}
[ ! -d $BACKUP_DB_DIR ] && mkdir -p $BACKUP_DB_DIR &>/dev/null
TABLE_LIST=$(mysql -h$HOST -u$USER -p$PASS -s -e "use $DB;show tables;" 2>/dev/null)
for TABLE in $TABLE_LIST; do
BACKUP_NAME=$BACKUP_DB_DIR/${TABLE}.sql
if ! mysqldump -h$HOST -u$USER -p$PASS $DB $TABLE >$BACKUP_NAME 2>/dev/null; then
echo "$BACKUP_NAME 备份失败!"
fi
done
done

Nginx 的访问日志按天切割

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
LOG_DIR=/usr/local/nginx/logs
YESTERDAY_TIME=$(date -d "yesterday" +%F)
LOG_MONTH_DIR=$LOG_DIR/$(date +"%Y-%m")
LOG_FILE_LIST="default.access.log"

for LOG_FILE in $LOG_FILE_LIST; do
[ ! -d $LOG_MONTH_DIR ] && mkdir -p $LOG_MONTH_DIR
mv $LOG_DIR/$LOG_FILE $LOG_MONTH_DIR/${LOG_FILE}_${YESTERDAY_TIME}
done

kill -USR1 $(cat /var/run/nginx.pid)

Nginx 访问日志的分析脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
#日志格式: $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"
LOG_FILE=$1
echo "统计访问最多的 10 个 IP"
awk '{a[$1]++}END{print "UV:",length(a);for(v in a)print v,a[v]}' $LOG_FILE | sort -k2 -nr | head -10
echo "----------------------"

echo "统计时间段访问最多 IP"
awk '$4>="[01/Dec/2018:13:20:25" && $4<="[27/Nov/2018:16:20:49"{a[$1]++}END{for(v in a)print v,a[v]}' $LOG_FILE | sort -k2 -nr | head -10
echo "----------------------"

echo "统计访问最多 10 个页面"
awk '{a[$7]++}END{print "PV:",length(a);for(v in a){if(a[v]>10)print v,a[v]}}' $LOG_FILE | sort -k2 -nr
echo "----------------------"

echo "统计访问页面状态码的数量"
awk '{a[$7" "$9]++}END{for(v in a){if(a[v]>5)print v,a[v]}}'

查看网卡实时流量脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
NIC=$1
echo -e " In ------ Out"
while true; do
OLD_IN=$(awk '$0~"'$NIC'"{print $2}' /proc/net/dev)
OLD_OUT=$(awk '$0~"'$NIC'"{print $10}' /proc/net/dev)
sleep 1
NEW_IN=$(awk '$0~"'$NIC'"{print $2}' /proc/net/dev)
NEW_OUT=$(awk '$0~"'$NIC'"{print $10}' /proc/net/dev)
IN=$(printf "%.1f%s" "$((($NEW_IN - $OLD_IN) / 1024))" "KB/s")
OUT=$(printf "%.1f%s" "$((($NEW_OUT - $OLD_OUT) / 1024))" "KB/s")
echo "$IN $OUT"
sleep 1
done

服务器系统配置初始化脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#/bin/bash
#设置时区同步时间
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
if ! crontab -l | grep ntpdate &>/dev/null; then
(
echo "* 1 * * * ntpdate time.windows.com >/dev/null 2>&1"
crontab -l
) | crontab
fi

#禁用 SELinux 防火墙
sed -i '/SELINUX/{s/permissive/disabled/}' /etc/selinux/config

#关闭各版本防火墙
if egrep "7.[0-9]" /etc/redhat-release &>/dev/null; then
systemctl stop firewalld
systemctl disable firewalld
elif egrep "6.[0-9]" /etc/redhat-release &>/dev/null; then
service iptables stop
chkconfig iptables off
fi

#历史命令显示操作时间
if ! grep HISTTIMEFORMAT /etc/bashrc; then
echo 'export HISTTIMEFORMAT="%F %T `whoami` "' >>/etc/bashrc
fi

#SSH 的超时时间
if ! grep "TMOUT=600" /etc/profile &>/dev/null; then
echo "export TMOUT=600" >>/etc/profile
fi

#禁止 root 的远程登录
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

#禁止定时任务发送邮件
sed -i 's/^MAILTO=root/MAILTO=""/' /etc/crontab

#设置最大打开的文件数
if ! grep "* soft nofile 65535" /etc/security/limits.conf &>/dev/null; then
cat >>/etc/security/limits.conf <<EOF
* soft nofile 65535
* hard nofile 65535
EOF
fi

#系统内核优化
cat >>/etc/sysctl.conf <<EOF
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tw_buckets = 20480
net.ipv4.tcp_max_syn_backlog = 20480
net.core.netdev_max_backlog = 262144
net.ipv4.tcp_fin_timeout = 20
EOF

#减少 Swap 的使用
echo "0" >/proc/sys/vm/swappiness

#安装系统性能分析工具
yum install gcc make autoconf vim sysstat net-tools iostat if

监控 100 台服务器磁盘利用率脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
HOST_INFO=host.info
for IP in $(awk '/^[^#]/{print $1}' $HOST_INFO); do
USER=$(awk -v ip=$IP 'ip==$1{print $2}' $HOST_INFO)
PORT=$(awk -v ip=$IP 'ip==$1{print $3}' $HOST_INFO)
TMP_FILE=/tmp/disk.tmp
ssh -p $PORT $USER@$IP 'df -h' >$TMP_FILE
USE_RATE_LIST=$(awk 'BEGIN{OFS="="}/^\/dev/{print $NF,int($5)}' $TMP_FILE)
for USE_RATE in $USE_RATE_LIST; do
PART_NAME=${USE_RATE%=*}
USE_RATE=${USE_RATE#*=}
if [ $USE_RATE -ge 80 ]; then
echo "Warning: $PART_NAME Partition usage $USE_RATE%!"
fi
done
done

MySQL 数据库备份脚本参考分享

2024年1月25日 00:00

在之前的文件中杜老师推荐了两款云主机面板,有小伙伴留言说因服务器配置比较低,未使用云主机面板,一直为数据库备份问题困扰。杜老师之前工作的环境也是没有面板,编写过相关的备份脚本,在此文中分享给需要的小伙伴们~

设置环境变量

1
2
3
4
5
6
#!/bin/bash
# Setup environment variables
export MYSQL_HOST=localhost
export MYSQL_PORT=3306
export MYSQL_USER=root
export MYSQL_PASSWORD=your_password

注意:需要设置环境变量,以便脚本可以找到 MySQL 数据库。在脚本中,添加以上内容。

创建备份目录

1
2
3
4
5
6
# Create backup directory
BACKUP_DIR=/path/to/backup/directory
# Check if backup directory exists
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
fi

注意:创建一个目录,以存储备份的文件。在脚本中,添加以上内容。

备份 MySQL 数据库

1
2
3
4
# Backup database
DATABASE_NAME=your_database_name
BACKUP_FILE="$BACKUP_DIR/$DATABASE_NAME-$(date +"%Y-%m-%d_%H-%M-%S").sql"
mysqldump -u $MYSQL_USER -p$MYSQL_PASSWORD --host=$MYSQL_HOST --port=$MYSQL_PORT $DATABASE_NAME > "$BACKUP_FILE"

注意:使用 mysqldump 命令备份数据库。在脚本中,添加以上内容。

检查备份是否成功

1
2
3
4
5
6
7
# Check if backup file exists
if [ -f "$BACKUP_FILE" ]; then
echo "Backup completed successfully!"
else
echo "Backup failed!"
exit 1
fi

注意:检查备份文件是否创建。在脚本中,添加以上内容。

压缩备份文件

1
2
3
4
5
# Compress backup file
COMPRESSED_BACKUP_FILE="$BACKUP_FILE".gz
gzip "$BACKUP_FILE"
# Remove original backup file
rm "$BACKUP_FILE"

注意:为了节省空间,可使用 gzip 命令压缩备份文件。在脚本中,添加以上内容。

删除 MySQL 旧备份

1
2
3
4
5
# Remove old backups
OLD_BACKUPS=$(ls -t "$BACKUP_DIR" | tail -n +7)
if [ ! -z "$OLD_BACKUPS" ]; then
rm "$BACKUP_DIR/$OLD_BACKUPS"
fi

注意:删除旧备份文件以节省空间。在脚本中,添加以上内容。

设置定时任务

最后,可使用 crontab 命令设置定时任务,以便脚本定期运行。在终端中输入以下命令:

1
crontab -e

在打开的编辑器中,添加以下行的内容,比如以每天凌晨的一点运行脚本。保存并退出编辑器:

1
0 1 * * * /path/to/your/backup_script.sh

还有哪些开源工具

除上面脚本外,还有一些开源「有商业版」工具也可实现 MySQL 数据库备份,供需要的小伙伴们参考选择:

名称描述
mysqldumpmysqldump 是 MySQL 官方提供的备份工具,可将 MySQL 数据库的数据和结构导出为 SQL 文件。
XtraBackupXtraBackup 是一个 Percona 提供的备份工具,可在线备份 MySQL 数据,提供增量备份、压缩功能。
Zmanda Recovery ManagerZmanda Recovery Manager 是一款商业的备份和恢复解决方案,可支持 MySQL 和其它数据库。提供完整备份、增量备份、多种存储介质、自动化备份和恢复等功能。
mydumpermydumper 是一个 MySQL 官方派生的高性能 MySQL 备份工具,支持并行备份,可更快地备份大型 MySQL 数据库。

如何删除 GitHub 的提交历史记录

2022年9月17日 00:00

有时候不经意把一些敏感的信息写到了代码里,并提交到 GitHub 上,代码公开时被人发现是很危险的事情,这时候就需要将之前的提交记录进行删除。

需求背景

细心的小伙伴会发现本博最后活动时间永远在 24 小时内,因为杜老师经常会调整博客,包括配置、内容等等。频繁更新消耗了大量 GitHub Actions 部署配额,后经香猪提示,将库公开则不会再消耗部署配额:

公开库的第一时间,就受到了凉心云的警告信息,其在之前的提交中扫描到高权限密钥。为了避免数据泄露,杜老师需要删除 GitHub 提交历史记录:

操作指令

1
2
3
4
5
6
git checkout --orphan master # 在非新存储库上以类似 git init 的状态创建分支
git add -A # 提交所有文件到数据暂存区
git commit -m a # 提交修改
git branch -D main # 删除分支
git branch -m main # 将当前分支重命名
git push -f origin main # 强制提交当前分支

注意:数据宝贵,删除前需做好备份!

Go 语言实现 2048 游戏

2021年5月13日 00:00

相信大家都玩过 2048 这个游戏,这次我们将使用 Go 语言及调用相关包来完成一个简易版的 2048 游戏,快来一同尝试下吧!

执行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
package main

import (
"fmt"
"github.com/shiyanlou/termbox-go"
"math/rand"
"time"
)

var Score int
var step int

func coverPrintStr(x, y int, str string, fg, bg termbox.Attribute) error {

xx := x
for n, c := range str {
if c == '\n' {
y++
xx = x - n - 1
}
termbox.SetCell(xx+n, y, c, fg, bg)
}
termbox.Flush()
return nil
}

type Status uint

const (
Win Status = iota
Lose
Add
Max = 2048
)

type G2048 [4][4]int

func (t *G2048) checkWinOrAdd() Status {
for _, x := range t {
for _, y := range x {
if y >= Max {
return Win
}
}
}
i := rand.Intn(len(t))
j := rand.Intn(len(t))
for x := 0; x < len(t); x++ {
for y := 0; y < len(t); y++ {
if t[i%len(t)][j%len(t)] == 0 {
t[i%len(t)][j%len(t)] = 2 << (rand.Uint32() % 2)
return Add
}
j++
}
i++
}

return Lose
}

func (t G2048) initialize(ox, oy int) error {
fg := termbox.ColorYellow
bg := termbox.ColorBlack
termbox.Clear(fg, bg)
str := " SCORE: " + fmt.Sprint(Score)
for n, c := range str {
termbox.SetCell(ox+n, oy-1, c, fg, bg)
}
str = "ESC:exit " + "Enter:replay"
for n, c := range str {
termbox.SetCell(ox+n, oy-2, c, fg, bg)
}
str = " PLAY with ARROW KEY"
for n, c := range str {
termbox.SetCell(ox+n, oy-3, c, fg, bg)
}
fg = termbox.ColorBlack
bg = termbox.ColorGreen
for i := 0; i <= len(t); i++ {
for x := 0; x < 5*len(t); x++ {
termbox.SetCell(ox+x, oy+i*2, '-', fg, bg)
}
for x := 0; x <= 2*len(t); x++ {
if x%2 == 0 {
termbox.SetCell(ox+i*5, oy+x, '+', fg, bg)
} else {
termbox.SetCell(ox+i*5, oy+x, '|', fg, bg)
}
}
}
fg = termbox.ColorYellow
bg = termbox.ColorBlack
for i := range t {
for j := range t[i] {
if t[i][j] > 0 {
str := fmt.Sprint(t[i][j])
for n, char := range str {
termbox.SetCell(ox+j*5+1+n, oy+i*2+1, char, fg, bg)
}
}
}
}
return termbox.Flush()
}

func (t *G2048) mirrorV() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[len(t)-i-1][j] = num
}
}
*t = *tn
}

func (t *G2048) right90() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[j][len(t)-i-1] = num
}
}
*t = *tn
}

func (t *G2048) left90() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-j-1][i] = num
}
}
*t = *tn
}

func (t *G2048) right180() {
tn := new(G2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-i-1][len(line)-j-1] = num
}
}
*t = *tn
}

func (t *G2048) mergeUp() bool {
tl := len(t)
changed := false
notfull := false
for i := 0; i < tl; i++ {

np := tl
n := 0

for x := 0; x < np; x++ {
if t[x][i] != 0 {
t[n][i] = t[x][i]
if n != x {
changed = true
}
n++
}
}
if n < tl {
notfull = true
}
np = n
for x := 0; x < np-1; x++ {
if t[x][i] == t[x+1][i] {
t[x][i] *= 2
t[x+1][i] = 0
Score += t[x][i] * step
x++
changed = true
}
}
n = 0
for x := 0; x < np; x++ {
if t[x][i] != 0 {
t[n][i] = t[x][i]
n++
}
}
for x := n; x < tl; x++ {
t[x][i] = 0
}
}
return changed || !notfull
}

func (t *G2048) mergeDwon() bool {
//t.mirrorV()
t.right180()
changed := t.mergeUp()
//t.mirrorV()
t.right180()
return changed
}

func (t *G2048) mergeLeft() bool {
t.right90()
changed := t.mergeUp()
t.left90()
return changed
}

func (t *G2048) mergeRight() bool {
t.left90()
changed := t.mergeUp()
t.right90()
return changed
}

func (t *G2048) mrgeAndReturnKey() termbox.Key {
var changed bool
Lable:
changed = false
//ev := termbox.PollEvent()
event_queue := make(chan termbox.Event)
go func() {
for {
event_queue <- termbox.PollEvent()
}
}()

ev := <-event_queue

switch ev.Type {
case termbox.EventKey:
switch ev.Key {
case termbox.KeyArrowUp:
changed = t.mergeUp()
case termbox.KeyArrowDown:
changed = t.mergeDwon()
case termbox.KeyArrowLeft:
changed = t.mergeLeft()
case termbox.KeyArrowRight:
changed = t.mergeRight()
case termbox.KeyEsc, termbox.KeyEnter:
changed = true
default:
changed = false
}

if !changed {
goto Lable
}

case termbox.EventResize:
x, y := termbox.Size()
t.initialize(x/2-10, y/2-4)
goto Lable
case termbox.EventError:
panic(ev.Err)
}
step++
return ev.Key
}

func (b *G2048) clear() {
next := new(G2048)
Score = 0
step = 0
*b = *next

}

func (b *G2048) Run() {
err := termbox.Init()
if err != nil {
panic(err)
}
defer termbox.Close()

rand.Seed(time.Now().UnixNano())

A:

b.clear()
for {
st := b.checkWinOrAdd()
x, y := termbox.Size()
b.initialize(x/2-10, y/2-4)
switch st {
case Win:
str := "Win!!"
strl := len(str)
coverPrintStr(x/2-strl/2, y/2, str, termbox.ColorMagenta, termbox.ColorYellow)
case Lose:
str := "Lose!!"
strl := len(str)
coverPrintStr(x/2-strl/2, y/2, str, termbox.ColorBlack, termbox.ColorRed)
case Add:
default:
fmt.Print("Err")
}
key := b.mrgeAndReturnKey()
if key == termbox.KeyEsc {
return
}
if key == termbox.KeyEnter {
goto A
}
}
}

func main() {
var game G2048
game.Run()
}

注意:创建源文件 2048.go,输入以上内容。

执行效果

运行代码 go run 2048.go 可启动游戏:

Go 语言实现 2048 游戏「中篇」

2021年5月10日 00:00

矩阵旋转操作是为了将其它三个方向的移动都转换为向上的移动操作。向下、向左、向右转换为向上操作时,数组需要进行翻转操作参考正文代码。

执行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package main

import "fmt"

type g2048 [4][4]int

func (t *g2048) MirrorV() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[len(t)-i-1][j] = num
}
}
*t = *tn
}

func (t *g2048) Right90() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[j][len(t)-i-1] = num
}
}
*t = *tn
}

func (t *g2048) Left90() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-j-1][i] = num
}
}
*t = *tn
}

func (g *g2048) R90() {
tn := new(g2048)
for x, line := range g {
for y, _ := range line {
tn[x][y] = g[len(line)-1-y][x]
}
}
*g = *tn

}

func (t *g2048) Right180() {
tn := new(g2048)
for i, line := range t {
for j, num := range line {
tn[len(line)-i-1][len(line)-j-1] = num
}
}
*t = *tn
}

func (t *g2048) Print() {
for _, line := range t {
for _, number := range line {
fmt.Printf("%2d ", number)
}
fmt.Println()
}
fmt.Println()
tn := g2048{{1, 2, 3, 4}, {5, 8}, {9, 10, 11}, {13, 14, 16}}
*t = tn

}

func main() {
fmt.Println("origin")
t := g2048{{1, 2, 3, 4}, {5, 8}, {9, 10, 11}, {13, 14, 16}}
t.Print()
fmt.Println("mirror")
t.MirrorV()
t.Print()
fmt.Println("Left90")
t.Left90()
t.Print()
fmt.Println("Right90")
t.R90()
t.Print()
fmt.Println("Right180")
t.Right180()
t.Print()
}

注意:创建源文件 martix_rorate.go,输入以上代码。

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
origin
1 2 3 4
5 8 0 0
9 10 11 0
13 14 16 0

mirror
13 14 16 0
9 10 11 0
5 8 0 0
1 2 3 4

Left90
4 0 0 0
3 0 11 16
2 8 10 14
1 5 9 13

Right90
13 9 5 1
14 10 8 2
16 11 0 3
0 0 0 4

Right180
0 16 14 13
0 11 10 9
0 0 8 5
4 3 2 1

注意:执行 go run martix_rorate.go 后,输出如上。

使用 Termbox 绘制数据流

2021年5月7日 00:00

Termbox 提供一个最小化的 API,允许程序员编写基于文本的用户界面。在 Linux 操作系统有基于终端的实现,基本思想是对所有主要终端和其他类似终端的 API 上的最大的通用功能子集进行抽象,以最小的方式进行。小的 API 意味着它很容易实现、测试、维护、学习。

重要函数

下面我们简单介绍下比较重要的函数:

函数介绍
termbox.Size()获取 Console 的尺寸
termbox.SetCell(x, y, ch, fg, bg)用于设置字符单元属性,其中 x 表示所在行,y 表示所在列,ch 是要设置的字符,fg 和 bg 分布表示前景色和背景色
termbox.Flush()同步后台缓存。Flush 方法一般用于将后台的处理输出到界面中。如重新绘制一个界面
termbox.Init()在使用 Termbox 进行程序开发时候,我们需要先使用 termbox.Init 方法来初始化
termbox.Close()当不再使用 Termbox 任何功能时候,使用 termbox.Close 来关闭对 termbox 引入
termbox.PollEvent()用于等待键盘事件的触发并返回事件,无事件发生时则会无限等待

绘制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import "github.com/nsf/termbox-go"
import "math/rand"
import "time"

func draw() {
w, h := termbox.Size()
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
termbox.SetCell(x, y, ' ', termbox.ColorDefault,
termbox.Attribute(rand.Int()%8)+1)
}
}
termbox.Flush()
}

func main() {
err := termbox.Init()
if err != nil {
panic(err)
}
defer termbox.Close()

event_queue := make(chan termbox.Event)
go func() {
for {
event_queue <- termbox.PollEvent()
}
}()

draw()
for {
select {
case ev := <-event_queue:
if ev.Type == termbox.EventKey && ev.Key == termbox.KeyEsc {
return
}
default:
draw()
time.Sleep(10 * time.Millisecond)
}
}
}

注意:创建源文件 random_output.go,输入以上代码。

执行代码

1
go run random_output.go

注意:执行以上代码,就可以在终端中看到五彩缤纷的数据流啦,如果想退出程序需按下ESC键。

运行效果

效果如下:

Java 的 SE/EE/ME 区别知道吗

2020年11月2日 00:00

许多零基础 Java 开发者刚参加 Java 培训时并不知道 JavaSE/JavaEE/JavaME 三者之间的区别,那我们究竟该学习 JavaSE 还是 JavaEE,还是 JavaME 呢?笔者将以通俗易懂的方式给大家讲解这三者之间的区别。

三大版本

JavaSE 即 Java 标准版,它是 JavaEE 和 JavaME 的基础,之前也称为 J2SE,用来开发 C/S 架构的软件,通俗来讲,主要用于开发、部署桌面、服务器以及嵌入设备和实时环境中的应用程序。例如,Java 应用程序开发平台 Eclipse。

JavaEE 企业版,之前被称为 J2EE,JavaEE 是在 JavaSE 基础上构建的,用来开发 B/S 架构的软件,主要针对企业应用开发。例如,电子商务网站、ERP 系统等。

JavaME 微型版,也是以 Java 为基础的,之前被称为 J2ME,它是一套运行专门为嵌入式设备设计的 API 接口规范,主要用于开发移动设备软件和嵌入式设备软件,主要针对消费类电子设备的。例如,手机、电视的机顶盒、汽车导航系统等等。

简单来说,JavaSE 是 Java 的基础,主要针对桌面程序开发;JavaEE 是针对企业应用开发;而 JavaME 是主要针对嵌入式设备软件开发。

JavaEE 企业版

多说一些 JavaEE 企业版相关。

JavaEE 在 JavaSE 的基础进行扩展,增加了一些更加便捷的应用框架。如我们现在常用的 Java 开发三大框架 Spring/Struts 和 Hibernate,我们可以应用这些框架轻松写出企业级的应用软件。

JavaEE 也可以说是一个框架也是一种规范,说它是框架是因为它包含了很多我们开发时用到的组件,例如:Servlet/EJB/JSP/JSTL 等。说它是规范是因为我们开发 Web 应用常会用到的一些规范模式,JavaEE 提供很多规范的接口却不实现,将这些接口的具体实现细节转移到厂商的身上,这样各家厂商推出的 JavaEE 产品虽然名称实现不同,但展现给外部使用的却是统一规范的接口。

例如,我们编写的 JSP 代码,由于大量的显示代码和业务逻辑混淆一起,彼此嵌套,不利于程序维护和扩展。当业务需求发生变化的时候,对于程序员和美工是一个很重的负担。为了程序的易维护性和可扩展性,这就需要我们使用 JavaEE 技术来进行项目开发。

如何用 Python 表白

2020年3月19日 00:00

快到情人节了,作为技术宅男,杜老师教大家如何通过 Python 给女神表白。其实 Python 可以做很多事情,通过其强大的库能实现各种效果,今天杜老师准备了一段很简单的代码,感兴趣的小伙伴可以试一下!

表白代码

1
2
3
4
5
import time
words = input('Please input the words you want to say!:')
for item in words.split():
print('\n'.join([''.join([(item[(x-y) % len(item)] if ((x*0.05)**2+(y*0.1)**2-1)**3-(x*0.05)**2*(y*0.1)**3 <= 0 else ' ') for x in range(-30, 30)]) for y in range(12, -12, -1)]))
time.sleep(1.5);

注意:不需要安装任何库;仅支持英文字母的输入;单词间需要加空格;单词越多,持续效果越久!

运行效果

点击播放效果:

asciicast

用Matlab画一个南瓜/Pumpkin

2024年10月26日 06:24

一年一度的万圣节要到了,正好这天在Linkedin上看到Mathworks发的帖子,用Matlab画一个南瓜,很是有趣。

matlab-pumpkin 用Matlab画一个南瓜/Pumpkin Matlab 学习笔记 程序设计 编程 节日 计算机 计算机

用Matlab画一个南瓜/Pumpkin

% Pumpkin
[X,Y,Z]=sphere(200);
R=1-(1-mod(0:.1:20,2)).^2/12;
x=R.*X; y=R.*Y; z=Z.*R;
c=hypot(hypot(x,y),z)+randn(201)*.03;
surf(x,y,(.8+(0-(1:-.01:-1)'.^4)*.3).*z,c, 'FaceColor', 'interp', 'EdgeColor', 'none')
% Stem
s = [ 1.5 1 repelem(.7, 6) ] .* [ repmat([.1 .06],1,10) .1 ]';
[t, p] = meshgrid(0:pi/15:pi/2,0:pi/20:pi);
Xs = -(.4-cos(p).*s).*cos(t)+.4;
Zs = (.5-cos(p).*s).*sin(t) + .55;
Ys = -sin(p).*s;
surface(Xs,Ys,Zs,[],'FaceColor', '#008000','EdgeColor','none');
% Style
colormap([1 .4 .1; 1 1 .7])
axis equal
box on
material([.6 1 .3])
lighting g
camlight

英文:Draw a Pumpkin using Matlab

Mathwork Matlab

英国万圣节/Halloween

本文一共 53 个汉字, 你数一下对不对.
用Matlab画一个南瓜/Pumpkin. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 用Matlab画一个南瓜/Pumpkin Matlab 学习笔记 程序设计 编程 节日 计算机 计算机
The post 用Matlab画一个南瓜/Pumpkin first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. SteemIt 高级定制微信文章列表 RSS/API/阅读器 v2.0 The Advanced Wechat Group Posts Feed/API/Reader v2.0 Abstract: I have added five parameters to the...
  4. 域名续费 – 强迫症 我有注册了好几个域名,每次续费我都会申请最多年,也就是续个 10 年 ( .COM 域名一般一次可以注册 10 年) 其实这是很不好的,很浪费钱,10年注册费也将近 100 镑, 何况我还有6个域名.是笔不小的开销.域名申请久一点,好处无非是自己看着爽;让你的读者知道你写博客有恒心,不会哪一天就半途而废;还有就是有争议的:搜索引擎会不会把域名注册年限当作一个SEO评分的因素呢? 有可能短期注册只有一年的域名会被惩罚.长期的比如五年和十年可能都没有区别. 我在三个域名提供商里都续过.Fasthosts 是做...
  5. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  6. VPS惊魂恢复: 手欠, 差点把这一个月的博客和VPS给丢了 我目前为止有 5 台VPS, 除了上个月在 Digital Ocean 租了一台在法国的新VPS(用于VPS数据库工具) 是选择了 Ubuntu 18.04, 其它4台都是16.04, 最近听说 Ubuntu 16.04 已经...
  7. 在英国带孩子去露营全攻略 之前就做了一些露营的准备工作, 因为大儿子Eric 很兴奋说是要去 Camping Holiday 估计是在 Papa Pig 里看到的. 英国有很多可以露营的地方, 最后面选了一个离家开车1个多小时. 看了评论还不错. 地址为: New Road,...
  8. LOGO 海龟作画 系列三 递归画一个国际象棋棋盘 今天我们要来讲一讲递归. 递归就是函数自己调用自己, 我们可以定义一个过程, 然后这只海龟不停的画, 结束的时候再调用自身再继续画. 再次调用的时候参数变化了, 至到参数满足一定的条件则停止. 比如 下面定义的这个过程可以用来画一个实现的正方形. TO FK :B IF :B>15 ;...

望明月

2024年10月25日 22:57

无意间看到一个干支纪年和节气相关的机器人账号,心里就想:这些信息或许在文化传承上有些价值,但对现代人还有什么其他意义呢?除了有些人会根据节气调整作息来养生之外,我真的想不出什么理由让人多记一份历法。

再去深究时发现,历法背后其实是天体运算,试着搞明白月相计算的时候,我想到个点子:emoji 图标里不是也有月相吗,可以计算出当前月相然后输出一个相应的 emoji,再花一些时间甚至可以用几个 emoji( 🌞 🌍 🌓 )把太阳、地球、月球的实时位置关系粗略地呈现出来。

太阳地球月球的位置关系示意图

嗯,我写这些的时候是想把那份兴高采烈传达给妳的,但是写完也意识到了,的确挺无聊。不过,通过计算能知道曾经「举杯邀明月,对影成三人」的李白,出生的那天月亮 40.8% 的区域被照亮,月相是娥眉月 🌒 ……对我来说还是很能产生些遐想的。千百年过去了,月球改变了多少呢?现代世界到处灯火通明,很难见到月光照出的自己的影子了,所以看到影子,我们第一个想到的也不会是月亮;对古时的人们来说,月亮多有存在感啊!是不是现代人的心性难得空明也就是因为早已忘了天空中的月亮呢?

fin.

C/C++ 中的内存管理器(堆与栈)

2024年10月3日 04:48

最近面试的时候遇到这个问题。这个问题考你计算机的基本功。

在 C/C++ 中,内存管理是控制程序如何分配和管理其资源的关键方面。C/C++ 程序中的内存通常分为不同的区域:堆栈和堆是最主要的动态和自动内存分配区域。

ACM题解系列之 – 最小堆栈 (Min Stack)

stack C/C++ 中的内存管理器(堆与栈) 学习笔记 技术 程序员 程序设计 编程 计算机 软件工程 面试

Stack 栈

堆栈内存

  • 定义:堆栈内存用于静态(自动)内存分配。它是存储函数参数、本地变量和返回地址的地方。当调用一个函数时,一个新的内存块(称为堆栈帧)会被添加到堆栈的顶部。当函数返回时,该内存会被自动释放。
  • 分配:内存由系统自动管理——在变量超出作用域时自动分配和释放。无需人工干预。
  • 生命周期:受限于函数或代码块的作用域。一旦函数退出,内存将被释放。
  • 大小限制:堆栈的大小通常较小并由系统预定义,意味着大的分配可能导致堆栈溢出。
  • 访问速度:由于其后进先出(LIFO)的结构,堆栈内存访问速度更快。由于内存是连续的且可预测的,它允许快速访问。
  • 使用场景:局部变量、函数调用信息和固定大小的对象(数组、结构体)。

堆内存

  • 定义:堆内存用于动态内存分配,程序员使用 C 中的 malloc()、calloc()、free() 和 C++ 中的 new、delete 手动分配和释放内存。
  • 分配:内存在运行时分配,并且分配的生命周期由程序员手动控制。它可以持续存在,直到显式释放。
  • 生命周期:堆分配的对象的生命周期不受作用域的限制。内存将一直被使用,直到被释放为止。
  • 大小限制:堆通常比堆栈大,但取决于系统资源。不当处理可能导致内存泄漏(忘记释放分配的内存)或碎片化(内存使用效率低)。
  • 访问速度:堆内存的访问速度比堆栈慢,因为分配是分散的,动态分配涉及更多的开销。
  • 使用场景:如链表、等大数据结构,或在运行时确定大小的对象。

堆与栈的主要区别

特征 堆栈
内存大小 通常较小,预定义 通常较大,受系统资源限制
分配 自动,由编译器管理 手动,由程序员管理(使用 new、malloc 等)
释放 自动(函数退出时) 手动(使用 delete、free 等)
生命周期 限于函数/代码块作用域 可以持续,直到显式释放
速度 较快(连续内存) 较慢(分散内存,开销更大)
风险 堆栈溢出(如果超出大小限制) 内存泄漏和碎片化

堆栈分配示例

void function() {
    int x = 10; // 分配在堆栈上
} // x 会自动释放

堆分配示例

void function() {
    int* p = new int; // 分配在堆上
    *p = 10;
    delete p; // 必须手动释放
}

正确管理堆内存在 C/C++ 中非常重要,因为它可能导致与内存相关的错误,如内存泄漏或重复释放。理解堆和堆栈内存之间的差异有助于优化程序的性能和可靠性。

英文:The Memory Manager in C/C++ (Heap vs Stack)

面试经历

面试题

面试技巧

面试其它

本文一共 874 个汉字, 你数一下对不对.
C/C++ 中的内存管理器(堆与栈). (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c C/C++ 中的内存管理器(堆与栈) 学习笔记 技术 程序员 程序设计 编程 计算机 软件工程 面试
The post C/C++ 中的内存管理器(堆与栈) first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. Javascript 中 sleep 函数实现 Javascript 中并没有 built-in 的 sleep 函数支持, 在 async/await/Promise 的支持之前, 我们可以用 busy-waiting 的方式来模拟: 1 2 3...
  2. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  3. 《Steem 指南》之 justyy 在线工具与 API 系列 – 同时给多个帐号发送SBD或者STEEM 同时给多个帐号发送SBD或者STEEM STEEMIT 和 BUSY 的前端都有一个内置的钱包工具, 您可以一次给一个帐号发送 SBD 或者 STEEM. 当我们要给很多很多人发送钱的时候, 就显得有些不方便了. 这时候可以用这个在线工具: https://steemyy.com/wallet-tool/ 填写表单 只需要填上你的ID,...
  4. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  5. 拔牙后的注意事项(图, 慎入) Care of Mouth after Extraction 昨天又拔了两颗牙, 初步定在5月4号装牙套. 这是牙医诊所给的术后注意事项: 拔完后需要等3-4小时麻醉失效后才能吃喝. 稍微流点血是很正常的. 但是请不要漱口吐出, 因为这会加速流血. 你只要轻轻的含着口水并咽下即可. 如果一直流血, 请拿着纱布(并不是纸巾)放在拔牙处20分钟. 24小时内请不要运动, 术后几小时内回家静静坐着. 12小时内不要吸烟, 喝酒或者喝热饮, 因为这会让伤口流血....
  6. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  7. 最简单有效的过滤WordPress垃圾评论的方法 当你的Wordpress博客流量大的时候, 不免会收到很多垃圾评论. 本文介绍一种特别简单而且免费的过滤Wordpress垃圾评论的方法. 这种方法不需要你安装任何插件, 也不需要拥有修改Wordpress主题模板函数的能力, 只需要1分钟就可以搞定. 把这个列表拷贝下来 打开 WordPress 的控制面版, 到设置-讨论 拷贝上面的列表到 “评论审核” 或者 “评论黑名单”...
  8. 更改全站的评论名称 坛子给我建议说: 我觉得很有道理,但是别人网站上的留言我改不了, 自己的还是可以先改改的. 于是,我登陆 phpmyadmin (一个网页式的php mysql 管理平台) 然后输入以下命令: update `wp_comments` set `wp_comment_author` = 'JustYY.com...

信奥风云

2024年9月29日 11:32
21日考试当天,很多家长接送孩子去考试,纷纷表示,2024年考试人数明显增加了好多。2023年广东参加J1的人数大约1.3万左右,听说今年增加到2万左右,事实也确实如此,东莞考点东莞中学松山湖学校周围交通当天上午直接瘫痪,周边道路都被车堵了,我想这也是为什么前几年考场设在市区而从去年开始就改到市区外的原因吧。

Excel 教程: SUMIF函数

2024年9月24日 01:45

SUMIF函数在Excel中用于对满足特定条件的范围内的值进行求和。以下是基本语法:

=SUMIF(range, criteria, [sum_range])

range: 要应用条件的单元格范围。
criteria: 必须满足的条件。可以是数字、表达式、单元格引用或文本。
sum_range (可选): 如果与range不同,指定要求和的单元格范围。如果不提供sum_range,Excel将对range中的值进行求和。

示例:
你有一个销售列表,想要只对销售额大于100的进行求和。

A	B
商品	销售额
苹果	150
香蕉	80
橙子	200

你可以使用以下公式对大于100的销售额进行求和:

=SUMIF(B2:B4, ">100")

这将返回350(150 + 200)。

使用sum_range:
如果条件在一列,而要求和的值在另一列,例如:

A	B	C
商品	销售额	价格
苹果	150	2.50
香蕉	80	1.00
橙子	200	3.00

你想对销售额大于100的价格进行求和,可以使用以下公式:

=SUMIF(B2:B4, ">100", C2:C4)

这将对苹果和橙子的价格求和(2.50 + 3.00 = 5.50)。

英文:Excel Tutorial: SUMIF Function

Excel教程

本文一共 240 个汉字, 你数一下对不对.
Excel 教程: SUMIF函数. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c Excel 教程: SUMIF函数 Excel 表格 学习笔记
The post Excel 教程: SUMIF函数 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 深度体验: OneKey虚拟货币出金卡(美元黑卡) 出金/变现的几种方法 出金:也叫Cash out/变现,一般把虚拟货币(如比特币BTC或以太坊ETH)变成法币的方式就叫出金。一般有几种方法: P2P:也叫线下,最直白的方式就是私下一手交钱/法币,一手交币。大型交易所都会有一个P2P的交易,比如币安和HTX火币都有。之前localbitcoin也是这种方式,可惜在2023年倒闭了。我曾经在微信上卖了几十个STEEM,当时是几美元一个的时候。一手交人民币,一手交STEEM币。这种P2P私下的方式不受监管,但是要互相信任。可以当面交易这样减少风险:见个面喝个茶,就把交易做成了。 变成法币:之前我用过Coinbase直接卖成英镑,然后通过发到Paypal再提现到英国银行帐号上变成实实在在在的英镑,不过这一趟下来,手续费不低,就当学费了。 直接花掉:我个人比较喜欢这种方式,有几种Crypto Visa/Master银行卡,可以把虚拟货币卖成法币然后购物花掉。大部分是需要有一个卖币成法币的过程,也有少部分是实时转换虚拟货币成法币,当然基本上是稳定币:USDC, USDT泰达币等。 在英国,想把虚拟货币出金,可以用几种选择: Wirex:支持波场U,支持各种Defi产品,比如定期30天存USDT可以达16%年利率,世界好多国家都支持Wirex卡,上次去塞尔维亚就刷了一次,不过发现汇率并不划算(有5%-10%的差别)。Wirex提现费用较高,不过转换成法币汇率较好。Wirex在乌克兰有个开发办公室。 Crypto.com:这家总部好像在香港,也是不错的,去年的时候它家的DEFI利率挺高,但后来越来越少,直接分成三档/Tier,有次无意和Wirex比较,发现它家USDT转英镑的利率比Wirex低多了,于是不怎么用了。Crypto.com也是需要先把币变成法币。 Crypto Ledger:这是家做硬件钱包的,最近一两年搞了这个产品,它家是直接刷稳定币,也就是消费的时候再兑换虚拟币成法币,有一个2%的费用,不过选择它家平台代币BXX就可以拿回这2%的返现/cashback,相当于不花钱。选择USDT或者BTC返现只有1%。它家的卡是支持加入Apple Pay的,所以可以用在线下支持,日常买菜吃饭都可以出金,很是方便。 OneKey:本文接下来要讲的。...
  2. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  3. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  4. 面经: Python 的 List 和 Dictionary 有啥区别? 问题: Python 的 List 和 Dictionary 有啥区别? 不许查资料, 你怎么回答这个面试题? 我不加思索的回答到: List 就像数组一样 而 Dictionary 是...
  5. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  6. 比较好的SQL插入语法 SQL语法中用INSERT来向一个表格里插入数据, 比如一个表 table 有四列 A, B, C 和D 你就可以使用: insert into table (`A`, `B`, `C`,...
  7. 人到中年 去年老婆生孩子,把一个月的假期都用在照顾老婆做月子,所以就没有时间回国.今年7月14号回了趟国,到9月9号才回英国, 有两个月在中国.感觉发生了很多事情.特别是刚回到英国的时候,感觉很陌生又熟悉. 先简单总结一下我这次的行程吧,很多事情,有很多朋友却无法一一相见,请见谅,因为孩子还太小,有些时候没法很洒脱的去见朋友. 7-15 伦敦->北京 英国出发的时间是 14号, 到了北京是15号(时差) 从 Sheffield 出发, 先是打的到火车站(其实也就5分钟,但是行李很多),坐上从 Sheffield 到 London...
  8. 一年的信用卡消费 换来 180英镑点卷 去年11月份申请了 这张AMEX信用卡, 一黑一白, 黑的是America Express, 白的是VISA卡. 并不是所有的商店都支持 America Express, 整体来说 VISA支持的更多 几乎英国刷卡的地方都支持VISA卡. 刷黑卡一镑钱能得2点积分, 刷白卡能得1点积分. 养家不容易啊,...

45 分钟模拟面试(编程、系统设计)+职业发展建议

2024年9月23日 05:41

许多大型科技公司(如 FANG:Facebook/Meta、Apple、Netflix、Google)以及微软等,在发出工作邀请之前,通常会进行多轮面试。这些面试通常包括编程/Coding、系统设计/System Design和行为评估/Behaviour,以考察文化契合度。

我提供 45 分钟的模拟面试,帮助您准备编码和系统设计环节。作为曾在亚马逊(AWS,S3 Object Lambda)担任面试官的我,将通过真实的练习环节为您提供指导。在编码模拟面试中,您将解决一到两个编程问题;而在系统设计模拟面试中,您需要在白板上设计一个可扩展的产品。

此外,我还提供 45 分钟的聊天时间,可以讨论任何话题,包括职业发展和建议。

我目前定价 60 英镑一小时,会员价是 55 英镑,毕竟时间就是金钱。而这也大概和我的时薪差不多。

如果您有兴趣,请点击此处

mocked-interviews 45 分钟模拟面试(编程、系统设计)+职业发展建议 微软 程序员 系统设计 资讯 面试

本人提供模拟面试(编程/系统设计)或/和 职业规划建议

PS:我发现 Buy Me a Coffee 这个创业点子很好,赞赏+集成了网上商店,很是方便,界面也很友好,感兴趣的可以通过这个链接来创建一个专属于你的页面,在为用户创作内容的同时也能很快捷的收到赞赏!

英文:45 Minute Mock Interview (Coding, System Design) + Career Development Advices

system-design-interview 45 分钟模拟面试(编程、系统设计)+职业发展建议 微软 程序员 系统设计 资讯 面试

系统设计面试 System Design Interview

面试经历

面试题

面试技巧

面试其它

软件工程师

本文一共 395 个汉字, 你数一下对不对.
45 分钟模拟面试(编程、系统设计)+职业发展建议. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 45 分钟模拟面试(编程、系统设计)+职业发展建议 微软 程序员 系统设计 资讯 面试
The post 45 分钟模拟面试(编程、系统设计)+职业发展建议 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  2. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  3. 英国房子的EPC节能报告(Energe/Efficiency Performance Certificate) EPC (Energe/Efficiency Performance Certificate) 是英国房子的节能报告, 法律上规定, 每个房子都必须要有一个EPC报告, 报告的有效期为十年. 房东在把房子出租或者想卖房的时候, 这个EPC就必须有效, 在一些情况下 比如出租房子的时候, 这个EPC报告还必须符合一些最低标准, 比如房子必须满足 F档(类似及格线)...
  4. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  5. 拔牙后的注意事项(图, 慎入) Care of Mouth after Extraction 昨天又拔了两颗牙, 初步定在5月4号装牙套. 这是牙医诊所给的术后注意事项: 拔完后需要等3-4小时麻醉失效后才能吃喝. 稍微流点血是很正常的. 但是请不要漱口吐出, 因为这会加速流血. 你只要轻轻的含着口水并咽下即可. 如果一直流血, 请拿着纱布(并不是纸巾)放在拔牙处20分钟. 24小时内请不要运动, 术后几小时内回家静静坐着. 12小时内不要吸烟, 喝酒或者喝热饮, 因为这会让伤口流血....
  6. 公司给配了台高配DELL笔记本 早上例会结束的时候我顺便说了一句 我的笔记本有点慢, 当时我并不知道我的经理远程用电话也参加会议了(他全程在听), senior staff SE 对着电话说, “peter, you hear that? btw, my disks are...
  7. 老婆的配偶签证被拒 郁闷死了, 601镑签证费打水漂,一去不回!费钱费力. 去年12月份我请了律师拿到了永居.老婆是T1G签证的陪工签 (DEPENDENT VISA) 2016年4月份到期. 然后我就想说得趁早把她的签证转成配偶签(SPOUSE)这样她就可以尽快走五年永居的路线. 今天收到拒签信,原因是我没有提供 有工资进帐的那份银行帐单,我提供了我和我老婆的联名帐户, 但是工资并不是直接打到这个帐单上的.所以就这一点被拒了.完全不给解释,不给补材料的机会.601镑就这样再见了. 英国的签证寄出之后是先由另一个部门先收费, 收完费才正式审理,而且不管结果如何是不退钱的.后悔没让律师弄,也不至于到现在浪费这么多时间和金钱,签证还没过.由于原签证还没到期,所以还不能上述.估计只能等搬完家后年底请律师搞定这事. 真是郁闷, 600镑, 我可以再买一个IPHONE6,或者给我的新买的车换四个轮胎....
  8. 花钱让人换汽车钥匙的电池真是个智商税 今天想不到我这么聪明的人也被人狠狠的收了一把智商税. 今天被收智商税了, 去 Tesco 换车钥匙的电池. . 才发现如此的简单, 那人直接2分钟搞定2个, 然后收了我25英镑. . 服了. . 我还以为很复杂…… 网友说 “1....

C++的 map 当键(Key)不存在的时候会发生什么?

2024年9月14日 18:27

面试流程(例如筛选)的早期阶段,一位 Google 招聘人员曾向我问过这个问题。

在C++中,当你使用std::map访问一个不存在的键时,行为取决于你是如何访问它的。

使用下标操作符 [] 访问时

如果键不存在,std::map 会默认插入一个该键的元素,并为其赋值为类型的默认值。比如,如果 map 的值类型是 int,那么它会插入该键并赋值为 0。

例子:

std::map<int, int> myMap;
int value = myMap[10]; // 如果键10不存在,会插入myMap[10] = 0

使用 at() 方法访问时

如果键不存在,at() 会抛出 std::out_of_range 异常。

例子:

std::map<int, int> myMap;
try {
    int value = myMap.at(10); // 如果键10不存在,会抛出异常
} catch (const std::out_of_range& e) {
    std::cout << "Key not found!" << std::endl;
}

使用 find() 方法

find() 方法不会修改 map,它返回一个迭代器。如果键不存在,它会返回 map.end()。

例子:

std::map<int, int> myMap;
auto it = myMap.find(10);
if (it == myMap.end()) {
    std::cout << "Key not found!" << std::endl;
} else {
    std::cout << "Value: " << it->second << std::endl;
}

C++ std::map 和 std::unordered_map的比较

std::unordered_map 处理不存在的键与 std::map 类似,但有一些差异,主要是因为它们内部的数据结构不同。

map 和 unordered_map 的区别:

  • 顺序:std::map 是有序的(内部实现为平衡树),所以元素会按键的顺序排列。而 std::unordered_map 是无序的,使用哈希表存储元素,因此没有特定的顺序。
  • 性能:std::unordered_map 通常有更快的平均访问时间(由于哈希结构,平均时间复杂度为 O(1)),而 std::map 的访问时间复杂度为 O(log n),因为其内部实现为树结构。然而,如果发生大量哈希冲突,unordered_map 在最坏情况下的时间复杂度可能是 O(n)。

总的来说,std::unordered_map 和 std::map 在处理不存在的键时,对于 []、at() 和 find() 的行为相似,但它们在顺序和性能方面存在差异。

总结

  • 使用 [] 访问时,如果键不存在,map 会插入一个新元素并赋予默认值。
  • 使用 at() 访问时,如果键不存在,会抛出异常。
  • 使用 find() 可以检查键是否存在,而不会修改 map。

英文:C++: Access a Non-existent Key in std::map or std::unordered_map

面试经历

面试题

面试技巧

面试其它

本文一共 473 个汉字, 你数一下对不对.
C++的 map 当键(Key)不存在的时候会发生什么?. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c C++的 map 当键(Key)不存在的时候会发生什么? ACM题解 学习笔记 小技巧 技术 数据结构与算法 程序设计 编程 资讯 软件工程
The post C++的 map 当键(Key)不存在的时候会发生什么? first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 步步高学生电脑上 Basic 编程语言 peek 用法示例 步步高学生电脑 是8位FC机的经典之作.它上面的BASIC有三个版本 1.0, 2.0 和 2.1 2.1 版本有个在线帮助,实际上是 help.cmd 1.0 是用 Esc 键退回到 DOS 的,...
  2. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  3. Javascript 中 sleep 函数实现 Javascript 中并没有 built-in 的 sleep 函数支持, 在 async/await/Promise 的支持之前, 我们可以用 busy-waiting 的方式来模拟: 1 2 3...
  4. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  5. 《Steem 指南》之 justyy 在线工具与 API 系列 – 同时给多个帐号发送SBD或者STEEM 同时给多个帐号发送SBD或者STEEM STEEMIT 和 BUSY 的前端都有一个内置的钱包工具, 您可以一次给一个帐号发送 SBD 或者 STEEM. 当我们要给很多很多人发送钱的时候, 就显得有些不方便了. 这时候可以用这个在线工具: https://steemyy.com/wallet-tool/ 填写表单 只需要填上你的ID,...
  6. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  7. 试用 Linkedin (领英) 高级帐号 (Premium) Linkedin (领英) 算是比较靠谱的职业社交网站, 在上面有很多猎头, 很多知名公司的HR 无时无刻在招人. 特别领英在被微软收购之后, 名气就变得大了许多. 领英是免费使用的, 但也有付费用户, 有给猎头的, 也有给想找工作的. 价格并不便宜, 对于想找工作的 Job...
  8. 最简单有效的过滤WordPress垃圾评论的方法 当你的Wordpress博客流量大的时候, 不免会收到很多垃圾评论. 本文介绍一种特别简单而且免费的过滤Wordpress垃圾评论的方法. 这种方法不需要你安装任何插件, 也不需要拥有修改Wordpress主题模板函数的能力, 只需要1分钟就可以搞定. 把这个列表拷贝下来 打开 WordPress 的控制面版, 到设置-讨论 拷贝上面的列表到 “评论审核” 或者 “评论黑名单”...

发现规律,抽象步骤

2024年9月10日 08:53

星期天的下午,初中的同学群又开始了数学题答问答。这一次又不知道是什么题目,反正照出来的那个相片右下角页码那里写的是有理数。有理数无理数具体是什么东西,实际上我已经不太记得了,但这就只是一个定义,稍微翻看一下书本,就会回忆起来。我不知道有理数跟那道题有什么关系。对一个路人来说,我觉得那道题就是一个考察发现规律的,发现那堆数字正负值的规律、发现那堆数字排列组合的规律。相对于高中时代的题目来说,我感觉那道可能是小学或者初中的题目,规律还是比较好找的。

当我第一眼看到那道题目的时候,我就彻底没想过要理,直接继续看我的电视,把那忽略掉,因为我知道即便我不回答,其他人也会回答。我不是唯一的一个答案。看上去那是一本习题册或者一个试卷册,无论是哪一款,肯定有标准答案,问题只是我的那个同学不知道那个答案到底是怎么来的。其实我也不知道他为什么要知道那个答案是怎么来的,毕竟要做这个题目的人不是他,而是他的孩子。所以孩子不知道怎么做那道题,为什么操心了半天的人却是家长呢?当我那个同学还是个孩子的时候,他不会做的题目他的家长绝对不会为他操心。不会做就不做,不会做就回去问老师问同学。现在这个该怎么做的这个责任仿佛都落到了家长的身上,又或许,是因为我的同学觉得他还是个小孩的时候,他的父母没尽过这种义务,他觉得不好,所以现在他成了孩子的家长,他觉得自己有责任去弄清楚,然后教小孩。

他觉得这是他的义务,但我就只是个同学而已,我没有义务解答题目。有兴趣的时候可能看一眼,没兴趣的时候就像这样直接忽略。周日下午吃过晚饭,回单位的路上,我又打开了那个群,仔细看了一下题目以及同学的回答。看过那道题目以后,我就得出了最上面的那个看法。我不知道那道题跟有理数到底有什么关系,但可以肯定的是,那道题的意图就是让做题的人发现规律并利用规律。

发现规律利用规律这种东西,在往后的人生之中实在太普遍了。抽象到哲学的层面,几乎每一件事都是这么个情况。数学这种东西,理论上是最容易用逻辑解决的,尤其是对大学以下的数学而言。以前没发现,后来我才意识到,以前那些有标准答案的题目逻辑性都非常强。只是当年的我要应付的事情太多,没办法逐个击破,没有领会其中的逻辑,于是也就只能死记硬背了。

那道题给我的感觉就是,对懂的人来说,找出规律以后,代入数字就能出结果,但是对那些知道规律,但又不知道如何把规律抽象成公式的人来说,那就等于是只能把那些东西都罗列出来,但关键是如果数量级很大,全部罗列是不可能的任务。对懂行的人来说,无论是自己列公式,还是写个程序跑一下,前提都是你发现了其中的规律,而且你把那个规律抽象成某些公式,于是最后题目就变成代入数字求和。如果硬是把所有东西都罗列一遍,那就只是写个程序,让程序按照你要的那个样子跑一遍,但这个跑一遍对懂的人来说是毫无意义的。因为不需要跑,不需要用最后的结果数字验证就能得出预判的那个结果就是最终答案。

编程是什么呢?编程就是发现规律,然后把那些规律抽象成某些步骤。

使用 WordPress 创建自定义链接分享

2024年9月9日 11:21

步骤 1:在主题文件夹下,找到 functions.php 文件,将下方代码复制粘贴进去。

function create_share_links_post_type() {
    register_post_type('share_links',
        array(
            'labels' => array(
                'name' => __('链接分享'),
                'singular_name' => __('链接'),
                'add_new' => __('添加新链接'),
                'add_new_item' => __('添加新链接'),
                'edit_item' => __('编辑链接'),
                'new_item' => __('新链接'),
                'view_item' => __('查看链接'),
                'search_items' => __('搜索链接'),
                'not_found' => __('没有找到链接'),
                'not_found_in_trash' => __('在回收站中没有找到链接')
            ),
            'public' => true,
            'has_archive' => true,
            'supports' => array('title'),
            'exclude_from_search' => true,
            'publicly_queryable' => true,
            'show_ui' => true,
            'show_in_menu' => true,
            'menu_position' => 5,
            'menu_icon' => 'dashicons-admin-links',
        )
    );
}
add_action('init', 'create_share_links_post_type');

function add_share_links_meta_boxes() {
    add_meta_box(
        'share_links_meta_box',
        '链接地址',
        'share_links_meta_box_callback',
        'share_links',
        'normal',
        'high'
    );
}
add_action('add_meta_boxes_share_links', 'add_share_links_meta_boxes');

function share_links_meta_box_callback($post) {
    wp_nonce_field('save_share_links_data', 'share_links_nonce');
    $link_url = get_post_meta($post->ID, 'link_url', true);
    ?>
    <input type="text" id="link_url" name="link_url" value="<?php echo esc_url($link_url); ?>" class="regular-text" />
    <?php
}

function save_share_links_data($post_id) {
    if (!isset($_POST['share_links_nonce']) || !wp_verify_nonce($_POST['share_links_nonce'], 'save_share_links_data')) {
        return;
    }

    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }

    if (!current_user_can('edit_post', $post_id)) {
        return;
    }

    if (isset($_POST['link_url'])) {
        update_post_meta($post_id, 'link_url', sanitize_text_field($_POST['link_url']));
    }
}
add_action('save_post_share_links', 'save_share_links_data');

function share_links_columns($columns) {
    $new_columns = array();
    foreach ($columns as$key => $value) {
        if ($key == 'date') {
            $new_columns['link_url'] = __('链接地址');
        }
        $new_columns[$key] = $value;
    }
    return $new_columns;
}
add_filter('manage_share_links_posts_columns', 'share_links_columns');

function share_links_custom_column($column,$post_id) {
    switch ($column) {
        case 'link_url':
            $link_url = get_post_meta($post_id, 'link_url', true);
            if (!empty($link_url)) {
                echo esc_url($link_url);
            } else {
                echo __('未设置');
            }
            break;
    }
}
add_action('manage_share_links_posts_custom_column', 'share_links_custom_column', 10, 2);

步骤 2:刷新 WordPress 后台页面,在左侧菜单可看到「链接分享」。

步骤 3:在你的主题文件夹中找到(或创建)一个名为 page-templates 的子文件夹。在该文件夹中创建一个名为 share-links.php 的新文件。将下面的代码复制并粘贴到 share-links.php 文件中。

<?php /* Template Name: share_links */ ?>

<?php get_header(); ?>

<?php
// 设置每页显示的链接数量
$posts_per_page = 20;

// 设置当前页码
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

// 创建一个查询来获取链接
$args = array(
    'post_type' => 'share_links',
    'posts_per_page' => $posts_per_page,
    'paged' => $paged,
);

$share_links_query = new WP_Query($args);

if ($share_links_query->have_posts()) {
    while ($share_links_query->have_posts()) {
        $share_links_query->the_post();

        // 获取链接地址
        $link_url = get_post_meta(get_the_ID(), 'link_url', true);

        // 输出链接地址
        if (!empty($link_url)) {
            echo get_the_date();
            echo '<a href="' . esc_url($link_url) . '">' . get_the_title() . '</a><br>';
        }
    }
}

// 分页导航
<div class="pagination">
    <?php
    echo paginate_links(array(
        'total' => $share_links_query->max_num_pages,
        'current' => $paged,
    ));
    ?>
</div>

<?php
} else {
    echo '<p>' . __('没有找到链接。') . '</p>';
}

wp_reset_postdata();
?>

<?php get_footer(); ?>

步骤 4:在 WordPress 后台新建一个页面,引用名为 share_links 的模板即可。

回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?)

2024年9月3日 05:28

2003年,我参加了高考,暑假后便到了北京,9月份入学中国农业大学国际学院(ICB)。不过当时我是通过“计划外”入学的,因为高考成绩没能达到农大的录取分数线,所以是自费进入这个中外合作办学项目的。大一在国内学习,大二大三则出国。

2003-2004学年是我计算机知识突飞猛进的第一个阶段。那时我非常专注于学习,学院还专门为我和另一位同学请了一位外教。我淘了一台二手老电脑,装了Windows 95/98系统。其他同学都在玩游戏,而我的老电脑只能用来学习和编程。我的宿舍床上堆满了计算机书籍,大多是从二手书市场或者地摊淘来的。那个时候我可能一个学期都没换过被子,床上卫生环境可能很糟糕。

学院有专门的计算机实验室,我们当时学习的是Java,Applet编程还很流行。我记得那时C#刚刚问世。我觉得电脑课上的内容很简单,于是外教给我布置了一个特殊的题目,并承诺如果我能完成,期末考核就给我A+。最后我确实做出来了,他也信守承诺给了我A+。

mycomputer-scaled 回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?) 照片 看图说话 程序员 编程 讲故事

当时二手淘的电脑,装的是WIN98,很老的电脑了,原来我喜欢这一类的美女,左下角那个可能是Modem

mywall2-scaled 回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?) 照片 看图说话 程序员 编程 讲故事

宿舍7人上下铺,中国农业大学国际学院ICB舒畅宿舍

SV400005-scaled 回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?) 照片 看图说话 程序员 编程 讲故事

笨重的CRT显示屏幕

SV400006-scaled 回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?) 照片 看图说话 程序员 编程 讲故事

当时教程是JAVA-当时Applet还很流行

SV400008-scaled 回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?) 照片 看图说话 程序员 编程 讲故事

中国农业大学国际学院ICB的电脑机房-摄于大概2003/2004年

英文:The Computers at Early 2000s

本文一共 542 个汉字, 你数一下对不对.
回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?). (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?) 照片 看图说话 程序员 编程 讲故事
The post 回忆起20年前大学时期的学生生活(2003-2004电脑长什么样?) first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 在英国给孩子换学校的经历: 孩子离开了村里的小学 由于搬了家, 孩子上学得提前半小时出门了, 因为早上堵, 也得开车半小时才能到. 之前在 Fen Drayton 村庄上小学, 早上8:45学校门开, 9点敲钟孩子排队依次进入教室, 我们由于在村里, 只需要提前5分钟出门和孩子一起走路就可以了. 现在一下子早上变得很匆忙, 得叫孩子起床, 做早饭,...
  4. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  5. 公司请的专业摄影师 公司来了新的CEO管理之后,很多事情都不一样了, 特别是一些公司对外形象的事情就特别的在意, 比如公司网站用上SSL.现在公司还有空闲的位置,请速来(钱多人不傻). 一月份出差回LUTON,刚好公司请来摄影师给高层管理照像放网站上的,于是我也凑了凑热闹(但是却还不够资格被放在公司网站上),不过没关系,放这里也差不多. 人到中年, 沧桑感强了些. 更新更新: 同事用他NB的单反给谢菲尔得办公室的人也拍了一组这样的照片.看起来很不错, 很专业,灯光,道具应有尽有.我已经用在了LINKEDIN页面上,立马高大上. 本文一共 230 个汉字, 你数一下对不对. 公司请的专业摄影师. (AMP...
  6. Leetcode 的在线调试器 最近 leetcode 刷题网站出了一个在线调试器. 个人感觉非常好用. 因为我平时是用 IPAD+蓝牙键盘来刷题, 而在 ipad 上是没有集成的IDE的, 对于调试来说, 只能很原始的让函数退出一个值, 然后尝试不同的输入来发现问题. leetcode在线调试器的好处 理论上来说, 你可以直接在浏览器里解决任何一道...
  7. 优化设计 个人主页 并且 PageSpeed Insights 双项 100分 坛子的个人主页 www.tanzhijun.com 不错 很适合个人主页的模板. 而且是手机友好. 于是我照着把 我的主页改了改. https://steakovercooked.com 并且做了几点修改: 0. 使用 google mod_pagespeed 把 JS,...
  8. 在英国开车的简单介绍/英国开车上路需要准备什么? 在英国合法上路需要有: 有效的驾照; MOT 车的年检; 路税 (Road Tax);还有最重要的汽车保险; 四者缺一不可. 千万不要有侥幸心理, 因为警察现在都高科技, 都能扫描车牌就能知道你合不合法. 不合法直接拦下来轻则罚款, 重则扣车上述法庭. 驾照 在英国可以用欧盟的大部分驾照,...

回溯 = 深度优先搜索(DFS) + 剪枝

2024年9月2日 17:45

回溯 = DFS + 剪枝” 是一个对回溯算法简明且直观的描述。要理解这一点,我们可以先拆解这个等式中的几个关键概念。

深度优先搜索 (DFS)

DFS(Depth-First Search)是一种图或树的遍历算法,它从根节点开始,沿着一个分支深入到尽可能远的节点,直到达到叶子节点或无可拓展的节点,然后回溯到上一个节点继续搜索其他分支。这种搜索策略自然地适合解决需要遍历所有可能状态的问题,如组合、排列问题等。

剪枝/Pruning

剪枝(Pruning)是指在搜索过程中,提前排除不符合条件的分支,以减少计算量。剪枝的主要作用是在搜索的过程中,避免无谓的计算。通过某些条件判断,可以在尚未完全展开某些分支时就停止搜索,从而减少时间复杂度。例如,当我们知道一个分支肯定不会产生有效解时,可以提前终止该分支的搜索过程。

回溯算法/Backtracking

回溯算法可以看作是深度优先搜索DFS的一种特例或具体应用。它采用DFS的思想,在搜索的过程中尝试每一种可能的选择(通常是通过递归实现),并在发现某个选择不符合条件或已经无法产生有效解时,及时回退(即“回溯”),然后继续尝试其他选择。这种“试探—回溯”的过程就构成了回溯算法。

结合三者的理解

DFS 为回溯算法提供了基本的搜索框架,即从起点开始沿着一个分支深入探索;

剪枝 则是在DFS基础上增加的优化步骤,目的是减少无效状态的探索。

因此,“回溯 = DFS + 剪枝” 是对回溯算法的一种总结。它表明回溯算法不仅仅是简单的深度优先搜索,还通过剪枝来提升效率。剪枝使得回溯算法在解决很多问题时比单纯的DFS更加高效,尤其是在解空间很大的情况下,剪枝能够大幅减少计算量,从而使得问题求解变得可行。

例子:Alpha-beta 算法剪枝

Alpha-beta 剪枝可以看作是一种回溯算法,它通过剪枝技术增强了深度优先搜索算法。

alpha-beta-pruning 回溯 = 深度优先搜索(DFS) + 剪枝 ACM题解 程序设计 算法 编程

alpha-beta-pruning

Alpha-beta 剪枝:这是用于减少博弈论中极小极大算法中评估节点数量的一种技术。

回溯算法:Alpha-beta 剪枝确实可以被视为回溯的一种形式,因为它系统地探索潜在解决方案(在本例中为游戏动作)并修剪保证不会影响最终决策的路径。

深度优先搜索 (DFS):Alpha-beta 剪枝通常使用 DFS 对树结构进行操作,在回溯之前深入探索节点。

剪枝技术:Alpha-beta 剪枝的主要特征是“剪枝”部分,其中跳过不可能影响最终决策的树的分支。

英文:Backtracking Algorithm = Depth First Search + Pruning

本文一共 810 个汉字, 你数一下对不对.
回溯 = 深度优先搜索(DFS) + 剪枝. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 回溯 = 深度优先搜索(DFS) + 剪枝 ACM题解 程序设计 算法 编程
The post 回溯 = 深度优先搜索(DFS) + 剪枝 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  2. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  3. 软件工程师面试技巧之: 使用哈希表降复杂度 最近在刷题, 倒不是为了找工作, 主要是为了练练脑子, 日子过得太舒服, 人脑不动容易变笨. 程序员应该都了解并能熟悉使用 Hash 哈希表, 哈希表的插入和查找时间复杂度是O(1), 空间复杂度是O(N). 我们来看一道简单的面试题: 给定一个数组,找出相差为2的数对,比如: {1, 3, 5,...
  4. 升级到 Delphi 10 西雅图 公司前不久买了DELPHI XE8 (花了1400多英镑 一套). 并且买一送一, 我选择了 Delphi 2007. 因为D2007是 ANSI版本中最好的WIN32开发利器. 由于当时选了一年的升级服务 所以昨天发布的 Delphi 10 Seattle...
  5. 英国房子的EPC节能报告(Energe/Efficiency Performance Certificate) EPC (Energe/Efficiency Performance Certificate) 是英国房子的节能报告, 法律上规定, 每个房子都必须要有一个EPC报告, 报告的有效期为十年. 房东在把房子出租或者想卖房的时候, 这个EPC就必须有效, 在一些情况下 比如出租房子的时候, 这个EPC报告还必须符合一些最低标准, 比如房子必须满足 F档(类似及格线)...
  6. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  7. Javascript 中 sleep 函数实现 Javascript 中并没有 built-in 的 sleep 函数支持, 在 async/await/Promise 的支持之前, 我们可以用 busy-waiting 的方式来模拟: 1 2 3...
  8. 在英国开车走公交通道 Bus Lane 被罚款的经历 昨天才发现有一封信没有打开, 打开后心凉了一下, 原来是媳妇开车不小心走 Bus Lane (公交通道)被摄像头拍到, 需要交罚款. 走公交通道 Bus Lane可以申诉么? 信上说, 如果有足够的理由, 可以在28天内提交相关的证据来申诉抗议, 会有人来审核, 当然不一定能保证通过....

电视机有声音没图像自己动手维修小记

2024年8月30日 22:10

2017年底买了暴风影音的电视机,当时就图它的屏幕大(55吋),价格便宜(两千元出头),想着电视机的技术含量不高,一般都很耐用,用个十几年应该没问题。没想到2022年电视机突然就坏了,开机后有声音没图像,什么也看不到,屏幕是全黑的。当时找了家维修店,开价700元,讲价到450元。到今年7月,用了不到两年,电视机又出现了同样的问题,这下犹豫要不要再修,再修的话,维修费用加起来就要900块,不划算,而且修了可能还会坏,还不如去买台新的。

在B站看了几个「电视机有声音没图像」问题的视频,都讲到是背光灯条坏了,判断方法是:开机后,用强光手电筒照屏幕,隐隐约约能看到图像。我用手机自带手电筒照了屏幕,确实能看到图像,应该就像视频里说的背光灯条坏了。

死马当作活马医,打算自己动手修下试试,修不好大不了买台新的。先在淘宝上买了屏幕对应的背光灯条,怕买错,问了好几家。灯条到货后,对照视频拆机,把屏幕拆出来,比想象的难,用了2个多小时。把灯条换上,接下来就要把其他的都装回去。没想到还原比拆机还麻烦,两人用了4个小时才装好。拆和装都小心翼翼,怕弄断了电线。

激动人心的时刻到了,开机,图像出来了!但是,看起来不太对,图像偏黄,屏幕的色彩失真,应该是买的背光灯条不是原装的原因。

两个人用了快一天的时间修好了电视机,省了400块,下次如果再坏,换起来应该会更快些了。

刷题之路: 刷题改变人生

2024年8月22日 14:38

2018年8月20日,我第一次购买了力扣会员,花费159美元,开启了刷题之路。两天后,8月22日,我去了伦敦参加亚马逊的面试。进入微软(剑桥研究院)后,我已经不再需要刷题,但我依然认为,刷题是对自己最有价值的投资。

第六年力扣会员了,依然觉得这个是对自己最好的投资。

2024-08-21-22.39.28 刷题之路: 刷题改变人生 Amazon 奋斗 程序员 编程 资讯 面试

力扣会员一年159美元,是对自己最好的投资。

2024-08-21-22.39.45 刷题之路: 刷题改变人生 Amazon 奋斗 程序员 编程 资讯 面试

2018年8月22日第一次面试亚马逊(ASCS伦敦)

2024-08-21-22.39.43 刷题之路: 刷题改变人生 Amazon 奋斗 程序员 编程 资讯 面试

收到伦敦亚马逊的面试邀请

2024-08-21-22.39.38 刷题之路: 刷题改变人生 Amazon 奋斗 程序员 编程 资讯 面试

刷题改变人生

2024-08-21-22.48.23 刷题之路: 刷题改变人生 Amazon 奋斗 程序员 编程 资讯 面试

在抱怨自己赚钱少之前,先努力 让自己值钱。Let Yourself Worth It

过往都是体验

本文一共 281 个汉字, 你数一下对不对.
刷题之路: 刷题改变人生. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c 刷题之路: 刷题改变人生 Amazon 奋斗 程序员 编程 资讯 面试
The post 刷题之路: 刷题改变人生 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  2. 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
  3. 在英国给孩子换学校的经历: 孩子离开了村里的小学 由于搬了家, 孩子上学得提前半小时出门了, 因为早上堵, 也得开车半小时才能到. 之前在 Fen Drayton 村庄上小学, 早上8:45学校门开, 9点敲钟孩子排队依次进入教室, 我们由于在村里, 只需要提前5分钟出门和孩子一起走路就可以了. 现在一下子早上变得很匆忙, 得叫孩子起床, 做早饭,...
  4. 同一台服务器上多个WORDPRESS站点的一些设置可以移出去 我自从把所有网站都挪到一处VPS服务器上 就发现很多事情省事很多 可以同时管理多个网站 包括 WORDPRESS博客. 比如我有四个WORDPRESS博客 然后我就把通用的一些资料给移出去 移到 HTTP或者HTTPS都不能直接访问的文件夹里这样就更安全许多. 文件 wp-conn.php 存储了 相同的数据库资料. 1 2...
  5. 公司请的专业摄影师 公司来了新的CEO管理之后,很多事情都不一样了, 特别是一些公司对外形象的事情就特别的在意, 比如公司网站用上SSL.现在公司还有空闲的位置,请速来(钱多人不傻). 一月份出差回LUTON,刚好公司请来摄影师给高层管理照像放网站上的,于是我也凑了凑热闹(但是却还不够资格被放在公司网站上),不过没关系,放这里也差不多. 人到中年, 沧桑感强了些. 更新更新: 同事用他NB的单反给谢菲尔得办公室的人也拍了一组这样的照片.看起来很不错, 很专业,灯光,道具应有尽有.我已经用在了LINKEDIN页面上,立马高大上. 本文一共 230 个汉字, 你数一下对不对. 公司请的专业摄影师. (AMP...
  6. Leetcode 的在线调试器 最近 leetcode 刷题网站出了一个在线调试器. 个人感觉非常好用. 因为我平时是用 IPAD+蓝牙键盘来刷题, 而在 ipad 上是没有集成的IDE的, 对于调试来说, 只能很原始的让函数退出一个值, 然后尝试不同的输入来发现问题. leetcode在线调试器的好处 理论上来说, 你可以直接在浏览器里解决任何一道...
  7. 优化设计 个人主页 并且 PageSpeed Insights 双项 100分 坛子的个人主页 www.tanzhijun.com 不错 很适合个人主页的模板. 而且是手机友好. 于是我照着把 我的主页改了改. https://steakovercooked.com 并且做了几点修改: 0. 使用 google mod_pagespeed 把 JS,...
  8. 公司给配了台高配DELL笔记本 早上例会结束的时候我顺便说了一句 我的笔记本有点慢, 当时我并不知道我的经理远程用电话也参加会议了(他全程在听), senior staff SE 对着电话说, “peter, you hear that? btw, my disks are...

通义灵码,你的编程小助手

2024年4月7日 11:25
通义灵码是一个编程助手,可以嵌入到你的IDEA中辅助编程,它经过了海量优秀开源代码数据训练,可根据当前代码文件及跨文件的上下文,为你生成行级/函数级代码、单元测试、代码注释等。沉浸式编码心流,秒级生成速度,让你更专注在技术设计,高质高效地完成编码工作。

PyTorch 初体验

2018年1月7日 08:00

这两周简单看了下 pytorch,虽然说还没有非常系统的、全方面的认识,但姑且总结一下好了。

基础模块构成

最核心的模型组件都在 torch.nn 这个模块里,这个模块里包含了

  • 不同类型的网络结构,如:Embedding, LSTM, Conv1d, MaxPool1d, Linear
  • 不同类型的激活函数,如:RELU, SELU, Sigmoid, Tanh
  • 不同类型的目标函数,如:CrossEntropyLoss, MSELoss, HingeEmbeddingLoss

不一一列举,总之,如果是想构建起一个网络,不考虑训练的话,那么只用 torch.nn 这个模块里的东西就足够了。

比较重要的是 torch.nn.Module 这个类,上述的网络结构、激活函数、目标函数都继承自这个类,如果是想自定义模型、激活函数、目标函数的话,继承这个类就好。所以这个类的行为和内在机制有必要好好了解一下。

其次就是 torch.autograd 这个模块,其中的 Variable 是 torch 里的输入、输出数据的标准类型,也就是说,我们定义好一个模型后,如果想输入东西,就得把数据都转成 Variable 类型的值。

torch.optim 里则定义了常用的一些优化方法,不多,罗列如下

  • Adadelta
  • Adagrad
  • Adam
  • SparseAdam
  • Adamax
  • ASGD
  • SGD
  • Rprop
  • RMSprop
  • Optimizer
  • LBFGS

差不多就是这个样子。

其他

暂时了解还不多,就不长篇大论了,这里随便写写。

在模型层面,pytorch 使用起来确实舒服很多。主要的点有这些

  • 不需要管 session、graph 这些东西,定义好的网络结构,直接就能接受输入并得到输出
  • 模块都继承自 torch.nn.Module 这个类,而这个类被设计成了 picklable 的,我们直接用 pickle.dump 和 pickle.load 就能对模型进行保存和加载,相比之下,tensorflow 默认将模型拆成若干个文件然后通过 saver 的方式来保存和加载一直让我非常抗拒 —— 倒不是说我认为模型存成多个文件就不好,但至少提供让我不存成多个文件的选项吧?在 tensorflow 里想要自己去把模型结构和模型参数拿出来按自己的想法存储这件事情,我是一直都没有成功过……
  • torch.nn.Module 类有一个 bool 类型的 training 成员,如果将其设置成 False,那么 Dropout、BatchNorm 之类的层就会失效,这个虽然是个很小的点但也是非常让人舒服的一点,tensorflow 里为了解决这个问题,通常就得自己来设置选项来保证在训练和预测的时候产生两张不同的图,贼恶心

    详情见 这个帖子这个帖子

当然,也有一些坑,或者说我觉得不太满意的地方吧

  • 目前 pypi 上的版本落后于官网版本,按照官网上的安装方法要下几百兆的东西……吐血……
  • 没有对整个项目结构和机制的总体介绍,当然,毕竟版本还在 0.3.0……
  • 模型内部的数据类型好像有不一致的地方,我在默认行为下,遇到过什么 DoubleTensor 的错误,可能和这个 issue 有关
  • 需要手动选择是否使用 CUDA,但是我明明看到 torch.cuda 下有个 is_available 的方法
  • RNN 的输入和输出,默认第二个维度是 batch,如果想让第一个维度表示 batch,就得手动把 batch_first 设置成 True,挺奇怪的

Windows 上 Python 开发环境搭建

2016年9月10日 08:00

前言

本文会涉及:

  1. Windows 上 Python 开发环境的建立(基于 Cygwin)
  2. Python 开发的一些基础知识
  3. 深度学习框架 Theano 在 Windows 环境下的安装和使用

简单介绍一下 Cygwin: Cygwin 是一个旨在提供 Windows 上的类 Linux 环境的软件,包含了提供 POSIX API 的底层 library 以及大量建立在 POSIX API 上的 GNU 软件和其他开源软件的集合。通过 Cygwin ,可以在不脱离 Windows 环境的情况下获得与 Linux 上一致的开发体验,当然也包括 Python。

Cygwin 的安装

Cygwin 的官网 上下载对应的安装程序,32 位系统就下载 setup-x86.exe,64 位就下载 setup-x86_64.exe —— 现在应该都是 64 位系统了吧。

cygwin_download.png

安装过程很简单,大部分情况下点击下一步进行下去即可。

cygwin_installation_1.png cygwin_installation_2.png

这里可以按自己的需要设置一下 Cygwin 的安装目录,同时在安装完成后,Cygwin 会将这个目录当作根目录(Linux 上的 /)。

cygwin_installation_3.png

安装过程中会从网络上下载各种东西,这里可以设置用于存放这些东西的 临时目录 :

cygwin_installation_4.png

到下面这一步需要选择下载站点,大致上来说选国内的站点速度会快一些,教育网的建议选中科大的源(http://mirrors.ustc.cn/),如图;非教育网的话可以在下面那个「User URL」输入框中填入网易的源(http://mirrors.163.com/cygwin/),点击 Add 后网易的源就会出现在列表中,选中即可。

cygwin_installation_5.png

然后会进入一个更详细的安装页面,一些基础的、必须的软件包已经默认选中了,可以按自己的需要选择一些额外的软件包。

cygwin_installation_6.png

点下一步继续,会开始下载、安装选中的软件包,如无意外,就可以顺利安装上 Cygwin 了。

cygwin_installation_7.png cygwin_installation_8.png cygwin_installation_9.png

至此安装结束,会在桌面上生成 Cygwin 的快捷方式,双击即可打开,正常的话,应该会看到下面这样一个黑乎乎的窗口,其实就是个终端啦。

cygwin_face.png

Cygwin 的界面设置

刚安装好时的 Cygwin 默认外观并不是很好看(当然比 cmd 还是好看一点的……),我们可以通过调整设置来获得一个更让人舒适的外观。

首先是字体,在 Cygwin 的窗口区域右键,选择 Options 会弹出 Cygwin 的设置,如下所示进行字体设置:

cygwin_font_setting.png

这里我选择了 DejaVu Sans Mono 这一款字体,这是我常用的编程字体 —— 以我的标准而言,一款编程用的字体,最低要求是要能区分出一些形状相似的字符,如下图所示:

cygwin_face_2.png

注: 这个字体并不是 Windows 自带的,需要自己下载和安装。

然后,如下图所示,在 Looks 一栏可以设置窗口的透明度

cygwin_ui_setting.png

这是选择透明等级 Med (中等)后的效果

cygwin_med_transparency.png

可以看到右边还有一个 "Glass" 可以选,选择后的效果为

cygwin_glass.png

注意,这里我为了配合透明和 Glass 效果,把窗口的滚动栏去掉了,见设置页面的 Window 一栏。

最后一项,我们设置一下 Cygwin 里的终端提示符。终端提示符一般通过环境变量 "PS1" 来进行设置,由于这个是 Cygwin(Linux) 特有的环境变量,就不在 Windows 的系统设置里设置这个环境变量了。我们可以新建一个名为 ".bash_profile" 的文件,Cygwin(严格来说是 shell) 启动时会读取这个文件并进行初始化。由于 Windows 下不允许直接以 "." 符号起始的文件,这个文件只能在 Cygwin 中创建,用 vi 命令直接创建并编辑即可(注意是 vi 而不是 vim),写入内容

CC_RESET='[00m'

CC_BLACK='[0;30m'
CC_RED='[0;31m'
CC_GREEN='[0;32m'
CC_BROWN='[0;33m'
CC_BLUE='[0;34m'
CC_PURPLE='[0;35m'
CC_CYAN='[0;36m'

CC_LIGHT_GRAY='[0;37m'
CC_DARK_GRAY='[1;30m'
CC_LIGHT_RED='[1;31m'
CC_LIGHT_GREEN='[1;32m'
CC_YELLOW='[1;33m'
CC_LIGHT_BLUE='[1;34m'
CC_LIGHT_PURPLE='[1;35m'
CC_LIGHT_CYAN='[1;36m'
CC_WHITE='[1;37m'

force_color_prompt=yes
export PS1='\[\e${CC_WHITE}\][\[\e${CC_RESET}\]\
\[\e${CC_LIGHT_BLUE}\]\w\
\[\e${CC_RESET}\]\[\e${CC_WHITE}\]]\[\e${CC_RESET}\]\n\
\[\e${CC_LIGHT_GREEN}\]\u \[\e${CC_RESET}\]\
\[\e${CC_LIGHT_RED}\]\$\[\e${CC_RESET}\] '

效果如下:

cygwin_bash_prompt.png

Python 的安装和使用

安装 Python/Python3

默认情况下,Cygwin 中没有安装 Python ,不过它会读取 Windows 系统中的环境变量,如果自己有单独安装 Python,且已经配置好环境变量,在 Cygwin 中也可以运行 Python 。但建议还是安装 Cygwin 自身软件仓库中的 Python。

首先下载一个 Cygwin 的软件包管理工具: apt-cyg 。这是一个类似 Debian/Ubuntu 中的 apt-get 的工具,可以让我们在 Cygwin 中查找、安装、卸载软件包,非常的方便。依次在 Cygwin 中执行下面两条命令即可安装 apt-cyg:

lynx -source rawgit.com/transcode-open/apt-cyg/master/apt-cyg > apt-cyg
install apt-cyg /bin

由于 apt-cyg 使用 wget 来访问服务器上的数据,建议先安装一下 wget (不安装的话会用 lynx 替代),同时一些常用的工具也先安装一下

apt-cyg install wget vim gcc-core tmux sl

apt-cyg 的几个子命令:

  • install <package>: 安装软件包
  • remove <package>: 卸载软件包
  • listall <keyword>: 以指定的关键词搜索相关的软件包

apt-cyg 安装好后就可以用来安装 Python 的基本环境了:

apt-cyg install python python3

美中不足的是 Cygwin 中的 Python 3 版本为 3.4.3 ,这对需要使用 Python 3.5 的朋友来说可能是一个比较大的问题。

安装 pip

在安装好 Python 后,通过下面的命令安装 pip 和 pip3:

python -m ensurepip
python3 -m ensurepip

鉴于国内访问 pip 官方的源速度有点慢,可以考虑修改 pip 的配置,使用国内的源,比如:

首先在 Cygwin 的 HOME 目录中新建配置文件:

mkdir ~/.pip/ && touch ~/.pip/pip.conf

然后往里写入以下配置(以使用豆瓣源为例):

[global]
trusted-host=pypi.douban.com
index-url=http://pypi.douban.com/simple/

建议配置好后也更新一下 pip 本身

pip install pip --upgrade

然后就可以直接安装自己想要使用的各种软件包了:

pip install numpy pandas nltk jieba

使用 virtualenv

在实际进行 Python 开发的时候,通常会使用 virtualenv 来进行环境的隔离,什么意思呢?举个例子,我们有两个项目,分别称之为项目 A 和项目 B,它们使用的 Python 版本和 Python 包的版本都不同,如下

项目 A:

  • Python 版本: 2.7
  • Python 包依赖:
    • jieba 0.37
    • nltk 3.1

项目 B:

  • Python 版本: 3.4
  • Python 包依赖:
    • jieba 0.38
    • nltk 3.2

由于系统上的同一个 Python 包是没法安装两个不同版本的,如果使用系统中的 Python 包,是不能满足以上这种情况的。virtualenv 的作用就是为每个单独的项目建立独立的环境。要使用 virtualenv ,在 Cygwin 上只能通过 pip 来安装:

pip install virtualenv

对项目 A ,按如下步骤建立环境:

  • 首先用 virtualenv 为项目 A 建立一个环境:

    virtualenv venv_for_a -p /usr/bin/python2.7
    

    这一步会建立目录 venv_for_a ,并将 python2.7 和 pip 拷贝到其中,形成一个最小的 Python2 运行环境。

  • 用 source 然后激活这个环境:

    source venv_for_a/bin/activate
    
  • 然后安装依赖

    pip install jieba==0.37 nltk==3.1
    

对项目 B 同样如此:

  • 建立环境

    virtualenv venv_for_b -p /usr/bin/python3.4
    
  • 激活环境

    source venv_for_b/bin/activate
    
  • 安装依赖

    pip install jieba==0.38 nltk==3.2
    

Theano/TensorFlow 的安装和使用

本节将在之前搭建好的 Python 开发环境基础上,展示 Windows 下深度学习框架 Theano的安装和使用。

下面的内容都在以下环境下进行:

  1. Cygwin PC x86_64
  2. python2.7
  3. pip 8.1.2
  4. ipythohn 5.1.0
  5. Theano 0.8.2

注: 我也尝试了在 Windows 上安装 TensorFlow ,但是以失败告终,详见后叙。

Theano

theano 的安装比较简单,直接用 pip 进行安装即可:

pip install theano --upgrade

Theano 还依赖 Numpy、Scipy,然后在安装 Scipy 的时候报错,错误信息为:

no lapack/blas resources found

LAPACK/BLAS 是提供线性代数相关计算的 C 库,既然缺失的话,用 apt-cyg 搜索看看:

apt-cyg listall lapack blas

结果为

lapack
lapack-debuginfo
liblapack-devel
liblapack-doc
liblapack0

liblasem0.4-devel
liblasem0.4-doc
liblasem0.4_4
libopenblas
openblas

把 liblapack-devel, libopenblas 和 openblas 装上

apt-cyg install liblapack-devel libopenblas openblas

重试安装,仍然发生错误,相应的错误信息如下:

error: library dfftpack has Fortran sources but no Fortran compiler found

这是因为缺少 fortran 编译器导致的,安装上 gcc-fortran 即可,但仍然报错,说找不到 g++ ,那把 g++ 也装上

apt-cyg install gcc-fortran gcc-g++

安装之后,尝试在 Python 交互环境中执行 import theano 出错,出错信息为

File "theano/gof/cmodule.py", line 2014, in compile_str
  return dlimport(lib_filename)
File "theano/gof/cmodule.py", line 289, in dlimport
    rval = __import__(module_name, {}, {}, [module_name])
ImportError: No module named cutils_ext

在 HOME 中的 .theano 目录下找到了 cutils,我的系统上是: ~/.theano/compiledir_CYGWIN_NT-6.1-2.2.1-0.289-5-3-x86_64-64bit–2.7.10-64/cutils_ext/。按照 这里这里 给出的信息,这个错误的原因在于里面一个名为 cutils_ext.pyd 的文件,它是 Python 生成的编译后的模块,实际上是一个动态链接库(Dynamic Link Library, DLL),见 Python 官方文档 。由于它是一个 DLL,Cygwin 在加载它的时候会希望它的后缀是 dll ,所以出错了,一种暂时的办法是修改一下它的后缀名

cd ~/.theano/compiledir_CYGWIN_NT-6.1-2.2.1-0.289-5-3-x86_64-64bit--2.7.10-64/cutils_ext/
cp cutils_ext.pyd cutils_ext.dll

这样在 import 时是可以成功的,但在执行下面这段代码时依然会出错

import numpy
import theano
import theano.tensor as T
from theano import function


x = T.scalar('x')
y = T.scalar('y')
z = x + y

f = function([x, y], z)
print f(3, 4)

这是因为用 Theano 定义的符号计算过程,最后也要编译成实际的模块,和前面的 cutils_ext.pyd 的问题是一样的,编译生成的模块后缀为 pyd ,但 Cygwin 加载时希望后缀是 dll 。所以要彻底解决这个问题,需要修改 Theano 中生成编译后模块的代码,让其生成的模块后缀为 dll 而不是 pyd。相关的代码在 Theano 代码中的 cmodule.py 中,在我的机器上,其路径为 "/usr/lib/python2.7/site-packages/theano/gof/cmodule.py"。其中有一个名为 get_lib_extension 的函数,其内容为:

def get_lib_extension():
    """
    Return the platform-dependent extension for compiled modules.
    """
    if sys.platform in ['win32', 'cygwin']:
        return 'pyd'
    else:
        return 'so'

将它改为:

def get_lib_extension():
    """
    Return the platform-dependent extension for compiled modules.
    """
    if sys.platform == 'win32':
        return 'pyd'
    elif sys.platform == 'cygwin':
        return 'dll'
    else:
        return 'so'

修改后可以尝试把 ~/.theano 这个目录删除,然后重新在 Python 交互环境中执行 import theano ,成功后会重新生成 ~/.theano/compiledir_CYGWIN_NT-6.1-2.2.1-0.289-5-3-x86_64-64bit–2.7.10-64/cutils_ext/ 这个目录,不出意外的话,里面应该只有一个 cutils_ext.dll 而没有 cutils_ext.pyd 了。

至此便可以在 Cygwin 中正常使用 Theano 了,如下图所示。

cygwin_run_theano.png

TensorFlow

在 TensorFlow 的 Download and Setup 中,介绍了在 Linux 和 Mac OS 平台上安装 TensorFlow 的详细过程,但并没有提供 Windows 的相关内容。不过其中的 Docker installation 一节给出的解决方案是一个在 Windows 上运行 TensorFlow 的折衷办法,但是仅仅为了安装一个 37MB 的 Python 包,而要下载 180 多 MB 的 Docker Toolbox,不太能接受。

目前在 Windows 上运行 TensorFlow 的方案有以下两种:

  1. 在 Windows 上安装 Docker 或者虚拟机,然后在其中安装 TensorFlow,参考: TensorFlow在Windows上的安装
  2. 使用 Windows 10 上的 Bash On Windows 安装 TensorFlow,有 Windows 10 的朋友可以自己尝试下

(这两种方法我也懒得去试验了,有兴趣和条件(我还用着 Windows 7 呢)的朋友自己再去折腾吧~)

事实上 TensorFlow 目前并没有对 Windows 的原生支持,不过对 Windows 平台的支持已经提上议程,见 Roadmap

在 Github 上有一个 Issue,从 2015 年年底至今,一直在讨论 TensorFlow 对 Windows 的原生支持这件事情,希望在 Windows 平台上使用 TensorFlow 的朋友可以关注一下这个 Issue。

其他方案

使用 Anaconda 是另外一个在 Windows 下建立 Python 编程环境的好主意,Anaconda 是一个基于 Python 的「数据科学平台」,内置了许多用于数据科学、机器学习的 Python 包,并有自己的软件包管理工具,也是很推荐的。

Anaconda 在 Windows 上的使用稍微尝试了一下,但没有对其细节做太多了解,所以就不罗嗦了。有需要的话后面再为 Anaconda 单独写一篇吧。

Numpy 笔记(二): 多维数组的切片(slicing)和索引(indexing)

2016年3月9日 08:00

切片(slicing)操作

Numpy 中多维数组的切片操作与 Python 中 list 的切片操作一样,同样由 start, stop, step 三个部分组成

import numpy as np

arr = np.arange(12)
print 'array is:', arr

slice_one = arr[:4]
print 'slice begins at 0 and ends at 4 is:', slice_one

slice_two = arr[7:10]
print 'slice begins at 7 and ends at 10 is:', slice_two

slice_three = arr[0:12:4]
print 'slice begins at 0 and ends at 12 with step 4 is:', slice_three
array is: [ 0  1  2  3  4  5  6  7  8  9 10 11]
slice begins at 0 and ends at 4 is: [0 1 2 3]
slice begins at 7 and ends at 10 is: [7 8 9]
slice begins at 0 and ends at 12 with step 4 is: [0 4 8]

上述例子是一维数组的例子,如果是多维数组,将不同维度上的切片操作用 逗号 分开就好了

# coding: utf-8
import numpy as np

arr = np.arange(12).reshape((3, 4))
print 'array is:'
print arr

# 取第一维的索引 1 到索引 2 之间的元素,也就是第二行
# 取第二维的索引 1 到索引 3 之间的元素,也就是第二列和第三列
slice_one = arr[1:2, 1:3]
print 'first slice is:'
print slice_one

# 取第一维的全部
# 按步长为 2 取第二维的索引 0 到末尾 之间的元素,也就是第一列和第三列
slice_two = arr[:, ::2]
print 'second slice is:'
print slice_two
array is:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
first slice is:
[[5 6]]
second slice is:
[[ 0  2]
 [ 4  6]
 [ 8 10]]

对于 slice_two,如果 arr 是用 Python 的 list 表示的,那么要得到相同的结果得像下面这样,相对来说就麻烦多了:

import numpy as np

arr = np.arange(12).reshape((3, 4)).tolist()

slice_two = [
    row[::2] for row in arr
]
print slice_two
[[0, 2], [4, 6], [8, 10]]

对于维数超过 3 的多维数组,还可以通过 '…' 来简化操作

# coding: utf-8
import numpy as np

arr = np.arange(24).reshape((2, 3, 4))

print arr[1, ...]               # 等价于 arr[1, :, :]
print arr[..., 1]               # 等价于 arr[:, :, 1]
[[12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]]
[[ 1  5  9]
 [13 17 21]]

索引(indexing) 操作

最简单的情况

对于一个多维数组来说,最简单的情况就是访问其中一个特定位置的元素了,如下所示:

# coding: utf-8
import numpy as np

arr = np.array([
    [1, 2, 3, 4],
    [2, 4, 6, 8],
    [3, 6, 9, 12],
    [4, 8, 12, 16]
])
print '第二行第二列的值:', arr[1, 1]
第二行第二列的值: 4

相比之下,如果用 Python 的 list 来表示上述二维数组,获取同一个位置的元素的方法为:

# coding: utf-8
arr = [
    [1, 2, 3, 4],
    [2, 4, 6, 8],
    [3, 6, 9, 12],
    [4, 8, 12, 16]
]
print '第二行第二列的值:', arr[1][1]
try:
    print '第二行第二列的值(尝试用 Numpy 的方式获取):', arr[1, 1]
except Exception as e:
    print str(e)
第二行第二列的值: 4
第二行第二列的值(尝试用 Numpy 的方式获取): list indices must be integers, not tuple

如果只是二维数组,这种差别可能看起来并不大,但想象一下假如有一个 10 维的数组,用 Python 的标准做法需要写 10 对中括号,而用 Numpy 依然只需要一对。

获取多个元素

事实上,在 Numpy 的索引操作方式 `x = arr[obj]` 中, obj 不仅仅可以是一个用逗号分隔开的数字序列,还可以是更复杂的内容。

  1. 用逗号分隔的数组序列

    • 序列的长度和多维数组的维数要一致
    • 序列中每个数组的长度要一致
    import numpy as np
    
    arr = np.array([
        [1, 2, 3, 4],
        [2, 4, 6, 8],
        [3, 6, 9, 12],
        [4, 8, 12, 16]
    ])
    
    print arr[[0, 2], [3, 1]]
    
    [4 6]
    

    以上面这个例子来说,其含义是: 选择第一行和第三行,然后对第一行选择第四列,对第三行选择第二列。

  2. boolean/mask index

    这个不太好翻译,所以就用原来的英语表达。

    所谓 boolean index,就是用一个由 boolean 类型值组成的数组来选择元素的方法。比如说对下面这样多维数组

    array([[1, 2, 3, 4],
           [2, 4, 6, 8],
           [3, 6, 9, 12],
           [4, 8, 12, 16]])
    

    如果要取其中 值大于 5 的元素,就可以用上 boolean index 了,如下所示:

    import numpy as np
    
    arr = np.array([[1, 2, 3, 4],
                    [2, 4, 6, 8],
                    [3, 6, 9, 12],
                    [4, 8, 12, 16]])
    mask = arr > 5
    
    print 'boolean mask is:'
    print mask
    
    print arr[mask]
    
    boolean mask is:
    [[False False False False]
     [False False  True  True]
     [False  True  True  True]
     [False  True  True  True]]
    [ 6  8  6  9 12  8 12 16]
    

    除了比较运算能产生 boolean mask 数组以外, Numpy 本身也提供了一些工具方法:

    • numpy.iscomplex
    • numpy.isreal
    • numpy.isfinite
    • numpy.isinf
    • numpy.isnan

切片和索引的同异

切片和索引都是访问多维数组中元素的方法,这是两者的共同点,不同之处有:

  1. 切片得到的是原多维数组的一个 视图(view) ,修改切片中的内容会导致原多维数组的内容也发生变化
  2. 切片得到在多维数组中连续(或按特定步长连续)排列的值,而索引可以得到任意位置的值,自由度更大一些

不考虑第一点的话,切片的操作是可以用索引操作来实现的,不过这没有必要就是了。

对于第一点,见下面的实验:

import numpy as np

arr = np.arange(12).reshape(2, 6)
print 'array is:'
print arr

slc = arr[:, 2:5]
print 'slice is:'
print slc

slc[1, 2] = 10000
print 'modified slice is:'
print slc
print 'array is now:'
print arr
array is:
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
slice is:
[[ 2  3  4]
 [ 8  9 10]]
modified slice is:
[[    2     3     4]
 [    8     9 10000]]
array is now:
[[    0     1     2     3     4     5]
 [    6     7     8     9 10000    11]]

Numpy 笔记(一): 多维数组的创建

2016年2月25日 08:00

Numpy 及 ndarray 简介

numpy_logo.png

Numpy 是一个 Python 的科学计算包,提供了以下功能:

  1. 强大、灵活的多维数组对象及丰富的操作
  2. C 实现,执行效率高
  3. 线性代数、丰富的随机数功能

ndarray 就是 Numpy 中表示多维数组的类。本篇笔记将对 ndarray 的创建方法进行整理、总结。

ndarray 的创建

从已有数据中创建

  1. 从 list, tuple 对象中创建:

    import numpy as np
    
    a = np.array([1, 2, 3, 4])
    print 'a is:', a
    
    b = np.array((1, 2, 3, 4))
    print 'b is:', b
    
    a is: [1 2 3 4]
    b is: [1 2 3 4]
    
  2. 从文件中读取

    从文件中读取的方法有: fromfile, load, *loadtxt*。其中 fromfile 方法不建议使用,因为 fromfile 读取的文件要求是用 tofile 保存的,而 tofile 方法保存数据到文件时的具体行为是和具体平台有关的。

    load 方法读取 save 方法保存下来的二进制文件:

    import numpy as np
    from tempfile import TemporaryFile
    
    origin_array = np.array([1, 2, 3, 4])
    np.save('/tmp/array', origin_array)
    
    array_from_file = np.load('/tmp/array.npy')
    print array_from_file
    
    [1 2 3 4]
    

    可以看一下 /tmp/array.npy 这个文件:

    file /tmp/array.npy
    
    /tmp/array.npy: data
    

    如果希望保存的文件是可读的,那么可以用 savetxt 这个方法,用这个方法保存的数据则用 loadtxt 来读取:

    import numpy as np
    
    origin_array = np.array([1, 2, 3, 4])
    np.savetxt('/tmp/array.txt', origin_array)
    
    array_from_file = np.loadtxt('/tmp/array.txt')
    print array_from_file
    
    [ 1.  2.  3.  4.]
    

    再来看一下 /tmp/array.txt 里面的内容:

    cat /tmp/array.txt
    
    1.000000000000000000e+00
    2.000000000000000000e+00
    3.000000000000000000e+00
    4.000000000000000000e+00
    
  3. 从字符串中读取

    fromstring 方法可以从字符串中读取数据并转换为 一维数组:

    import numpy as np
    
    array = np.fromstring('1 2 3 4', dtype=float, sep=' ')
    print array
    
    [ 1.  2.  3.  4.]
    

    如果是用 tostring 将一个多维向量转换为字符串,然后再用 fromstring 读取,也只能得到一个一维数组。另外如果是读用 tostring 转换成的字符串, 建议使用 fromstring 的时候显式指定数组中元素的数据类型 ,不然就 有可能 发生下面这样的悲剧:

    import numpy as np
    
    array = np.array([1, 2, 3, 4], dtype=int)
    print np.fromstring(array.tostring())
    
    [  4.94065646e-324   9.88131292e-324   1.48219694e-323   1.97626258e-323]
    
  4. fromiter: 从可迭代对象中生成一维数组

    和第一种方法的不同之处在于:

    • 只能返回一维数组
    • 必须显式指定数组中元素的数据类型
    • 输入可以是 所有可迭代对象 ,包括 list、tuple、string、unicode, generator,但需要注意的是,这里的 list 和 tuple 不能有嵌套。
    # coding: utf-8
    import numpy as np
    
    
    def count_generator():
        for i in range(4):
            yield i
    
    
    print 'from list:', np.fromiter([1, 2, 3, 4], int)
    print 'from tuple:', np.fromiter([1, 2, 3, 4], int)
    print 'from string:', np.fromiter('1234', int)
    print 'from unicode:', np.fromiter(u'白日依山尽', 'U1')
    print 'from generator:', np.fromiter(count_generator(), int)
    
    
    try:
        print 'from nested list:', np.fromiter([[1, 2], [3, 4]], int)
    except ValueError:
        print 'bad list'
    
    from list: [1 2 3 4]
    from tuple: [1 2 3 4]
    from string: [1 2 3 4]
    from unicode: [u'\u767d' u'\u65e5' u'\u4f9d' u'\u5c71' u'\u5c3d']
    from generator: [0 1 2 3]
    from nested list: bad list
    

创建特定形状的多维数组并进行填充

  • ones

    创建给定形状的多维数组并将数组中所有元素填充为 1:

    import numpy as np
    
    print np.ones((3, 4))
    
    [[ 1.  1.  1.  1.]
     [ 1.  1.  1.  1.]
     [ 1.  1.  1.  1.]]
    
  • zeros

    类似 ones ,但用 0 进行填充:

    import numpy as np
    
    print np.zeros((3, 4))
    
    [[ 0.  0.  0.  0.]
     [ 0.  0.  0.  0.]
     [ 0.  0.  0.  0.]]
    
  • empty

    类似 ones, 但不进行初始化,得到的多维数组中的元素值是不确定的。

    import numpy as np
    
    print np.empty((3, 4))
    
    [[  6.93164023e-310   1.66900197e-316   4.64956088e-317   4.00193173e-322]
     [  1.77481898e-316   6.93164023e-310   9.30845863e-039   2.42092166e-322]
     [  6.93164023e-310   6.93164023e-310   4.68423639e-320   0.00000000e+000]]
    
  • full

    类似 ones, 但需要自己手动指定需为多维数组填充的值。

    import numpy as np
    
    print np.full((3, 4), 17)
    
    [[ 17.  17.  17.  17.]
     [ 17.  17.  17.  17.]
     [ 17.  17.  17.  17.]]
    

从 numerical range 创建多维数组

  • arange

    创建一个一维的数组,用法同 Python 内建方法 range:

    import numpy as np
    
    print np.arange(10)
    print np.arange(0, 10)
    print np.arange(9, -1, -1)
    
    [0 1 2 3 4 5 6 7 8 9]
    [0 1 2 3 4 5 6 7 8 9]
    [9 8 7 6 5 4 3 2 1 0]
    
  • linspace

    给定一个区间,取其 N 等分点组成一个一维数组:

    import numpy as np
    
    print np.linspace(1, 10, num=10)
    
    [  1.   2.   3.   4.   5.   6.   7.   8.   9.  10.]
    
  • logspace

    给定一个 对数尺度(log scale)区间 ,取其 N 等分点对应的 线性尺度(linear scale)上的数值:

    import numpy as np
    
    print np.logspace(1, 3, num=3)
    print np.log10(np.logspace(1, 3, num=3))
    
    [   10.   100.  1000.]
    [ 1.  2.  3.]
    
  • meshgrid

    根据给定的坐标向量创建坐标矩阵。

    import numpy as np
    
    
    print np.meshgrid(np.arange(0, 6))
    print
    
    x, y = np.meshgrid(np.arange(-1, 2), np.arange(0, 2))
    print 'x is:', x
    print 'y is:', y
    print
    
    print 'points built by (x, y):'
    print np.rec.fromarrays([x, y])
    
    [array([0, 1, 2, 3, 4, 5])]
    
    x is: [[-1  0  1]
     [-1  0  1]]
    y is: [[0 0 0]
     [1 1 1]]
    
    points built by (x, y):
    [[(-1, 0) (0, 0) (1, 0)]
     [(-1, 1) (0, 1) (1, 1)]]
    

    在上面的例子中,所得到的是 X 轴上 [-1, 0, 1] 和 X 轴上 [0, 1] 构成的一个 3x2 的网格,共有 6 个点。返回的两个值中的 x 是这 6 个点 在 X 轴上的投影, y 则是这 6 个点在 y 轴上的投影。

    meshgrid 方法的参数数量不受限,可以得到任意 N 维空间中的坐标矩阵。

    了解 meshgrid 方法的功能后,可以来做一些有趣的事情,比如:

    import numpy as np
    import matplotlib.pyplot as plt
    
    x, y = np.meshgrid(np.arange(-1, 1, 0.01), np.arange(-1, 1, 0.01))
    
    contor = np.sqrt(x ** 2 + y ** 2)
    plt.imshow(contor)
    plt.colorbar()
    
    plt.savefig('contor.png')
    '/assets/img/contor.png'
    

    contor.png

  • mgrid

    mgrid 的功能与 meshgrid 类似,但有几点不同:

    1. mgrid 不是函数,而是一个类的对象
    2. mgrid 和 meshgrid 的使用方式不一样,meshgrid 需要传入一维数组作为对象,而 mgrid 则直接使用 '[]' 运算符

      import numpy as np
      
      np.meshgrid(np.arange(-1, 2), np.arange(-1, 2))
      np.mgrid[-1:2, -1:2]
      
    3. 返回值的前两个顺序相反

      import numpy as np
      
      x, y = np.meshgrid(np.arange(-1, 1), np.arange(-1, 1))
      xx, yy = np.mgrid[-1:1, -1:1]
      print (x - yy).sum(), (y - xx).sum()
      
      x, y, z = np.meshgrid(np.arange(-1, 1), np.arange(-1, 1), np.arange(-1, 1))
      xx, yy, zz = np.mgrid[-1:1, -1:1, -1:1]
      print (x - yy).sum(), (y - xx).sum(), (z - zz).sum()
      
      x, y, z, w = np.meshgrid(np.arange(-1, 1), np.arange(-1, 1), np.arange(-1, 1), np.arange(-1, 1))
      xx, yy, zz, ww = np.mgrid[-1:1, -1:1, -1:1, -1:1]
      print (x - yy).sum(), (y - xx).sum(), (z - zz).sum(), (w - ww).sum()
      
      0 0
      0 0 0
      0 0 0 0
      
  • ogrid

    ogrid 与 mgrid 类似,也是类的对象而非函数,但和 mgrid 的不同之处在于,它返回的结果是 稀疏 的。

    import numpy as np
    
    x, y = np.mgrid[-1:2, 3:6]
    print 'x from mgrid:'
    print x
    print 'y from mgrid:'
    print y
    
    x, y = np.ogrid[-1:2, 3:6]
    print 'x from ogrid:'
    print x
    print 'y from ogrid:'
    print y
    
    x from mgrid:
    [[-1 -1 -1]
     [ 0  0  0]
     [ 1  1  1]]
    y from mgrid:
    [[3 4 5]
     [3 4 5]
     [3 4 5]]
    x from ogrid:
    [[-1]
     [ 0]
     [ 1]]
    y from ogrid:
    [[3 4 5]]
    
  • fromfunction

    fromfunction 的行为稍微有点不一样,它有三个参数:

    1. function
    2. shape
    3. dtype(optional)

    其中 function 的参数个数要和 shape 的长度一致,fromfunction 会对 shape 对应的多维数组中每个元素的坐标传给 function ,然后将返回值组合起来。

    import numpy as np
    
    def f(x, y):
        return (x, y)
    
    
    def g(x, y):
        return x + y
    
    
    x, y = np.fromfunction(f, (3, 3))
    print 'x generated by f is:\n', x
    print 'y generated by f is:\n', y
    print 'array generated by g is:\n', np.fromfunction(g, (3, 3))
    
    x generated by f is:
    [[ 0.  0.  0.]
     [ 1.  1.  1.]
     [ 2.  2.  2.]]
    y generated by f is:
    [[ 0.  1.  2.]
     [ 0.  1.  2.]
     [ 0.  1.  2.]]
    array generated by g is:
    [[ 0.  1.  2.]
     [ 1.  2.  3.]
     [ 2.  3.  4.]]
    

创建矩阵(二维数组)

  • eye:

    创建一个对角矩阵或者 super/sub diagional square matrix,且所指定的对角线上的元素值为 1.

    import numpy as np
    
    print np.eye(2)
    print np.eye(2, 3, k=1)
    print np.eye(2, 3, k=-1)
    
    [[ 1.  0.]
     [ 0.  1.]]
    [[ 0.  1.  0.]
     [ 0.  0.  1.]]
    [[ 0.  0.  0.]
     [ 1.  0.  0.]]
    
  • identity

    创建单位矩阵

    import numpy as np
    
    print np.identity(2)
    print np.identity(3)
    
    [[ 1.  0.]
     [ 0.  1.]]
    [[ 1.  0.  0.]
     [ 0.  1.  0.]
     [ 0.  0.  1.]]
    
  • diag

    创建对角矩阵或 super/sub diagional matrix。与 eye 的不同之处在于:

    1. 对角线上的元素值不是都为 1 ,而是手动指定
    2. 不需要制定矩阵的形状,而是靠指定对角线上元素值来确定矩阵的形状
    import numpy as np
    
    print np.diag([1, 1, 1])
    print np.diag([3, 4], 1)
    
    [[1 0 0]
     [0 1 0]
     [0 0 1]]
    [[0 3 0]
     [0 0 4]
     [0 0 0]]
    
  • diaglat

    对输入进行 flatten 然后用之创建对角矩阵

    import numpy as np
    
    print np.diagflat([[1, 1], [1, 1]])
    
    [[1 0 0 0]
     [0 1 0 0]
     [0 0 1 0]
     [0 0 0 1]]
    

词形还原工具对比

2016年1月21日 08:00

注: 本文仅讨论英文

词形还原(Lemmatization)

在英文当中,一些词会在不同的情况中有不同的形态:名词有单复数,动词有时态和语态,形容词有比较级。而在自然语言处理的一些场景中,这种形态的差异是没有意义的甚至有干扰作用,需要将不同形态的词转换为其 原型 ,这种处理就称为 "词形还原(Lemmatization/Lemmatisation)" ,得到的原型被称为 "词元(Lemma)" 。

举栗子:

  1. dogs -> dog, cats -> cat
  2. doing -> do, done -> do
  3. better -> good

为什么说词的不同形态有干扰作用呢?这么说其实不太严谨,要结合实际应用来说才是有意义的,不妨以文档相似计算这个应用来看一看。

假设我们有三个文档,内容分别为:

D1: I work at home
D2: I was working at home
D3: I was sleeping at home

从表达的内容上来看,我们会认为 D1 和 D2 是更接近的,而 D2 和 D3 表达的是两件事情。接下来计算一下 Document-Term 矩阵

  D1 D2 D3
I 1 1 1
was 0 1 1
work 1 0 0
working 0 1 0
sleeping 0 0 1
at 1 1 1
home 1 1 1

然后计算三个文档之间两两的相似性,为计算简单,这里定义文档相似性为: 两个文档在表中对应的列中 相同位的个数 ,那么

  D1 D2 D3
D1 7 4 4
D2 4 7 5
D3 4 5 7

结果是: D2 和 D3 最相似 —— 这和预期的结果是不一致的。

如果对文档中每个词都进行词形还原,那么结果会不一样。

注意,词形还原(lemmatization)与词干提取(stemming)在一些词的表现上很相似,但它们是两个东西。

词形还原的工具

Python: NLTK

NLTK 是著名的 Python 自然语言处理包,其中包含了非常多的自然语言处理相关方法和工具,其中也包含了词形还原的功能。

NLTK 中的词形还原功能依赖 WordNet 语料和一个名为 averaged_perceptron_tagger 的词性标注集,所以在使用其词形还原方法前需要先下载相关的数据:

import nltk

nltk.download("wordnet")
nltk.download("averaged_perceptron_tagger")

使用方法:

from nltk.stem import WordNetLemmatizer

lemmatizer = WordNetLemmatizer()
print lemmatizer.lemmatize('dogs')
print lemmatizer.lemmatize('working', pos='v')

NLTK 里这个词形还原工具的一个问题是需要手动指定词性,比如上面例子中的 "working" 这个词,如果不加后面那个 pos 参数,输出的结果将会是 "working" 本身。

如果希望在实际应用中使用 NLTK 进行词形还原,一个完整的解决方案是:

  1. 输入一个完整的句子
  2. 用 NLTK 提供的工具对句子进行分词和词性标注
  3. 将得到的词性标注结果转换为 WordNet 的格式
  4. 使用 WordNetLemmatizer 对词进行词形还原

其中分词和词性标注又有数据依赖:

nltk.download("punkt")
nltk.download("maxent_treebank_pos_tagger")

给一个小 demo

from nltk.corpus import wordnet
from nltk import word_tokenize, pos_tag
from nltk.stem import WordNetLemmatizer


def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return None


def lemmatize_sentence(sentence):
    res = []
    lemmatizer = WordNetLemmatizer()
    for word, pos in pos_tag(word_tokenize(sentence)):
        wordnet_pos = get_wordnet_pos(pos) or wordnet.NOUN
        res.append(lemmatizer.lemmatize(word, pos=wordnet_pos))

    return res

Python: Pattern

Pattern 是一个 Python 的数据挖掘库,还实现了自然语言处理、机器学习以及数据可视化等一整套的功能,是一个非常强大的库, Github 上 4000 多个 star 呢。

Pattern 中的 en 模块是专门用于英文的自然语言处理的,实现了很多非常棒的方法,比如:

from pattern.en import quantify

print quantify(['goose', 'goose', 'duck', 'chicken', 'chicken', 'chicken'])
print quantify({'carrot': 100, 'parrot': 20})
print quantify('carrot', amount=1000)

再比如:

from pattern.en import comparative, superlative

print comparative('bad')
print superlative('bad')

嗯,跑题了……使用 Pattern 进行词形还原在形式上非常简单,如下所示:

from pattern.en import lemma

print lemma('working')

So easy!完全不用管什么词性标注之类的。

Python: TextBlob

TextBlob 是一个专注自然语言处理的 Python 库,集成了 NLTK 和 Pattern 的一些功能。其中的词形还原功能使用如下:

from textblob import TextBlob

blob = TextBlob('I was working at home')
print blob.words
for word in blob.words:
    print word.lemmatize()

结果并不是很理想,同 NLTK 一样,可以传入词性来使词形提高还原结果。

Tree Tagger

Tree Tagger 是一个命令行词性标注工具,词形还原是它附带的功能。

安装:

  1. 下载词性标注器: Linux / Mac OS-X
  2. 下载词性标注 脚本
  3. 下载安装工具: install-tagger.sh
  4. 下载需要处理的语言的参数文件: 英语
  5. 将上述下载好的资源放到同一个目录中,其中的压缩包不需要解压,直接执行 install-tagger.sh

    chmod +x install-tagger.sh && ./install-tagger.sh
    

完成上述操作后将会在当前目录中生成 bin 和 cmd 两个目录,其中是一些可执行程序或脚本,不多介绍。想要进行词形还原,以英语为例,可以这么做:

echo 'I was working at home' | tree-tagger-english

Tesseract: 训练(续)

2015年8月15日 08:00

续上篇: 《Tesseract:训练》。本篇主要解释一下 DAWG 文件的生成方法,并提供一个更易用的训练工具。

更易配置和使用的训练工具

Tesseract 自身提供的训练工具分为了多个不同的程序,用起来比较灵活,但说实话,要记住每个程序的使用方法还真是一件费力的事情。此外,为了得到较好的训练结果,往往会需要用多种字体进行训练,而不同的字体除了一两个参数(一般为字体名称、位置)不一样外,其他的过程、参数都是相似的,这里有大量的重复性操作。

为此我写了一个更好的训练工具,只要开始时在配置文件中设置好字体、文本等信息,然后就可以敲击几个简单的命令进行训练。当然了,与此同时,训练过程中的灵活性会受到一定的影响。

获取工具

工具放在我的 Github 项目中,项目地址为:

https://github.com/Linusp/zutil

项目 python 目录下的 tess_train.py 就是了

配置

见项目 python/configs/ 目录下的 tess.json,其内容为:

{
    "name": "chi_eng",
    "font": {
        "wqy": {
            "font-dir": "/usr/share/fonts/truetype",
            "sub-font": {
                "fixed": "WenQuanYi Micro Hei"
            }
        },
        "sun": {
            "font-dir": "/home/linusp/datas/fonts/",
            "sub-font": {
                "serif": "SimSun"
            }
        },
        "nsun": {
            "font-dir": "/home/linusp/datas/fonts/",
            "sub-font": {
                "serif": "NSimSun"
            }
        },
        "times": {
            "font-dir": "/home/linusp/data/fonts/TimesNewRoman",
            "sub-font": {
                "italic": "Times New Roman, Italic",
                "bold": "Times New Roman, Bold",
                "serif": "Times New Roman,"
            }
        }
    },
    "data": {
        "chi": {
            "file-name": "chi.txt",
            "font-list": ["wqy", "sun", "nsun"]
        },
        "eng": {
            "file-name": "eng.txt",
            "font-list": ["wqy", "times"]
        }
    }
}

可以看到,配置文件内容分为三大块:

  • name: 即最后生成的 traineddata 文件名称
  • font: 要使用到的字体
  • data: 要用于训练的文本,以及它们各自要使用的字体

name 和 data 这两块都比较易懂,这里只对 font 这一块多做一点说明。

以其中的 times 为例:

"times": {
    "font-dir": "/home/linusp/data/fonts/TimesNewRoman",
    "sub-font": {
        "italic": "Times New Roman, Italic",
        "bold": "Times New Roman, Bold",
        "serif": "Times New Roman,"
    }
}

"times" 是这个字体的名称,用于区别其他字体,在训练过程中生成中间结果时也会用到。需要注意的是,一个名称不一定是只对应 一个字体文件 ,因为种字体除了它的普通形式外,还有斜体、粗体等不同样式,这些不同的样式可能是通过不同的字体文件来表现的。

"font-dir" 就是字体文件所在的目录了,如上,如果要使用同一种字体的多种样式且它们在不同的字体文件中,请将它们放置到相同的目录下。

"sub-font" 指定需要使用的字体样式,可用的有五种,分别是:

  • italic: 斜体
  • bold: 粗体
  • serif: 衬线体
  • fixed: 无衬线体
  • fraktur: 哥特体

serif 和 fixed 都是字体的普通样式,但是这两者是只能有其一的,这是字体自身设计决定的。

每种字体样式对应的 真实字体名称 ,可以用 text2image 来的到:

text2image --fonts_dir=/home/linusp/data/fonts/TimesNewRoman --list_available_fonts

训练

在设置好配置文件后,就可以方便地使用 tess_train.py 进行训练了, tess_train.py 的使用方法是:

./tess_train.py -c <conf file> [-a <action>]

不使用 -a 选项时,会认为是一个全新的训练过程,并将处理整个训练过程,通过 -a 选项可以指定进行训练过程中的某个子步骤,以便更灵活地控制训练过程。可以使用的 <action> 有:

  • tifbox: 用配置文件中 "data" 部分指定的文本生成图像和 BOX 文件
  • charset: 提取字符集信息并生成字符集文件
  • fontprop: 生成字体信息文件
  • feature: 用之前生成的图像和 BOX 文件提取每个字符的特征
  • cluster: 从特征文件产生每个字符的 原型
  • combine: 将中间文件打包成 traineddata

字符识别歧义校正

针对单个字符的识别,所谓歧义,包含以下三种情况(以下A, B皆为代指):

  1. 将字符 A 识别作字符 B
  2. 将一个字符 A 识别多个字符
  3. 将多个字符识别作一个字符 A

第一种情况就是典型的误分类情况,其后两种情况则和字符分割不正确有关系。

对于这三种情况,Tesseract 提供了途径,以便在后处理时对识别结果进行校正。方法是在 traineddata 中添加 unicharambigs 这个 歧义校正文件

该文件有两种格式,旧版本格式在文件首行用 v1 标识,对应的新版本的则用 v2 标识。

注意: 歧义校正文件应编码为 UTF-8

旧版本格式

不管是哪个版本,unicharambigs 文件都是以文本文件的形式存在,并且每行一条校正规则。

在旧版本格式中,每条校正规则是这样的:

<字符数量> <字符串> <校正后的字符数量> <校正后的字符串> <是否强制替换>

最后一个字段如果为 0,则表示匹配到该规则时,不会进行强制替换,只是提供 "建议" ;为 0 时则进行强制替换。

比如说如果 unicharambigs 文件中内容为:

v1
2 ' ' 1 " 1
1 m 2 r n 0
3 i i i 1 m 0

从前到后三条规则的含义分别为:

  1. 识别结果中两个连续的单引号(')应当被强制替换为双引号(")
  2. 识别结果中的字符 m 有可能是 rn
  3. 识别结果中的连续字符串 iii 可能是字符 m

新版本格式

新版本格式更为友好一点,每条校正规则为:

<字符串> <字符串> <是否强制替换>

上面那个例子可以写作:

v2
'' " 1
m rn 0
iii m 0

需要注意的是,新版本格式里的两个字符串 内部不能有空格

词典: DAWG 文件

上篇中提到有 DAWG 文件可以增强、调整识别结果,这些 DAWG 文件被称为词典(dictionary),使用来对识别结果进行后处理的。

所谓后处理是这样的: 对没一个字符,都会有多个候选结果,如果每次都挑选候选结果中最好的结果,不一定是正确的,这是因为实际的数据中会有各种各样的干扰信息,所谓的 "最好结果" 并不一定是正确的结果。在 假设 待识别的文本都是由正常的、 拼写正确的 词组成的时,通过预定义的词典,就可以 以词为单位 对识别结果进行校正。

在 Tesseract 中,DAWG 文件就是作为词典用在后处理中的,自然也应该是从训练用的文本中进行统计得到的。

Tesseract 中支持的 DAWG 文件有五种类型,分别是:

  1. word-dawg

    这个词典文件记录可用的词,每个词占一行。

  2. freq-dawg

    这个词典文件记录最常用的一些词,建议数量在 这个量级。

  3. bigram-dawg

    这个词典记录二元词,同样每个二元词一行。

    所谓的二元词是由前后连续的两个词组成的一个单元,比如说

    生存 还是 死亡 这 是 一个 问题
    

    这句话里的二元词就有:

    生存 还是
    还是 死亡
    死亡 这
    这 是
    是 一个
    一个 问题
    

    其实这已经近似一个二元语言模型(2-Gram Language Model)了,只不过这里不记录词频等信息。

    另外一点要注意的是,在 bigram-dawg 中,二元词之中的数字统一用英文的问号(?)替代

  4. number-dawg

    这个词典用来记录包含数字的词,同样每个词一行,但和 word-dawg 不一样的是,这里记录的词里面的数字,都要用空格替代。

  5. punc-dawg

    这个文件用来记录标点的 模式 ,所谓模式就是说标点在训练用的文本(或语言)中是如何使用的。描述不太直观,下面是从 Tesseract 的英文 traineddata 中 punc-dawg 提取出来的的一段,可以参考一下:

    [
    [ ]
    [ ],
    [ ];
    [ ]:
    [ ]!
    [ ]!!!
    [ ]?
    [ ]?!
    [ ]???
    [ ].
    [ ]...
    {
    { }
    { },
    { };
    { }:
    { }!
    { }!!!
    { }?
    { }?!
    { }???
    { }.
    { }...
    

这五个文件的名称必须是 word-dawg, freq-dawg, bigram-dawg, number-dawg 和 punc-dawg,然后和其他所有用于打包 traineddata 的中间结果一样,应当加上前缀,如要生成名为 eng.traineddata 的最终结果,这五个文件必须是 eng.word-dawg, eng.freq-dawg, eng.bigram-dawg, eng.number-dawg 和 eng.punc-dawg

另外,这五个文件必须是 DAWG 格式,而不是文本文件。一般是先从训练文本中生成对应的文本文件,然后用 Tesseract 提供的 wordlist2dawg 来进行转换,如将 word-list-last 转换为 eng.word-dawg

wordlist2dawg word-list-last eng.word-dawg eng.unicharset

注意: 以上文件都应编码为 UTF-8 再转换为 DAWG 文件

深度学习框架 Caffe 的安装与基本使用

2015年7月21日 08:00

Caffe 简介

Caffe 是一个 C++ 编写的深度学习框架,原来是 UC Berkeley 博士毕业的贾扬清的个人项目,后来被他开源。由于其清晰与高效而被广泛使用,用户逐渐地也形成了一个开放的社区,各方的一些重要的研究成果(主要是各种模型)都被分享到社区中,这是非常棒的一点。

Caffe 的清晰表现在网络结构与参数都独立于代码,用户只要以普通文本(但需遵循一定的简单格式)就可以定义好自己的神经网络,并按自己的需要进行调整。而其高效体现在对 CUDA 的支持,GPU 运算能极大地提高运算速度,同时提供了在 CPU 模式和 GPU 模式之间切换的简便方法。

另外,代码也写得很漂亮!

Caffe 的安装

Caffe 依赖以下外部的库或者工具:

  1. CUDA: NVIDIA 公司发布的并行计算框架,通过利用 GPU 的处理能力来大幅提升计算性能。由于需要数据的量很大以及 DNN 本身的结构复杂性,DNN 的训练过程通常会非常非常慢,如果能够利用 CUDA 能极大地提高效率。

    来块 TITAN 吧少年!

    gtx690-1.jpg

    gtx690-2.jpg

    gtx690-3.jpg

  2. BLAS: 被广泛使用的线性代数库
  3. Boost(>=1.55): 著名的 C++ 第三方库
  4. OpenCV(>=2.4): 著名的计算机视觉库
  5. Google 开源的一套东西:
    • protobuf: 数据序列化框架
    • gflags: 命令行参数解析库
    • glog: 日志记录框架
  6. IO 相关的库
    • hdf5
    • leveldb
    • snappy
    • lmdb

以上依赖中可以从系统软件源安装的有 2, 5 中除 glog 外的其他两个, 6。在 Debian 上使用以下命令进行安装:

sudo apt-get install libatlas-dev libblas-dev libatlas-base-dev libatlas3-base  # BLAS
sudo apt-get install libprotobuf-dev protobuf-compiler libgflags-dev
sudo apt-get install libhdf5-dev libleveldb-dev libsnappy-dev liblmdb-dev

Boost 虽然也能从软件源安装,但需要注意版本,如果版本过低还是需要进行编译安装。

glog 会在 Caffe 安装的时候自动安装,所以需要手动安装的就剩下:

  1. CUDA
  2. Boost
  3. OpenCV

CUDA 的安装笔者没有经验,就不作展开了,另外两个通过源代码编译安装即可。OpenCV 建议选择稳定版本 2.4.10。

解决上述依赖问题后进行编译安装即可。

cd caffe && mkdir build
cd build
cmake ..
make && make install

上述命令将会把 Caffe 安装到 caffe/build/install 目录下,如果需要安装在其他位置,可以在执行 cmake 时附加参数 "CMAKE_INSTALL_PREFIX":

cmake -DCMAKE_INSTALL_PREFIX=/opt/caffe ../

注意 cmake 输出中的 "Install path" 以确认是否生效。

如果权限允许,比较方便的方法是安装到 /usr/local 下面,这样相应的头文件、库文件都是在系统的查找路径中,在使用 Caffe 的 API 时就无需再另外指定头文件位置和链接参数。

此外 Caffe 还提供了 Python 的接口,在安装目录中有一个 python 目录,将其加入到环境变量中即可在 Python 项目中使用:

export PYTHONPATH=/opt/caffe/python:$PYTHONPATH

当然了,这个 python 接口也有一些依赖需要安装,这些依赖在 python 目录下的 requirements.txt 中设置,使用 pip 即可进行安装:

sudo pip install -r requirements.txt

其中一个依赖 scipy 需要 fortran 编译器:

sudo apt-get install gfortran

至此安装结束,此时可用的资源有:

  1. C++ 库,包括头文件、动态链接库(或静态链接库)
  2. Python 接口
  3. 命令行工具,位于安装目录的 bin/ 目录下面

相关概念

Blob

Blob 是用于存储数据的对象,在 Caffe 中各种数据(图像输入、模型参数)都是以 Blob 的形式在网络中传输的。同时 Blob 还能在 CPU 和 GPU 之间进行同步以支持 CPU/GPU 的混合运算。

Layer

Layer 是网络的次级单元,也是 Caffe 中能在外部进行调整的最小网络结构单元 —— 一般来说,都让同一层的神经元具备相同的性质,因此也就没有必要提供对单个神经元的操作。

每个 Layer 都会有输入的 Blob 和输出的 Blob。

Net

即一个完整的包含输入层、隐藏层、输出层的深度网络,在 Caffe 中一般是一个卷积神经网络(Convolution Neural Networ, CNN)。

通过定义不同类型的 Layer,并用 Blob 将不同的 Layer 连接起来,就能产生一个 Net 。

使用 Caffe 进行训练

这里以 MNIST 的例子来进行说明。

训练参数设置

训练参数通常记录到一个统一的文件中,关键参数有:

  • 训练(及测试)使用的网络

    网络结构要在另外一个文件中定义,在配置文件中用 "net" 指定其位置,如:

    net: "examples/mnist/lenet_train_test.prototxt"
    
  • 学习率等网络迭代参数

    Caffe 中的权值更新通过学习率和动量项来进行计算

    base_lr: 0.01        # 初始学习率
    momentum: 0.9        # 动量项
    

    同时还有 "lr_policy" 等参数用于学习率的自适应优化:

    lr_policy: "inv"
    gamma: 0.0001
    power: 0.75
    

    此外还可以通过 "weight_decay" 来设置权重衰减

    weight_decay: 0.0005
    
  • 训练迭代次数

    设置最大迭代次数:

    max_iter: 10000
    
  • 训练模式(CPU/GPU)

    如果系统安装了 CUDA 支持 GPU 运算,那么 GPU 模式是更好的选择,否则应当选择 CPU 模式:

    solver_mode: CPU
    

除以上参数外,还有用于测试的一些参数:

  • test_iter: 测试次数
  • test_interval: 两次测试之间的间隔

在 MNIST 例子中,配置文件为 examples/mnist/lenet_solver.prototxt,其内容为:

# The train/test net protocol buffer definition
net: "examples/mnist/lenet_train_test.prototxt"

# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
test_iter: 100

# Carry out testing every 500 training iterations.
test_interval: 500

# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005

# The learning rate policy
lr_policy: "inv"
gamma: 0.0001
power: 0.75

# Display every 100 iterations
display: 100

# The maximum number of iterations
max_iter: 10000

# snapshot intermediate results
snapshot: 5000

snapshot_prefix: "examples/mnist/lenet"

# solver mode: CPU or GPU
solver_mode: GPU

网络结构定义

如前所述,Net 由 Layer 组成,每个 Layer 的定义都是以下形式:

layer {
  name: "<name>"
  type: "<type>"
  bottom: "<input blob>"
  top: "<output blob>"
  transform_param {
    ...
  }
  <prefix>_param {
    ...
  }
}

可用的 Layer 类型及其参数见 Caffe|Layer catalogue

层与层通过输入和输出联系在一起,比如说定义一个两层的前馈网络,可以这样:

layer {
  name: "input"
  type: "Data"
  top: "data"
}
layer {
  name: "output"
  type: "SoftmaxWithLoss"
  bottom: "data"
  top: "loss"
}

"input" 层的输出 Blob 是 "data" ,同时 "data" 这个 Blob 又是 "output" 层的输出,这两个层就这样被连接起来了。

在 MNIST 例子中,网络结构定于于 examples/mnist/lenet_train_test.prototxt 中,由于其内容较长,这里就不详细写出来了,仅以图示之。

TODO: 图

数据准备

在 MNIST 的例子中,需要将 MNIST 数据集转换为 lmdb 格式。先来看看 MNIST 数据集的处理再谈一下普通的数据如何转换成可用于训练的数据。

首先要获取数据,这都已经在 Caffe 的源代码包中提供了相应的工具:

  • data/mnist/get_mnist.sh
  • examples/mnist/create_mnist.sh

第一个脚本用于下载 MNIST 数据集,阅读一下脚本内容,发现不过就是几条 wget 命令而已:

wget http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
wget --no-check-certificate http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
wget --no-check-certificate http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
wget --no-check-certificate http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz

第二个脚本用于将下载的 MNIST 数据集转换为 lmdb 文件,脚本内容如下,也是比较简单。

EXAMPLE=examples/mnist
DATA=data/mnist
BUILD=build/examples/mnist

BACKEND="lmdb"

echo "Creating ${BACKEND}..."

rm -rf $EXAMPLE/mnist_train_${BACKEND}
rm -rf $EXAMPLE/mnist_test_${BACKEND}

$BUILD/convert_mnist_data.bin $DATA/train-images-idx3-ubyte \
                              $DATA/train-labels-idx1-ubyte $EXAMPLE/mnist_train_${BACKEND} --backend=${BACKEND}
$BUILD/convert_mnist_data.bin $DATA/t10k-images-idx3-ubyte \
                              $DATA/t10k-labels-idx1-ubyte $EXAMPLE/mnist_test_${BACKEND} --backend=${BACKEND}

echo "Done."

对于我们自己的数据,一般来说是一堆图片,Caffe 也提供了 convert_imageset 这个工具,这是安装 Caffe 后提供的命令行工具之一。该命令的使用方式为:

convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME

其中 LISTFILE 每行记录一张图片的(相对)路径及类别,如:

pics/0_1.png 0

需要注意的是,这里的 "类别" 必须是一个整数 。ROOTFOLDER 和 LISTFILE 中图片的路径构成图片的真实路径,DB_NAME 是要创建的 lmdb 文件的名称。

如果图片集中存在图片大小不一致的情况,可以用 -resize_height 和 -resize_width 来将所有图片规整成统一的尺寸。

convert_imageset -resize_height 64 -resize_width 64 ./ pic_label.list mydb

训练与测试

在以上步骤都完成后即可开始进行训练与测试,使用命令行工具中的 caffe 命令即可:

caffe train --solver=lenet_train_test.prototxt

在训练过程中会观察到类似下面这样的输出:

I1203 solver.cpp:204] Iteration 100, lr = 0.00992565
I1203 solver.cpp:66] Iteration 100, loss = 0.26044
...
I1203 solver.cpp:84] Testing net
I1203 solver.cpp:111] Test score #0: 0.9785
I1203 solver.cpp:111] Test score #1: 0.0606671

这些输出表明了学习率、loss 和测试数据在当前网络上的表现,这些输出都是需要注意的。举个栗子

chesnut.jpg

如果在开始时学习率设置过大,导致训练过程不收敛了,是能够通过观察 loss 和 测试数据在当前网络上的表现判断出来的。

在 MNIST 的例子中,最后训练生成的模型是: examples/mnist/lenet_iter_10000.caffemodel。这个模型可以在之后的分类中使用。

Shell 中的随机数生成方法

2015年7月10日 08:00

昨天写了个脚本,用 ImageMagick 生成单个字符的图片,并随机为其附加模糊、噪声、旋转、扭曲等效果,这个脚本生成的图片是准备用来测试 DNN 的。目前来看的话,用 ImageMagick 生成的图片并不完全理想 —— 不过还能凑合着用。总之,为了生成尽量多尽量丰富的样本,我希望各种效果的作用程度是随机的,而且应用哪些效果也是随机的。毫无疑问这里是需要使用随机数的了。

当然了,本文提到的“随机数”实质上都是伪随机数,毕竟计算机只能模拟这一过程。

最后我选择的是使用 shuf 这个工具,不过在解决这个问题的过程中也总结了不止一种办法,在这里分享一下。

$RANDOM

这里 讲, RANDOM 是 Bash 的一个内建函数(而不是常量),会返回一个在区间 [0, 32767] 内的整数。

echo $RANDOM
# 4230

使用这个方法时,可以通过设置相同的 seed 来得到相同的随机数,当然也可以反过来用不同的 seed 来产生不同的随机数。

RANDOM=10
echo $RANDOM
# 4230

设置相同的 seed 后,可以观察到连续使用 $RANDOM 生成的随机数序列都是一样的。实际情况中我们可能希望生成的“随机数”尽量的不同,我们可以将时间转换为数字来设置 seed:

function random_with_time{} {
    RANDOM=$(date +%s)
    echo $RANDOM
}

不过需要注意的是,由于这里 date 命令的输出是精确到秒,seed 的值在 1 秒内是不变的,产生的随机数值也会相同。而在不设置 seed 的情况下,RANDOM 的输出在 1 秒内是会变化的,这里只是为了说明而举个例子而已。

如果需要产生特定范围内的随机数,可以这么写:

# @args <beg> <end>
# return random integer in [<beg>, <end>)
function random_range() {
    local beg=$1
    local end=$2
    echo $((RANDOM % ($end - $beg) + $beg))
}

/dev/random

/dev/random 是 Linux 上的一个字符设备,里面会源源不断地产生随机数,只要进行读取就行了。一般来说,用 od 命令即可:

od -An -N2 -i /dev/random
# 13176

这里的 -N2 指定要读取的字节数, -i 则是指定输入。

若要产生特定范围内的随机数,则和使用 $RANDOM 的方法类似:

# @args <beg> <end>
# return random integer in [<beg>, <end>)
function random_range() {
    local beg=$1
    local end=$2
    echo $(($(od -An -N2 -i /dev/random) % ($end - $beg) + $beg))
}

seq + sort

sort 命令有一个 -R 选项,可以根据随机 hash 排序,那么我们就可以用 seq 命令先生成一个整数序列,然后用 sort 的 -R 选项处理取其中一行即可。

# @args <beg> <end>
# return random integer in [<beg>, <end>]
function random_range {
    local beg=$1
    local end=$2
    seq $beg $end | sort -R | head -n1
}

值得注意的是,使用这种方法时,要求的值域可以包含负数区域,而之前的两种方法则要进行不同的处理。

shuf

shuf 和 'sort -R' 的作用类似,用来根据输入生成随机序列:

# @args <beg> <end>
# return random integer in [<beg>, <end>]
function random_range {
    shuf -i $1-$2 -n1
}

在各种方法中,使用 shuf 命令是最简洁的,因此就选择了这种方法。

GDB调试技巧(续)

2015年6月12日 08:00

以下是这段时间阅读 Tesseract 源代码时所学习并实践过的一些 GDB 调试技巧,和 前一篇 一样,我也认为这些是比较实用的(虽然对别人来说未必)。

另外,我觉得阅读源代码的话,最好的办法莫过于进行调试,深入其内部的处理流程 —— 当然如果作者有提供完善的文档的话可以省下很多的精力。

设置环境变量

这个其实倒和 GDB 本身没多大关系,一般来说在 shell 里启动 GDB 的话,是能够正常使用用户的环境变量的,不过作为一个 Emacser ,我更喜欢在 Emacs 里用 gud-gdb 来进行调试,Emacs 会为 GDB 提供一个更易用的用户界面,但问题是这种情况下自己设置的一些环境变量都没有被读取。

在调试 Tesseract 的时候,程序要使用一个外部的环境变量 TESSDATA_PREFIX ,于是就出现问题了。

解决这个问题的方法是用 set 命令在 GDB 里设置环境变量:

set environment TESSDATA_PREFIX=/home/linusp/data/tessdata

当然了 set 命令还可以用来设置、修改变量的值,是非常有用的。

修改、设置程序中的变量

有两种办法,一种是使用 set 命令,另外一种是使用 print 命令。

set 命令:

set variable i=0

或者先得到待设置的变量的地址(用 print 命令),然后根据地址来修改值:

set *((int *) 0xbfbb0000) = 20

print 命令:

print i=0

查看变量的类型

使用 whatis 命令可以得到变量的类型,如果变量的类型是一个复合类型而非 C/C++ 的基础类型,则可以通过 ptype 命令来查看该类型的定义。

gdb_show_type.png

调用函数

在调试的时候可以通过 call 命令来调用一些函数。在调试 Tesseract 时,它使用的图像库 Leptonica 提供了一个显示图像的函数 pixDisplay ,让我能够用 call 命令来调用它查看每一步处理结束后图像变成了什么样子,实在是帮了大忙了。

除了辅助调试外,不妨将思维发散一下,用 call 命令可以调用函数,而 print 命令则能执行一些非函数调用的普通语句(不过控制语句无法被执行),结合这两者我们就有了一个几乎是交互式的 C/C++ 运行环境了。

百度 OCR API 的使用以及与 Tesseract 的简单对比

2015年6月10日 08:00

百度 OCR API 初探

近日得知百度在其 APIStore 上开放了 OCR 的 API,目前以 即用API 的形式试运行,所谓 "即用" 指可立即调用、无需注册付费,但也加上了有使用次数这么一个限制。

目前该 API 的文档很少,不过接口和参数都在其 API页面 进行了说明,要用起来还是没有问题的。下面是接口的信息

相关的参数则有

参数名 类型 必填 参数位置 描述 可用值
fromdevice string yes bodyParam 来源设备 android, iPhone, pc
clientip string yes bodyParam 客户端出口ip  
detecttype string yes bodyParam OCR服务类型 LocateRecognize, Recognize, Locate, SingleCharRecognize
languagetype string yes bodyParam 待检测的文字类型 CHN_ENG, ENG, JAP, KOR
imagetype string yes bodyParam 图片资源类型 1: 经过BASE64处理的字符串; 2: 图片源文件
image string yes bodyParam 图片资源,300K以下  
apikey string yes header API 密钥  

返回的结果会是一个 JSON 字符串,如下所示:

{
    "errNum": 0,
    "errMsg": "success",
    "querySign": "3845925467,2370020290",
    "retData": [
        {
            "rect": {
                "left": "0",
                "top": "0",
                "width": "33",
                "height": "31"
            },
            "word": "  8"
        }
    ]
}

各字段的意义如下:

  • errNum

    标识处理是否成功, 0: 表示成功, 其它值, 表示失败。在 API页面 上可以查看更详细的错误码列表。

  • errMsg

    错误类型说明,当服务调用成功时为字符串 'success'

  • querySign

    本次请求用户传递原图或rl的唯一标示, 方便定位问题

  • retData

    返回内容集合

  • rect

    该行文字所在的矩形区域的信息

  • word

    该行所识别出的文字

用 Python 调用百度 OCR API

在 API 页面上倒是提供了 Python 的调用示例,不过个人感觉比较丑,居然还在使用 urllib !我们完全可以用 requests 来做这些事情,下面是我写的一个方法:

# -*- coding: utf-8 -*-
import requests

URL = 'http://apis.baidu.com/apistore/idlocr/ocr'
LANG_LIST = ['CHN_ENG', 'ENG', 'JAP', 'KOR']


def ocr(picture, lang='CHN_ENG'):
    """Recognize a picture and return the text on it.

    picture could be a local picture or url of picture on web.

    lang should be one of CHN_ENG, ENG, JAP, KOR
    """
    data = {}
    data['fromdevice'] = "pc"
    data['clientip'] = '10.10.10.0'
    data['detecttype'] = 'Recognize'
    data['imagetype'] = "2"

    if lang not in LANG_LIST:
        raise Exception('invalid language: %s' % lang)
    else:
        data['languagetype'] = lang

    # 此处应使用自己的 API key
    header = {"apikey": "your api key"}

    image_file = None
    try:
        if picture.startswith('http://') or picture.startswith('https://'):
            image_file = requests.get(picture).content
        else:
            image_file = open(picture, 'rb').read()
    except Exception:
        raise Exception('invalid picture: %s' % picture)

    resp = requests.post(URL, headers=header, data=data, files={"image": ("baidu.jpg", image_file)})

    if resp is not None:
        resp = resp.json()
        if  int(resp.get('errNum')) != 0:
            raise Exception(reps.get('errMsg'))
        else:
            return resp.get('retData')[0].get('word')
    else:
        return None

需要注意的是,虽然 API 页面上说图片目前仅支持 jpg 格式,但实际上对图片格式的检查很有可能是通过检查文件名的后缀来进行的,所以通过将 "imagetype" 设置为 2 并将图片以 jpg 作为后缀名进行上传来绕过这个限制。

然后拿一张图片来看看!好,就是下面这张了:

ocr_test_002.jpg

识别结果如下:

ocr_test_002_result.png

与 Tesseract 的简单对比

要比较全面、客观地对比出两个不同的产品的性能是需要大量的数据和详尽地设计的,这里只是根据我个人对 OCR 系统的认识,使用了少量的数据进行的对比,至于两者孰优孰劣请读者自行试验再下结论。

图一:

ocr_test_001.jpg

百度 OCR API 识别结果:

讨论代数方程r+积—劲—1=0在(—”0)内实根的情况

Tesseract 识别结果:

讨论代数方程娱+4筹-3χ-l=O在 (-∞,O〉 内实根的情况₋

图二:

ocr_test_002.jpg

百度 OCR API 识别结果:

L(理)已知:是虚数单位.则(―1―D《2―D=一一一一f》    A―3+iB―1―3  C―3+3D―1―1

Tesseract 识别结果:

L₍₋₎已概ᵢ量ᵦ效单位。口(—₁针)(2₋E,= ⋯烟~₋ (
A₋₋₃+ᵢ BL-{•S宜

 C.-3+3i D·一l震拿

罐震 ,△、an.△ᵅ=₂₋!₋₃—?又₋T=xf —$⩽ᵣ⩽玉 ₋l S校 r= —

图三:

ocr_test_003.jpg

百度 OCR API 识别结果:

新课标全国)在一组样本数据(Xl,y)G,y)%不全相等的散点图中,若所有样本点ki,y4+|上,则这组样本数据的样本相关系数为0      Boc.号

Tesseract 识别结果:

!新课标全国)在岗组样本数据Uh趴汕懒h…
蘑. 不全相等)的散点图中,若所有样本点牺酬

L篆+I 上,则这组样本数据的样本相关系数为 o

1
B.0 时

图四:

ocr_test_004.jpg

百度 OCR API 识别结果:

11分)如图271日.在△A风中,∠C=45·现妹10.高AD=8.矩形EFPQ的一边吐在风边上,1

Tesseract 识别结果:

.’⁻.∴ 、,〉 、、.罐’∣【




r\∪【川 '′ ∣⋃./L 、∥('中.
"pQ的

图五:

ocr_test_005.jpg

百度 OCR API 识别结果:

B

Tesseract 识别结果:

乐散说, 你是找的全部
你是耕掌希宁触-都余
但找的主命宁
猪漆能装有你

就傅
她环东能玻节玻引才
A美未能表有童气
大她东能装有四兔

从以上这些对比,我得到以下一些 不一定客观 的结论:

  1. 百度的 OCR 系统对噪声、模糊等有较强的抗干扰性,这一点上要优于 Tesseract
  2. 百度的 OCR 系统没有进行布局分析,但 Tesseract 在布局分析上做得比较好
  3. 百度的 OCR 系统 可能黑底白字 的图像识别不好

Tesseract源代码阅读:字符串 STRING

2015年5月11日 08:00

STRING 类是 Tesseract 中自定义的字符串类,封装了一些字符串操作,该类定义于 ccutil/strngs.h 中,同样的,不知道出于什么目的,其设计让人一开始摸不着头脑。

按理来说,如果要实现一个字符串类,那么其内部应该要有一个保存字符串内容的数据成员,这个 STRING 类确实有数据成员,不过其类型是内部定义的一个结构 STRING_HEADER:

class TESS_API STRING {
public:
    // ....

private:
    typedef struct STRING_HEADER {
        int capacity_;

        // used_ is how much of the capacity is currently being used,
        // including a '\0' terminator.
        // if used_ is 0 then string is NULL(not even the '\0')
        // else if used_ > 0 then it is strlen() + 1 (because it includes '\0')
        mutable int used_;
    } STRING_HEADER;

    STRING_HEADER *data_;

    // ....
};

可以看到这个数据成员中并没有保存字符串内容的部分。

STRING 类中有两个方法,返回的是一个 const char * 类型的值,这两个方法的名称为 string()c_str() ,从这两点上来看,可以认为这两个方法是将 STRING 类内部存储的字符串内容转换为 const char * 类型返回了出来,那么可以从这两个方法去入手来发掘这个类的秘密。

这两个方法的实现都很简单:

  • string() 方法

    const char* STRING::string() const {
        const STRING_HEADER* header = GetHeader();
        if (header->used_ == 0)
            return NULL;
    
        // mark header length unreliable because tesseract might
        // cast away the const and mutate the string directly.
        header->used_ = -1;
        return GetCStr();
    }
    
  • c_str()

    const char* STRING::c_str() const {
        return string();
    }
    

c_str() 方法是 string() 方法的别名,而 string() 方法中返回的结果是 GetCStr() 这个方法的结果,顺藤摸瓜找到这个方法,发现它的实现如下:

inline const char* GetCStr() const {
    return ((const char *)data_) + sizeof(STRING_HEADER);
};

也就是说,STRING 类把字符串内容放在了 data_ 后面的那个地址空间上,不过这段空间是匿名的,只能通过上面这样的方式进行访问。

到底是出于何种考虑进行这样的设计,我也无从得之,但我个人是对这个设计不太赞同的。要说有什么优点,就是在进行内存分配时少了一步操作 —— 在 STRING::AllocData 中是这样进行内存分配的:

char *STRING::AllocData(int used, int capacity) {
    data_ = (STRING_HEADER *)alloc_string(capacity + sizeof(STRING_HEADER));

    STRING_HEADER *header = GetHeader();
    header->capacity_ = capacity;
    header->used_ = used;

    return GetCStr();
}

另外,上面这个方法里的 GetHeader() 方法返回的就是 data_ ,直接这样我想也是可以的:

data_ = (STRING_HEADER *)alloc_string(capacity + sizeof(STRING_HEADER));

data_->capacity_ = capacity;
data_->used_ = used;

return GetCStr();

在 STRING 类封装的方法里,大部分在 C/C++ 中已有对应的字符串操作。

Tesseract源代码阅读:单链表 ELIST

2015年5月6日 08:00

因为工作以及个人兴趣原因,已经阅读了一段时间 Tesseract 的源代码了(Tesseract 的介绍见我的另一篇文章)。从这篇文章开始,会不定期地将阅读笔记整理好发布出来。

ELIST 是 Tesseract 中的单链表基类, Tesseract 的核心处理流程中有不少不同类型的单链表类都继承自 ELIST ,因此了解 ELIST 的具体实现是很有必要的。

虽然这里先提到 ELIST ,但实际上这个单链表由三个类组成,它们分别是:

  • ELIST_LINK: 链表节点
  • ELIST: 链表,保存了链表头节点
  • ELIST_ITERATOR: 封装了对链表的具体操作

这三个类都定义于 ccutil/elst.h 中

ELIST_LINK

该类的定义为:

class DLLSYM ELIST_LINK
{
    friend class ELIST_ITERATOR;
    friend class ELIST;

    ELIST_LINK *next;

public:
    ELIST_LINK() {
        next = NULL;
    }

    //constructor
    ELIST_LINK(                 //copy constructor
        const ELIST_LINK &) {   //dont copy link
        next = NULL;
    }

    void operator= (            //dont copy links
        const ELIST_LINK &) {
        next = NULL;
    }
};

这个类实际被作为链表的 "节点"。

可以看到,这个类的定义异常简单,数据成员只有一个同类型的指针,也没有定义除构造方法外的其他方法。而且在拷贝构造函数和赋值运算符重载中,直接忽略了传入的参数,从注释里也能明白,作者是将 "拷贝" 这个行为直接禁止了 —— 如果不显式定义拷贝构造函数的话,编译器会生成一个默认的拷贝构造函数,里面还是会进行拷贝操作的,赋值运算符重载同理。

不过令人疑惑的是,为什么这个 "节点" 类型里没有保存节点数据的成员呢?这个待会再说,不妨先保留着疑问吧。

ELIST

该类的定义如下(出于简洁考虑,这里没有列出它内部的一些方法):

class DLLSYM ELIST
{
    friend class ELIST_ITERATOR;

    ELIST_LINK *last;           // End of list
    //(points to head)
    ELIST_LINK *First() {       // return first
        return last ? last->next : NULL;
    }

public:
    ELIST() {
        last = NULL;
    }

    bool empty() const {
        return !last;
    }

    // ......
};

正如之前所说,ELIST 中记录了链表的头节点,同时还定义、实现了一些链表层面的操作方法。

令人比较在意的是,ELIST 中表示头节点的成员的名称为 "last" ,而且名为 "First" 的私有方法也是返回这个头节点的 下一个节点 ,脱离上下文来看的话,可能会觉得别扭。不过其实 ELIST 实现的是一个单向循环链表,这里的 "last" 确实是指向了最后一个节点,而这最后一个节点又指向了第一个节点。

ELIST_ITERATOR

顾名思义,该类是 ELIST 类的 "迭代器" ,可以用来对 ELIST 进行遍历操作。该类的定义为:

class DLLSYM ELIST_ITERATOR {
    friend void ELIST::assign_to_sublist(ELIST_ITERATOR *, ELIST_ITERATOR *);

    ELIST *list;
    ELIST_LINK *prev;
    ELIST_LINK *current;
    ELIST_LINK *next;

    // 其他成员

public:
    ELIST_ITERATOR() {
        list = NULL;
    }

    ELIST_ITERATOR(ELIST *list_to_iterate);

    void set_to_list(ELIST *list_to_iterate);

    void add_after_then_move(ELIST_LINK *new_link);

    void add_after_stay_put(ELIST_LINK *new_link);

    ELIST_LINK *forward();

    ELIST_LINK *extract();

    // 其他方法
}

实际单链表的实现方法

刚才提到,在表示节点的类型 ELIST_LINK 中其实是没有表示节点数据的成员的,如果直接使用,肯定是没有意义的 —— 实际上 Tesseract 中确实也没有哪儿直接就使用 ELIST 了。Tesseract 中通过多重继承来解决了这个问题。

比如说我们希望我们的节点中保存一个点的坐标,那么我们可以先定义一个 Point 类:

class Point
{
public:
    Point(): _x(0), _y(0) {}
    Point(int x, int y): _x(x), _y(y) {}
    ~Point();

private:
    int _x;
    int _y;
};

然后定义节点类 POINT ,继承 Point 和 ELIST_LINK:

class POINT: public ELIST_LINK, public Point
{
public:
    POINT();
    ~POINT();
};

然后依次定义 POINT_LIST(继承 ELIST) 和 POINT_IT(继承 ELIST_ITERATOR)。

在 Tesseract 中提供了两个宏来直接产生 POINT_LIST 类和 POINT_IT 类的定义与实现,这两个宏分别是:

  • ELISTIZEH
  • ELISTIZE

第一个宏用来产生 POINT_LIST 和 POINT_IT 的定义,第二个宏用来产生实现,其用法是(仍以 POINT 为例):

  • 在 point.h 中类 POINT 的定义之后,添加以下语句:

    ELISTIZEH(POINT)
    
  • 在 point.cpp 中,添加以下语句:

    ELISTIZE(POINT)
    

这两个宏也是在 ccutil/elst.h 中定义的。这种用宏来展开类定义和实现的做法,在代码编写上确实很方便,但是阅读起来真的是好头疼,刚开始了解这个单链表的时候,我为了找 BLOCK_LIST (继承自 ELIST 的单链表类)的定义头疼地要死,代码里找不着,Google 也搜索不到答案,着实费了一番功夫。

由于 Tesseract 的这个设计把链表的操作与对节点数据的操作分离开来了,所以虽然没使用多态机制,这一套设计依然工作良好。

Tesseract 中实际被使用的单链表类

之前说过,Tesseract 中有不少不同类型的单链表都是从 ELIST 继承的,由于对 ELIST 的继承都是在宏展开后进行的,从代码里是找不到这些类的定义的,不过链表节点却是要手动去定义、实现的,所以查看那些继承了 ELIST_LINK 类的类就可以知道了。从 Tesseract 的 API Refrence 可以得知有 20 个类继承自 ELIST_LINK,它们是:

  • ASSOC
  • BLOB_CHOICE
  • BLOCK_RES
  • C_BLOB
  • C_OUTLINE_FRAG
  • CHAR_SAMPLE
  • CHAR_SAMPLES
  • FPSEGPT
  • FRAGMENT
  • ICOORDELT
  • MENU_L
  • OUTLINE_FRAG
  • PBLOB
  • PIXROW
  • REGION_OCC
  • ROW
  • ROW_RES
  • SORTED_FLOAT
  • WERD
  • WERD_RES

Tesseract:训练

2015年5月5日 08:00

资源文件

上一篇文章中已经讲述了 Tesseract 的基本使用,同时也提到, Tesseract 在识别是需要使用存储在磁盘上的 "语言文件" —— 为不产生歧义,这里简单以 "资源文件" 称呼它。在 Windows 系统上,这些资源文件可以在安装目录下的 tessdata 目录下找到;在 Linux 系统上,这些资源文件通常是在 /usr/share/tesseract-ocr/tessdata 目录下。按照 Tesseract 的约定,这些资源文件以 "traineddata" 作为后缀,除去后缀的部分则是该资源文件的 "名称" ,在使用 Tesseract 命令行工具或者 API 时,就通过这个名称来引用需要的资源文件。比如我们要用英语的资源文件来识别一张图像,通常会这么写:

tesseract input.png output -l eng

上述命令将会引用 eng.traineddata 这个资源文件。在 API 使用中同理。

除了默认的路径, Tesseract 还会在环境变量 TESSDATA_PREFIX 指定的目录中的 tessdata 目录下寻找资源文件 —— 事实上在 Windows 系统中,安装系统就是把该环境变量的值设置成了 Tesseract 的安装目录。这方便我们管理我们自己生成的资源文件。

如果系统中没有资源文件,或者没有需要的资源文件,该如何获取呢?一种办法是到 Tesseract 的下载页面去下载,在 "Summary+Labels" 一栏标注中有 "language data" 的就是了。对于 Linux 系统,可以直接从软件仓库中安装,以 Debian 为例,假如我们需要安装繁体中文的资源文件,可以执行

sudo apt-get install tesseract-ocr-chi-tra

如果不知道需要的资源文件在安装时用什么名称,可以使用 aptitude 进行搜索,在描述字段会有说明。

aptitude search tesseract-ocr-

存储位置与获取方式讲完了,如果是一个有足够好奇心的人,肯定会想了解一下这个资源文件里有什么内容 —— 哈,说得就是我自己啦!当然啦,我也是后面才知道怎么去查看的。

Tesseract 提供了工具来将一个资源文件打开,这个工具叫做 combine_tessdata ,它的更常用的功能是将训练过程中产生的各种资源打包到一起产生一个 Tesseract 可用的资源文件。以 eng.traineddata 为例,我们可以这样来解开它

combine_tessdata -u /usr/share/tesseract-ocr/tessdata/eng.traineddata english/eng

上述命令将会把 eng.traineddata 解开并将结果保存到 english 目录下,所有结果文件都会以 eng 作为前缀。需要注意的是,这里如果 english 这个目录不存在,是会出错的。下面是我在我的系统上的一次实际操作:

tessdata-extract.png

可以看到,这个资源文件里包含了很多东西,这些文件各自的意义为:

  • config: 配置文件
  • unicharset: 字符集文件,所谓 "字符集" 即是所有可被识别的字符的集合
  • unicharambigs: 识别歧义修正文件
  • inttemp: 每个字符的 "原型" ,或者是 "标准型" ,当然啦,这里面其实是包含了字符的各种特征,并不是一个标准的 "字符图像"
  • pffmtable: 指明了每个字符的特征数量
  • normproto: 项目网站上的说法是 "normalization sensitivity prototypes" ,不知道怎么翻译合适
  • 以 dawg 结尾的文件: 有向非循环词图(Directed Acyclic Word Graph, DAWG)文件,用来增强、调整识别过程
  • cube-unicharset, cube-word-dawg: 用于 Tesseract 内部名为 CUBE 的识别引擎,无资料,不详
  • shapetable: 同样是个不知道如何翻译的家伙,反正项目网站上说这个文件已经不需要使用(但还是得有这个文件),就不纠结了

对于这些文件,我们可以用 combine_tessdata 将其重新打包起来:

cd english
combine_tessdata eng

上述命令会在该目录下寻找前缀为 eng 的必要文件,然后打包成 eng.traineddata。这些文件里面有一些是资源文件的必须成分,有一些则是锦上添花的成分。读者可以试试进入到这些文件所在的目录,尝试去掉其中一些然后进行打包操作。

资源文件的训练

如果刚才按照建议进行过了尝试,应该能发现哪些是必要的文件,它们是:

  • unicharset
  • inttemp
  • pffmtable
  • normproto
  • shapetable

训练的过程就是为了从训练数据中产生这些东西。

数据准备

首先要准备好训练用的文本数据,根据不同的应用场景,对文本数据的要求会不一样。这些文本数据有两个用途:

  1. 用以产生字符集
  2. 用以产生语言模型

产生字符集好理解,数据文件应尽量涵盖可能出现的字 —— 不过字符集越大,在使用生成的资源文件进行识别时时间消耗也会越大,所以应该根据实际情况进行折衷处理,比如说汉语的识别,一般来说涵盖了国标一级常用汉字(3755个)就已经足够了。

除了字符集尽量涵盖可能出现的字外,我们也希望训练用的数据中的文字组合能尽量贴合真实场景,我们可以用这些数据来产生语言模型信息添加到最后的资源文件中。之前例子中的 DAWG 文件中就承载了这些信息。不过比较遗憾的是,对于中文训练,我目前知道的只是添加高频词表(freq-dawg)与词表(word-dawg),如何添加 ngram 信息目前毫无头绪。将简体中文资源文件 chi_sim.traieddata 解开后里面有一个名为 chi_sim.fixed-length-dawgs 的文件,从项目网站上来看,应该是与 ngram 信息对应的文件,但该文件无法解开,项目网站上对这个文件也是语焉不详。 如果有对这个问题的解决办法,请不吝赐教。

所以这一步要做的事情有两个:

  1. 数据清洗,这个和自然语言处理里的清洗是一样的
  2. 字符集提取,简单来说,排序、去重即可

其中清洗后的数据用来提取语言模型信息,去重后的字符集数据用来进行训练。

项目网站上说到,每个字在训练用的数据文件中一般应该有 10 个样本,低频字也至少要有 5 个,高频的应该在 20 个以上。不过就我目前进行的中文训练情况来看,每个字一个样本得到的结果也没有明显的差异,读者可以自行试验。

需要注意的是,在生成图像时可能存在一部分字符要用某个字体来表示,而另一部分字符要用另外一个字体来表示 —— 比如汉字通常用宋体而英文可能用 Time News Roman ,这种情况下建议将数据分割成开来。

图像与BOX文件生成

有了数据文件后,我们需要用这些数据文件中的文字来生成图像,用这些图像去进行训练。在 3.03 后, Tesseract 已经提供了相应的工具 text2image,使用方法为:

text2image  --text=chinese.txt --outputbase=chinese.sun.exp0 --font="SimSun" --fonts_dir=./fonts/

上述命令以 chinese.txt 作为输入,字体使用宋体,将图像输出为 chinese.sun.exp0.tif。同时还会输出一个名为 chinse.sun.exp0.box 的 BOX 文件,里面会对应每一个文字在图像中的位置信息。

同一个数据文件,可以应用不同的字体产生不同的图像,字体越多,产生的资源文件所能支持的实际情况也就越多,但建议还是按照实际应用情况来添加字体支持。

字符集文件与字体信息文件生成

提取 Tesseract 能读取、处理的字符集文件,使用 unicharset_extractor 命令:

unicharset_extractor chinses.box english.box

该命令以上一步输出的 BOX 文件作为输入,实际使用时,应将本次训练时的所有 BOX 文件作为输入。该命令的输出是名为 unicharset 的文件。

此外,在后续步骤中需要字体属性文件,该文件的名称应为 font_properties ,该文件中每一行表示一种字体的信息,其格式为:

<font name> <italic?> <bold?> <fixed?> <serif?> <fraktur?>

第一个字段为字体名称,名称中不能有空格,名称可以任意,但建议尽量贴近字体在操作系统上的名称,后面五个字段分别表示:

  • 该字体是否有斜体
  • 该字体是否有粗体
  • 该字体是否有无衬线体
  • 该字体是否有衬线体
  • 该字体是否有哥特体

比如宋体是有衬线体的,对应的,它在 font_properties 这个文件中的内容应为:

SimSun 1 1 0 1 0

特征文件生成

特征文件的生成使用 tesseract 命令:

tesseract chinese.sun.exp0.tif chinese.sun.exp0 nobatch box.train

上述命令将生成名为 chinese.sun.exp0.tr 的特征文件。对每一张生成的 TIFF 图像,都要进行该步骤以生成特征文件。

聚集

上一步的特征文件生成后,需要将同样文字的不同字体的特征聚集到一起来产生该文字的一个 原型 ,这一步需要执行三个命令:

shape_clustering -F font_properties -U unicharset  *.tr

这一步将会生成一个名为 shapetable 的文件。

mftraining -F font_properties -U unicharset -O banker.unicharset *.tr

这一步将会生成一个名为 inttemp 的文件和一个名为 pffmtable 的文件。

cntraining *.tr

这一步将会生成一个名为 normproto 的文件。

[可选]添加配置文件、歧义修正文件、DAWG文件

略(后面补上)

打包

在上述步骤都完成后,将要打包入资源文件的那些文件加上一个统一的前缀,该前缀即是待生成的资源文件的名称,这里假定我们要生成 chi.traineddata 这样一个资源文件,那么我们的可能操作就是:

mv unicharset chi.unicharset
mv shapetable chi.shapetable
mv inttemp chi.inttemp
mv pffmtable chi.pffmtable
mv normproto chi.normproto

combine_tessdata chi

至此,训练过程结束。

Tesseract:安装与命令行使用

2015年4月17日 08:00

Tesseract 是一款被广泛使用的开源 OCR 工具,本文将对其进行简单的介绍.

简介

tesseract.gif

Tesseract(/'tesərækt/) 这个词的意思是"超立方体",指的是几何学里的四维标准方体,又称"正八胞体"。右图是一个正八胞体绕着两个四维空间中互相正交的平面进行双旋转时的透视投影。不过这里要讲的,是一款以其命名的开源 OCR(Optical Character Recognition, 光学字符识别) 软件。

所谓 OCR 是图像识别领域中的一个子领域,该领域专注于对图片中的文字信息进行识别并转换成能被常规文本编辑器编辑的文本。

Tesseract 已经有 30 年历史,开始它是惠普实验室的一款专利软件,然后在 2005 年开源,自 2006 年后由 Google 赞助进行后续的开发和维护。

在 1995 年 Tesseract 曾是世界前三的 OCR 引擎,而且在现在的免费 OCR 引擎中,其识别精度也仍然是出类拔萃的。因为其免费与较好的效果,许多的个人开发者以及一些较小的团队在使用着 Tesseract ,诸如验证码识别、车牌号识别等应用中,不难见到 Tesseract 的身影。

获取,安装与配置

Linux

主流的 Linux 发行版都可以通过包管理器来安装 Tesseract,以 Debian 及其衍生版为例:

sudo apt-get install tesseract-ocr

如果想用 Tesseract 对图像进行识别,还需要对应的语言文件。所谓的语言文件是 Tesseract 识别某种语言的文字图像时需要的一些资源,这些东西也可以通过包管理器获取。比如我们需要识别英语和简体中文,那么:

sudo apt-get install tesseract-ocr-eng tesseract-ocr-chi-sim

当然了,这是通过包管理器的方式进行安装,如果需要,还可以通过编译安装的方式来构建最新版的 Tesseract.

Tesseract 的项目托管在 Google Code 上,在下载页面可以自己选择需要的版本,假如我们需要安装 tesseract-ocr-3.02.02.tar.gz 这个版本:

wget https://tesseract-ocr.googlecode.com/files/tesseract-ocr-3.02.02.tar.gz
tar xzvf tesseract-ocr-3.02.02.tar.gz

Tesseract 的编译需要 automake, autoconf, libtool 支持,所以这几个工具得装上:

sudo apt-get install autoconf automake libtool

当然了, Tesseract 还依赖一些图像库:

sudo apt-get install ligjpeg62-dev libtiff4-dev libpng12-dev libleptonica-dev

注意: 以上安装的包的名称可能在不同发行版上略有不同

需要注意的是,Leptonica 是 Tesseract 的一个比较重要的依赖,而且不同版本的 Tesseract 对 Leptonica 的版本要求也不一样,需要留意包管理器所安装的 Leptonica 版本是否满足要求,如果不满足要求,最好还是下载 Leptonica 的源代码编译安装。

  • Tesseract 3.01: Leptonica 版本不低于 1.67
  • Tesseract 3.02: Leptonica 版本不低于 1.69
  • Tesseract 3.03: Leptonica 版本不低于 1.70

解决依赖后按常规方法编译安装即可:

./configure
make
sudo make install
sudo ldconfig

在 3.03 及以上版本中,用于训练产生语言文件的工具需要单独编译和安装:

make training
sudo make training-install

建议在执行 ./configure 时加上参数 –prefix=xxx 来指定安装路径,这样以后要卸载会方便一些——当然如果这样做的话在安装完后需要做一些额外的工作,包括:

  • 添加 Tesseract 的可执行程序路径到环境变量 PATH 中
  • 在 /usr/include 目录或者 /usr/local/include 目录下建立 Tesseract 安装目录下 include/tesseract 的符号链接
  • 在 /usr/lib 目录或者 /usr/local/lib 目录下建立 Tesseract 安装目录下的 lib 目录下的静态链接库、动态链接库的符号链接

安装完成后,无论是通过包管理器安装的还是通过编译源代码安装的,建立都配置一下 TESSDATA_PREFIX 这个环境变量。在这个环境变量未设置的情况下,Tesseract 将会在安装目录中的 share/tessdata 这个目录下去寻找、加载语言文件,这本身当然没什么问题。

问题在于当我们想添加新的语言文件时,会遇到一些麻烦——程序一般都是安装的系统目录中,也就是说,我们需要提升权限才能将语言文件放到正确的地方。假如是在公司的服务器上进行相关的操作,普通用户一般都是没有 sudo 权限的。将语言文件放置在用户目录中可以解决这个问题,方法是在 .bashrc (假设您使用 bash 作为日常的 shell)中设置

export TESSDATA_PREFIX=$HOME/

如上设置时,将语言文件放在 ~/tessdata/ 下面即可。

Windows

Windows 上的安装也很简单,下载对应的安装程序,双击运行,按照提示进行即可。

tesseract-install.png

注意在 "Language data" 那个选项里,默认是只勾选了英文的,如果需要进行其他语言的识别,记得勾选对应的语言。

再一个是,如果需要进行相应的开发工作,建立把 "Tesseract development files" 这个选项也勾选。

安装完成后如无异常,会将安装目录添加到环境变量 PATH 中,这样就可以在 cmd 中直接运行程序了——Tesseract 并没有提供图形界面的工具,只能在命令行中使用,当然了,我们可以自己编写 GUI 应用程序来调用它,这个准备后面再讲。

命令行使用

这里只简单讲一下 Tesseract 识别图像的基本用法,关于训练和开发将另开新篇来专门讲述。

由于 Tesseract 只提供命令行工具,这里讲到的用法对 Linux 和 Windows 平台都适用。

首先可以通过 "–list-langs" 来查看有哪些可用的 "语言" ,如果之前的 TESSDATA_PREFIX 环境变量没有设置错,将会看到这样的输出:

bash > tesseract --list-langs
List of available languages (17):
math
chi
chi_sim
eng

这里的 "eng" 和 "chi_sim" 是 Tesseract 提供的英文和简体中文的语言文件,而其他的几个则是我自己训练得到的。

另外要说明的是,这里的 "语言文件" 的本质是包含了某种 "自然语言" 的文字的特征等辅助识别的一些资源,但像 chi_sim 这个中文简体里也包含了英文字母与阿拉伯数字的资源。而我们也可以为了特定的用途而去训练产生对应的资源,这里的 "math" 是用于识别简单数学表达式的一个资源,并不是说有一种叫做 "math" 的语言。

如果发现以上命令的输出为空,那应该去检查一下 TESSDATA_PREFIX 这个环境变量。在这个环境变量无误且 "语言文件" 存在的情况下,假设我们有一张名为 paper.png 的图片,则通过以下命令对图片进行识别,

tesseract paper.png paper -l chi_sim
  • 第一个参数是待识别的图像的文件名
  • 第二个参数用于指定输出,如果希望直接输出而不是保存到文件,那么就使用 stdout,否则这个参数将会作为保存结果的文件的前缀
  • -l chi_sim 这个应该很好理解,就是用来指定使用哪个 "语言文件",如果是使用 英文(eng) ,这个参数可以不加,因为默认就是使用英文的 "语言文件" 来进行识别

以上命令如不出错,结果将会保存到 paper.txt 这个文本文件中。

此外 Tesseract 还提供非常丰富的可选参数来对识别过程进行调整,可用的参数及其默认值可以通过以下命令进行查看:

tesseract --print-parameters

参数的使用有两种:

  • 使用 -c 选项来设定单项参数的值,比如:

    tesseract paper.png paper -l chi_sim -c language_model_ngram_on=1
    

    允许使用多个 -c 选项来设置多个参数的值。

  • 将多项参数设置写入文件,然后在识别时使用该文件,比如:

    tesseract paper.png paper -l chi_sim tess.conf
    

    需要注意的是,如果使用配置文件,用作参数的配置文件名要放在最后面——这里也支持多个配置文件,但它们必须要在最后面。假如我有两个配置文件 tess_1.conf 和 tess_2.conf,那么这样是正确的:

    tesseract paper.png paper -l chi_sim tess_1.conf tess_2.conf
    

    而这样则是错误的:

    tesseract paper.png paper tess_1.conf -l chi_sim tess_2.conf
    

至于 Tesseract 那些参数各有什么含义,官方没有提供任何文档来进行解释,这里有一个链接提供了部分参数的用处说明,应该是阅读了 Tesseract 源代码后得到的结论。

Python触发按键事件

2015年3月2日 08:00

缘起

最近想尝试做一个简单的命令控制系统,关于命令控制方面的技术暂且不说,这个我也还在调研、学习,估计进度会很慢——我将问题分解后,发现有一些无关语音技术的小问题是可以优先去解决的,比如音频的录制,还有就是本篇所说的按键触发。

按键的捕获这个是很容易做的事情,很容易想到 Python 众多的 GUI 库必然都会包含这一功能,但按键触发的 Python 库找了一圈也没找到几个。最后还是在 StackOverflow 上找到了可靠的答案。也就是说,本文也不过是一些已有信息的组织和加工而已——事实上我写的大部分博客都属于此列吧。

按键触发最终必然涉及到底层系统调用,对于这些底层的东西,我不太关心,我只是想达到效果而已。所以我也是对我觉得好用的库进行实践说明了。鉴于我的开发环境是 Linux,我只会对适用于 Linux 的或者跨平台的库进行说明。

适用于Linux的方法

在 Linux 的 X 环境(即桌面环境)上,可以通过 xautomation 软件包中的 xte 工具来触发按键、鼠标点击等事件,在 Linux 系统上通过软件包管理系统直接安装即可。其效果如下图所示:

xte.gif

在 Linux 上的办法就是在 Python 中通过子进程来调用 xte ,以达到效果。下面是一个简短的示例代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from subprocess import Popen, PIPE

emacs_save_sequence = '''keydown Control_L
key x
key s
keyup Control_L
'''
def keypress(sequence):
    p = Popen(['xte'], stdin=PIPE)
    p.communicate(input=sequence)

keypress(emacs_save_sequence)

上述代码触发的是 Emacs 的保存快捷键"C-x C-s"。

适用于Windows的方法

在Windows系统上可以通过 pyHook 来进行按键触发,限于环境,就不作展开了。

跨平台的方法

PyUserInput 这个 Python 库能够在 Linux 、Mac 和 Windows 三个系统上使用,考虑跨平台的工具的话可以使用它。

它的使用也很简单,对于按键事件来说,是通过一个 PyKeyboard 对象来进行的,通过三种不同的方法来触发不同的事件

  • press_key: 按下
  • release_key: 松开
  • tap_key: 敲击

当然,它还提供了如 type_string 这样的方法来进行字符串的输入。

下面是用 PyUserInput 写的触发 Emacs 的保存快捷键 "C-x C-s"的示例代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pykeyboard import PyKeyboard

k = PyKeyboard()

k.press_key(k.control_l_key)
k.tap_key('x')
k.tap_key('s')
k.release_key(k.control_l_key)

Linux Shell技巧: 进程替代(Process Substitution)

2015年1月3日 08:00

What is process substitution?

"Process Substitution",我将之翻译为"进程替代",不知道有没有更相应的专业中文翻译,姑且先用着好了。它允许用将命令的输出结果当作"文件"来使用——这句话的意思是这样的,假设你有一个工具,它原本接受的参数应该是一个指代某个具体文档的"文件名",使用"进程替代"后,我们可以用其他命令的输出来作为文件的内容,让这个工具去处理。

说得比较绕,先看看 Wikipedia 上的解释:

In computing, process substitution is a form of inter-process communication that allows the input or output of a command to appear as a file. The command is substituted in-line, where a file name would normally occur, by the command shell. This allows programs that normally only accept files to directly read from or write to another program.

Process substitution on Linux

在Linux上,通过下面的形式使用 process substituion:

<(<some command> <args>)

下面用一个实际的例子来说明它的使用。

以我的工作为例,对于一个测试集,在进行完 Speaker Diarization 后,会根据标注文件(即用作参照的标准结果)计算它的错误率,而 Speaker Diarization 的错误率由三部分组成:

  • Missed speech
  • False alarm speech
  • Speaker error

读者不必对这些词的具体含义去深究,只要知道是一个错误率的统计,同时总体错误由三个成分组成就行了。

在计算了错误率之后,会将统计结果记录在一个文件中,在这个文件中,每一行都一个音频的测试结果,形式如下:

1.wav Miss = 2.9 False = 3.4 Speaker = 1.0 Total = 7.3

现在我们有两个这样的文件,是对同一批测试集进行了两次测试后得到的结果,第一个文件 2015-05-31-der.log 的内容如下:

1.wav Miss = 2.9 False = 3.4 Speaker = 1.0 Total = 7.3
2.wav Miss = 1.0 False = 2.5 Speaker = 0.0 Total = 3.5
3.wav Miss = 2.7 False = 1.1 Speaker = 0.1 Total = 3.9

第二个文件 2015-06-22-der.log 的内容如下:

2.wav Miss = 0.5 False = 2.2 Speaker = 0.9 Total = 3.6
1.wav Miss = 2.8 False = 0.0 Speaker = 0.0 Total = 2.8
3.wav Miss = 2.4 False = 1.3 Speaker = 0.4 Total = 4.1

(注: 以上数据纯属杜撰,与我目前工作中的实际错误率情况没有任何关系)

我需要根据这两个文件,得到每个音频在两次测试中各个成分的对比情况,希望输出的每一行是这样的:

1.wav Miss = 2.9 False = 3.4 Speaker = 1.0 Total = 7.3 | Miss = 2.8 False = 0.0 Speaker = 0.0 Total = 2.8

比较容易想到需要根据文件名进行 sort ,然后使用 paste 把两个文件拼接起来,那么很自然地可以这样写:

sort -k1,1 2015-05-31-der.log > 2015-05-31-der-sorted.log
sort -k1,1 2015-06-22-der.log | cut -d ' ' -f 2- > 2015-06-22-der-sorted.log
paste -d '|' 2015-05-31-der-sorted.log 2015-06-22-der-sorted.log
rm *-sorted.log

使用进程替代的话,我可以用一行就搞定,而且不需要生成临时文件:

paste -d '|' <(sort -k1,1 2015-05-31-der.log) <(sort -k1,1 2015-06-22-der.log | cut -d ' ' -f 2-)

另外一个例子,就是使用 diff 比较两个文件内容的时候,而且我们关心的是某个文件中某个记录在另外一个文件中有没有,不希望受次序影响时——diff是按行来进行文件内容对比的。还是来假设一个场景吧。

假设我和我的一个朋友各自出去购物,完了回来想比较一下我们购买东西的区别:我买的东西里面哪些他没有买,他买的哪些我没有买。

我的购物清单是 shopping-list-1.txt ,内容如下:

苹果
上衣
毛巾
耳机
无线键盘

我朋友的购物清单是 shopping-list-2.txt ,内容如下:

耳机
苹果
无线键盘
科幻小说
五号电池
体重秤
移动电源

那么相比不用进程替代的传统办法,使用进程替代的办法会简单很多,一行搞定:

diff <(sort shopping-list-1.txt) <(sort shopping-list-2.txt)

结果如下:

diff-result.png

可以看到,我买了而我朋友没买的东西是:

毛巾
上衣

我朋友买了而我没买的东西是:

科幻小说
体重秤
五号电池
移动电源

Some more

实际上,进程替代也不是什么很新奇的东西,Wikipedia 上说,它是进程间通信的一种方式,事实上也确实是这样。在 Linux 上使用进程替代的时候,系统会创建一个临时的文件描述符,然后将用以替代的进程的输出和这个文件描述符关联起来,这个可以通过以下命令来验证:

echo <(sort shopping-list-1.txt)

不出意外,应该会看到这样的输出:

/dev/fd/63

"fd"就是文件描述父(File Description)的缩写,但你去/dev/fd/下面找这个文件描述符,却会发现找不到,那是因为这个文件描述符是临时的,在传给"echo"命令后就被释放了。

此外,进程替代并不能和文件完全等价,这一点要切记。进程替代所建立的"对象",是不能进行写入和随机读取操作的。不能写入的话应该很好理解,因为如果进行写操作,将会写到那个临时的文件描述符里面去,而这个临时文件描述符会被迅速地释放掉,而且由于创建的这个"文件"——姑且这么称呼,不是一个 regular file (与之相对的是 special file),如果在写入时有严格的检查,甚至连写入都会被拒绝;有时候我们需要对文件进行随机读取,比如 C 语言里的 fseek() 函数的操作,这样的操作将不能在进程替代产生的临时对象上正常运作。

总的来说,我个人是很喜欢这个功能的,减少了处理数据时不少的工作量。

That's it!

编写Shell脚本时的一些小技巧

2014年8月10日 08:00

自工作以来,写了不少脚本,有自己临时需要而写的,也有给测试妹子编写的工具。在这个过程中,碰到了许多问题,当然也就学习到了很多啦。有些零零碎碎的细节,花大篇幅讲有点没必要,就整合在这篇文章里吧。

使用特殊变量

  1. $#

    $#表示当前参数列表的长度,对需要参数的Shell函数,这个变量同样有效

  2. $*与$@

    这两个都包含了当前所有参数,但当用 双引号将这两个特殊变量括起来 后,它们之间会产生差异。

    编写一个简单的脚本spe_arg.sh,内容如下:

    #!/bin/bash
    
    function arg_num(){
        echo $#, $1
    }
    
    arg_num "$*"
    

    执行它,随便给点参数:

    ./spe_arg.sh a b c d
    

    得到的结果是:

    spe_arg-1.png

    而将脚本中的$*替换成$@后,得到的结果是:

    spe_arg-2.png

  3. $$

    这表示当前的进程号。我经常将其用于生成临时文件,由于每个脚本运行后,其进程号都是唯一的,可以保证不会和其他脚本产生的临时文件发生冲突。

  4. $?

    上一条命令的返回值,为0则表示上一条命令正常执行,否则就是出错了。通过这个值可以方便地进行错误处理。

  5. $0

    表示当前脚本的名字

  6. $1, $2, … $9

    分别表示第1个、第2个…第9个参数

条件表达式

在Shell脚本中,有三种条件表达式的形式:

  1. 一对中括号括起来的条件表达式,如

    [ 0 -gt 1 ]
    

    使用这种方式,在进行字符串比较时,用于比较的运算符"<"和">"必须转义,即必须表示为"\<"和"\>"。

  2. 用一对双中括号括起来的条件表达式,如

    [[ 0 -gt 1 ]]
    

    使用这种方式时,字符串比较的运算符不需要转义。因此,应该尽量使用这种方式而不是第一种。

    另外,无论是第一种方式还是第二种方式, 条件表达式前后一定都要有至少一个空格 ,即下面这样的是非法的:

    [0 -gt 1]
    [0 -gt 1 ]
    [ 0 -gt 1]
    
  3. 用一对双括号括起来的条件表达式,如

    (())
    

    这种方式用于算术比较,写在其中的条件表达式应该按照类似C语言的风格来表示,即不需要遵循上面两种方法的限制,也不需要使用dollar符($)来表示变量的值。

    当然,双括号不只用于条件表达式,还可以在其中进行四则运算,for循环也可以使用它。

变量使用

  1. 局部变量

    在函数中,应该尽量将使用到的变量定义为局部变量,以下做法可以达到该目的:

    local var=""
    
  2. \(var与\){var}

    在可能产生歧义的地方,应该使用后一种方式。所谓的歧义,可能这个词我用得不太准确,还是举个例子来描述一下吧。

    以前阵子我写的一个脚本为例,当时我定义了一个循环计数变量,命名为"i",然后在每一次循环时,根据其值创建一些文件,大致是下面这个样子

    i=0
    for i in $(seq 1 3);do
        now_file=$ROOT_PATH/$i_wavlist
        > $now_file
    done
    

    我的本意是产生诸如"1_wavlist"、"2_wavlist"这样的文件,但没注意下划线也是变量名合法字符这个事实。所以这样才是正确的:

    i=0
    for i in $(seq 1 3);do
        now_file=$ROOT_PATH/${i}_wavlist
        > $now_file
    done
    
  3. 在sed/grep/awk中使用Shell变量

    sed和grep可以一起说,因为是同样的问题。在sed/grep的表达式中使用Shell变量,如果想得到期望的结果,应该用双引号而不是单引号将表达式括起来。这是因为在Shell中,双引号是 弱引用(Weak Quotes) ,而单引号是 强引用(Strong Quotes) ,在一对单引号中的内容,会作为字符串内容原原本本地输出,也就是说,Shell的取值操作符dollar符($)在一对单引号中,也是被视为一个普通字符的。

    不仅仅是取值操作符,像转义符(\)、反引号符(`)等特殊字符,在一对单引号中,其特殊意义会统统失效。

    awk中的情况与前面两者类似,但又有所不同。由于awk的表达式中也用dollar符($)来取awk处理时每个域(field)的值,所以在awk中使用Shell变量,必须要区分开Shell变量与awk变量。

    第一种方法是通过"-v"选项定义一个awk变量并将Shell变量赋值给它,如下:

    awk -v var="$shell_var" '{print var}' a.txt
    

    第二种方法是用双引号来将awk的表达式括起来,但要注意的是,当用双引号括起awk表达式时,像"$1"这样的都会被认为是在获取Shell变量的值,即在awk处理这串表达式前,Shell会将所有以dollar符开头的东西按照Shell中取值规则取值,然后替换成这个值,于是本来想让awk打印第一个域(field)的意图就会落空。

    解决这个问题的方法是用转义符将不是使用Shell变量的地方转义,如下:

    awk "{print \$1, $shell_var}" a.txt
    

    第三种办法是通过额外的引用来达到目的,如下所示:

    awk '{print $1, "'$shell_var'"}' a.txt
    

处理文件

文本处理这里就不说了,只是说一下处理文件时的一些小细节。

  1. 文件属性判断

    条件表达式中,'-e'用于判断文件是否存在,'-d'用于判断是否是一个目录,'-s'用于判断文件是否存在但内容为空。这三个都是我比较常用的。

  2. 获取文件绝对路径

    在一些情景下,我们需要获得文件的绝对路径,比较蠢的办法是:

    dir=$(dirname $file)
    full_dir=$(cd $dir && pwd)
    

    但其实还有更好的办法,那就是使用'readlink'这个命令:

    dir=$(dirname $(readlink -f $file))
    

后记

暂时先这样吧,以后还有东西,会继续更新本文。

Shell脚本中参数处理方法

2014年8月9日 08:00

在Shell脚本中处理命令行参数,可以使用getopts/getopt来进行——当然,手工解析也是可以的。

下面通过一个特定的情景来讲一下这三种参数处理方法。

这两天写了一个安全删除的脚本,原理就是将指定的文件移动到某个特定的目录下并保存其原始路径信息,这和在Windows下以及在Linux的桌面环境下"将文件移动到回收站"的意义是一样的。就拿这个来做例子吧。

在这个脚本中,有五个选项,分别代表五种动作:

  1. -d : 将文件移动到回收站,该选项后需要指定一个文件或目录名
  2. -l : 列出被移动到回收站的文件及其id,该选项不需要值
  3. -b : 恢复被移动到回收站的文件,该选项需要指定一个文件对应的id
  4. -c : 清空回收站,该选项不需要值
  5. -h : 打印帮助信息

手工解析

所谓的手工解析,就是取到参数后手工一个一个解析了,以下是手工解析上述情景参数的过程:

while [ $# -gt 0 ];do
    case $1 in
        -d)
            shift
            file_to_trash=$1
            trash $file_to_trash # trash is a function
            ;;
        -l)
            print_trashed_file  # print_trashed_file is a function
            ;;
        -b)
            shift
            file_to_untrash=$1
            untrash $file_to_untrash # untrash is a function
            ;;
        -c)
            clean_all           # clean all is a function
            ;;
        -h)
            usage
            exit 0
            ;;
        \?)
            usage
            exit 1
            ;;
    esac
done

这里用到了'shift'这个命令,这个命令的作用是将参数列表以空格为分隔符左移一个单位,或者可以理解为将第一个参数给去掉了,比如获取的命令行参数为:

-d hello.txt

在执行了'shift'后,命令行参数就变成了

hello.txt

这样,在使用了shift后,我们每次都只要去看参数列表中的第一个就行了。当然,其实不用'shift'也是可以的,比如说这样:

i=1
while [ $i -le $# ];do
    case ${!i} in
        -d)
            i=$(expr $i + 1)
            file_to_trash=${!i}
            trash $file_to_trash # trash is a function
            ;;
        -l)
            print_trashed_file  # print_trashed_file is a function
            ;;
        -b)
            i=$(expr $i + 1)
            file_to_untrash=${!i}
            untrash $file_to_untrash # untrash is a function
            ;;
        -c)
            clean_all           # clean all is a function
            ;;
        -h)
            usage
            exit 0
            ;;
        \?)
            usage
            exit 1
            ;;
    esac
    i=$(expr $i + 1)
done

对比可以发现使用'shift'会稍微方便一点。

当然,上面的处理没有进行参数检查,这些检查应该要防止这些错误情况:参数个数为0、完全冲突的"动作"一起出现、选项需要值但未给值。

getopts

'getopts'是POSIX Shell中内置的一个命令,其使用方法是:

getopts <opt_string> <optvar> <arguments>

下面是在Shell中使用该命令的一个示例:

getopts.gif

本质上来说,'getopts'的处理和我们手工处理是差不多的,它不过是提供了更便利的方式而已。它的使用方式非常简单明了,其形式为:

while getopts <opt_string> <optvar>
    case $<optvar> in
        # ...
    esac
done

其中<opt_string>是要处理的选项的一个集合,每个选项在其中用不包含连字符'-'的字母来表示,每个代表选项的字母前后可以有一个冒号,前面有冒号表示当处理该选项出错时不输出'getopts'自身产生的错误信息,这方便我们自己编写对应的错误处理方法;后面的冒号表示这个选项需要一个值。对于我们这个"安全删除"的例子,这个<opt_string>应该是:

d:lb:ch

冒号的归属的话,先到先得吧,大概是这样。

在使用'getopts'时,有两个特殊的变量,它们是 OPTINDOPTARG ,前者表示当前参数在参数列表中的位置——相当于手工解析第二种方法中那个自定义的变量 i ,其值初始时为1, 会在每次取了选项以及其值(如果有的话)后更新; OPTARG 则是在选项需要值时,存储这个选项对应的值。这样,我们这个例子用'getopts'就可以写成:

while getopts d:lb:ch OPT;do
    case $OPT in
        d)
            file_to_trash=$OPTARG
            trash $file_to_trash # trash is a function
            ;;
        l)
            print_trashed_file  # print_trashed_file is a function
            ;;
        b)
            file_to_untrash=$OPTARG
            untrash $file_to_untrash # untrash is a function
            ;;
        c)
            clean_all           # clean all is a function
            ;;
        h)
            usage
            exit 0
            ;;
        \?)
            usage
            exit 1
            ;;
    esac
done

对比可以看到,相比手工解析的第一种办法,又更为简洁一点了。不过需要注意的是,'getopts'会从第一个参数开始,只按照<opt_string>指定的形式来寻找并解析参数,如果给出的实际命令行参数与其所描述的参数形式不符,则会出错中止。

比如说,对于上面的例子,假设这个脚本已经完全写好了,脚本名为 trash.sh ,其参数处理就是上面这样,那么如果我在终端里执行:

./trash.sh a -b hello.txt

开始那个多余的参数'a'将会导致'getopts'在解析到选项'-b'前就出错终止。所以呢,像使用'getopts'这样的方法,其自由度不如手工解析,如果要保证脚本在任何情况下都能正确解析参数,它需要多做一点——当然啦,上面这个愚蠢的错误使用情况还是比较少出现的啦,反正我现在写的脚本里压根没考虑这样的情况。

getopt

'getopt'与'getopts'类似,不过'getopts'只能处理短选项,'getopt'则能处理短选项和长选项。所谓的短选项就是类似下面这样的选项:

-a

而下面这样的则是长选项

--action=delete

当然,事无绝对,通过一些技巧,用'getopts'处理长选项也是可能的。这里先说一下如何用'getopt'来处理参数吧。

需要事先说明的一点是,'getopt'不是Shell内建的命令,而是'util-linux'这个软件包提供的功能,它不是POSIX标准的一部分,所以也有人建议不使用'getopt'

首先将之前说到的五种动作对应的短选项扩展一下,以便讲解'getopt'的使用:

  1. -d/–delete : 将文件移动到回收站,该选项后需要指定一个文件或目录名
  2. -l/–list : 列出被移动到回收站的文件及其id,该选项不需要值
  3. -b/–back : 恢复被移动到回收站的文件,该选项需要指定一个文件对应的id
  4. -c/–clear : 清空回收站,该选项不需要值
  5. -h/–help : 打印帮助信息

'getopt'既能处理短选项也能处理长选项,短选项通过参数 -o 指定,长选项通过参数 -l 指定。同'getopts'一样,它一次也只解析一个选项,所以也需要循环处理,不过与'getopts'不同的是,'getopt'没有使用 OPTINDOPTARG 这两个变量,所以我们还得手动对参数进行'shift',对需要值的选项,也得手动去取出值。

下面是在Shell中使用'getopt'的一个示例:

getopt.gif

可以看到,'getopt'将参数中以下形式的内容:

--longopt=argument

在返回结果中替换成下面这样的形式:

--longopt argument

这样就可以通过循环和'shift'来进行处理了,不过在脚本中,'shift'命令是对命令行参数起作用的,即特殊变量"$@",而我们在脚本中只能将'getopt'的返回结果作为字符串存储到一个变量中。为了让'shift'起作用,通常还要使用'set'命令来将变量的值赋给"$@"这个特殊变量。

真是有够麻烦的……算了,下面再集中吐槽吧……

然后,在设置好短选项和长选项后,在将实际的参数传给'getopt'时,要在实际参数前加上一个两个连字符 -- ,而'getopt'会将这两个连字符放到返回结果的最后面,在处理时可以将这两个连字符视为结束标志。

以下是针对本文假设的情景,使用'getopt'解析参数的流程:

arg=$(getopt -o d:lb:ch -l delete:,list,back:,clear,help -- $@)

set -- "$arg"

while true
do
    case $1 in
        -d|--delete)
            file_to_trash=$2
            trash $file_to_trash # trash is a function
            shift 2
            ;;
        -l|--list)
            print_trashed_file  # print_trashed_file is a function
            shift
            ;;
        -b|--back)
            file_to_untrash=$2
            untrash $file_to_untrash # untrash is a function
            shift
            ;;
        -c|--clear)
            clean_all           # clean all is a function
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        --)
            shift
            break
            ;;
    esac
done

然而,知道了'getopt'的使用及其原理后,自然而然地可以发现,我可以不用去管这个结束标志,用"$#"这个表示参数个数的特殊变量,同样可以控制参数解析的流程,这完全和手工解析是同一个道理。我甚至可以将'getopt'的返回结果存储到一个数组里,直接循环处理这个数组,而不用使用'set'命令了。

好了,吐槽时间。

我之前写脚本都是用的'getopts',一来我用不上长选项,二来'getopts'的使用足够简单。在写本文之前,我倒是知道'getopt'可以处理长选项,但没仔细了解过。这两天了解了一下,觉得还是别用'getopt'的好,理由如下:

  1. 'getopt'不是Shell内建命令,跨平台使用时可能会出现问题;
  2. 只是将'–longopt=val'这样的参数形式替换成了'–longopt val',但因此增加了许多复杂性,比如使用了'set'命令,在使用'set'命令时还要考虑'getopt'的返回结果中有无Shell命令,有的话应该使用'eval'命令来消除可能导致的错误

    eval set -- "$arg"
    
  3. 调用完还要进行与手工解析类似的工作,相比手工解析,并没有多大优势;
  4. 真的需要长选项吗?我觉得短选项就足够了

getopts处理长选项

既然不建议使用'getopt',那么怎么处理长选项呢?自然是有办法的。

为了方便讲解,这里假设一个简单的情景吧,在这个情景里,我们只需要处理两个可能的选项

  1. -f/–file: 设置文件名,该选项需要值
  2. -h/–help: 打印帮助信息,该选项不需要值

用'getopts'处理这种情况,可以这么做:

filename=""
while getopts f:h-: opt;do
    case $opt in
        -)
            case $OPTARG in
                help)
                    usage
                    exit 0
                    ;;
                file=*)
                    filename=${OPTARG#*=}
                    ;;
            esac
            ;;
        f)
            filename=$OPTARG
            ;;
        h)
            usage
            exit 0
            ;;
        \?)
            usage
            exit 1
            ;;
    esac
done

当然,也许并不比手工解析简洁多少,但用起来肯定是比'getopt'要舒服的。

在函数中解析参数

有时候,我们也许想把参数解析的工作放到函数中去做,比如说定义了一个'main'函数然后在'main'函数中封装整个流程处理逻辑。又或者像我一样,写了几个小小的工具函数,放到了Bash的配置文件 .bashrc 中,参数解析的工作必须得在函数中做。

手工解析是能想到的最直接的办法,简单可行。

不过假如我们想用'getopts'来处理呢?动手尝试后,你会发现直接在函数中使用'getopts'是会出错的。要在函数中使用'getopts',必须在这个函数中使用'getopts'前,将 OPTIND 这个被'getopts'使用的特殊变量设置为函数局部变量,像这样:

function main() {

    local OPTIND

    while getopts d:lb:ch OPT;do
       case $OPT in
           d)
               file_to_trash=$OPTARG
               trash $file_to_trash # trash is a function
               ;;
           l)
               print_trashed_file  # print_trashed_file is a function
               ;;
           b)
               file_to_untrash=$OPTARG
               untrash $file_to_untrash # untrash is a function
               ;;
           c)
               clean_all           # clean all is a function
               ;;
           h)
               usage
               exit 0
               ;;
           \?)
               usage
               exit 1
               ;;
       esac
    done
}

main $@

就是这样啦!

GDB调试技巧

2014年7月27日 08:00

以下是这几个月工作以来所学习到的GDB调试技巧,虽然并不全面,但我认为还是比较实用的一些东西。

bt/backtrace

这条命令可以在程序出错时打印出函数调用栈,方便追踪问题发生的所在。对于代码结构比较复杂的项目,这条命令是非常有用的,所以这条命令也是必须掌握的了。

比如对于下面这个程序:

#include <stdio.h>

int read_int(FILE *fp)
{
    int a = 0;
    fscanf(fp, "%d", &a);
    return a;
}

int main(int argc, char *argv[])
{
    FILE *file = fopen(argv[1], "r");
    int a = read_int(file);
    printf("read %d\n", a);
    fclose(file);
    return 0;
}

该程序没有检查文件是否打开成功,便进行读文件操作,在文件打开失败的时候,将会发生段错误。

sf-test.png

这个时候就可以用backtrace/bt 命令来发现是哪里出了问题:

gdb-backtrace.png

打印数组内容

在GDB中,如果在数组的定义所在的函数内查看数组内容,直接调用print命令即可,如,对下面的代码:

#include <stdio.h>

void add_one(int *arr, int len)
{
    int i = 0;
    for (i = 0; i < len; ++i) {
        arr[i] = arr[i] + 1;
    }
}

int main(int argc, char *argv[])
{
    int a[] = {1, 2, 3, 4, 5, 6, 7};

    add_one(a, 7);

    return 0;
}

在函数调用

add_one(a, 7);

前执行"p a",得到的结果如下:

gdb-print-array.png

但如果step进入函数 add_one ,执行"print arr",得到的却是这个结果了:

gdb-print-array2.png

这是因为在函数 add_one 中,数组的指针 a 作为参数传入后退化为了普通指针了,这个时候如果要我们手动一个一个打印来查看,那无疑是很痛苦的。但在GDB中有查看这样的连续内存数据的方法,而且有多种,如下:

  1. print *arr@7 / p *arr@7

    gdb-print-array3.png

    不管指针指向的内存中存储的是基本类型的数据还是自定义的类型,这个方法都适用。

  2. print (int [​7])*arr / p (int [​7])*arr

    gdb-print-array4.png

    同上,但在操作上要麻烦一点。

  3. x/7dw arr

    gdb-print-array5.png

    x 是GDB中用来检查内存的命令,其使用方法是:

    x/nfu addr
    

    其中 n 表示要重复打印的次数,默认值为1;*f* 表示输出的格式,支持 x(十六进制)、d(十进制)、 u(无符号整型)、 o(八进制)、t(二进制)、a(地址值)、c(字符型)、f(浮点型)、s(字符串)这几种格式,默认使用 x ;u表示每个输出的宽度,可以选择b(1字节)、h(2字节)、w(4字节)和g(8字节),默认为4字节(w)。

    不过对于自定义的复杂类型,这个方法并不好用。

设置源代码目录

有时候存在这样的情况,程序运行的机器和源代码所在的机器不是同一台机器,假如程序崩溃了,因为没有源代码,也只能大致知道是在什么地方出错了,但却没有办法详查。当然,这种情况下,一个很笨的办法是,把整个项目代码拿过来,配置好编译环境,然后用编译好的程序替换原来的程序,再进行调试。

但是这样实在是很麻烦。

我们是可以这样的,首先定位问题发生的代码范围(涉及到哪些源文件),并把这些涉及的源文件拷贝过来,然后在GDB调试时指定源代码目录就OK了。

指定源代码目录有两种方式:

  1. 在启动时使用 -d 选项设置
  2. 在启动后使用 directory 命令设置

当然了,最后说一下,像这种跨机器的调试,最好是在运行机器上设置好能生成core文件,否则要重现问题可能要花费一番功夫。打开core dump的方法是执行:

ulimit -c unlimited

当然,在shell中执行这个只是会暂时生效,如果需要在登录的时候生效,应该把这条语句写到 .bashrc 这个文件中去。

生命游戏(Game of Life)及其Python实现

2014年7月4日 08:00

生命游戏(The Game of Life)

生命游戏 是数学家John Horton Conway 发明的一个"零玩家游戏",因此也被称为"Conway's Game of Life"。实际上它并非是一个游戏,其本质为一个元胞自动机(cellular automaton)。生命游戏由一个无限二维平面以及被定义在这个平面上的几条简单规则组成,一般来说,这个无限二维平面是由无限个相同大小的方形网格组成的,定义于其上的规则有:

  1. 每个网格称为cell
  2. 每个cell有两种状态,alive和dead,分别用两种不同的颜色来表示
  3. 每个cell的状态变化受其周围8个cell影响
  4. 如果一个活的(alive)cell,其周围的活的cell数量少于2——即为0或1,则这个cell将死去(dead)
  5. 如果一个活的cell,其周围的活的cell数量超过3——即为4至8,则这个cell将死去
  6. 如果一个活的cell,其周围的活的cell数量为2或3,则它继续存活
  7. 如果一个死的cell,其周围的活的cell数量为3,则它成为活的cell,否则仍然为死的cell

前面三条规则定义了"世界"的表示,后面四条规则定义了"世界"的变化规律。在这几条简单的规则下,生命游戏产生了难以想象的复杂、有规律的图像。

首先来看看一些简单的、经典的模式(pattern):

  1. 滑翔机(Glider)

    这个模式里的图案在不断变化时,形似一个在不断运行的滑翔机,因而得名。

  2. 滑翔机枪(Gosper Glider Gun)

    滑翔机枪可以源源不断地制造出滑翔机。最初John Horton Conway悬赏50美金希望有人创造出滑翔机枪,MIT的黑客Bill Gosper尝试了上百种可能,最后率先发现了这个模式并得到了奖金,这个模式也因此冠以Gosper的名字。

然后来看一些复杂的模式:

  1. 旅行者(Weekender)

  2. 蜘蛛(Spider)

    gol-spider.gif

生命游戏是Conway对John von Neumann提出的"可自我复制的机器"产生兴趣后创作出来的,并于1970年10月在《科学美国人》(Scientific American)上发表。从理论上来说,生命游戏与通用图灵机(Universal Turing Machine)是等价的。由于生命游戏的"可进化性",在其发表后,就吸引了大批的人,时至今日,也仍然有不少人热衷于收集各种不同的模式,Unix/Linux系统上也还有生命游戏的实现,编辑器Emacs中也内置了一个生命游戏实现。

此外,生命游戏中还展现出了"涌现(Emergence)"和"自组织(Self-organization)"现象,甚至还催生出了人工生命(Artificial Life)这一领域。

Python实现

就生命游戏的规则而言,实现它是不需要多少工作量的,麻烦的是怎么通过图形来表现出来。最后我用Tkinter实现了一个简单的生命游戏,可以用鼠标点击的方式来设定初始模式,也可以通过下拉列表选择内置的模式,同时提供将自定义模式保存的功能。

为了保持代码逻辑上的简洁,我将状态更新的逻辑封装到了一个类里:

class GOL():
    """Status manager of GOL(Game of Life).

    Each world in game of life is a set of cells, and status of
    every cell should be alive or dead. Status changes from gen
    -eration to generation.
    """
    def __init__(self, row = 0, col = 0):
        """Init size and status of world."""
        self.row = row
        self.col = col
        self.now = {}
        self.next = {}
        self.init_status([])

    def init_status(self, init_cells):
        """Set status of world.

        if the begining status given is not empty, then use them
        to initialize the world.

        Args:
          init_cells: begining status given. It's a tow-dimensional
                      array. Becase its size maybe smaller than the
                      world, we should be map each coordinate to the
                      center area of world.
        """
        # code is hiden

    def update(self):
        """Update status of world by status before.

        For each cell in world, do these things:
        1. look and find how many alive neighbors around,each cell
        have eight neighbors
        2. update status of cell according the number of its alive
           neighbors. The following are the rules
           + if cell is alive, and number of neighbors is too small
             (less than 2) or too too much(more than 3), this cell
             will die, or it will be keep alive
          + if cell is dead, and number of neighbors is three, then
            cell will be alive
        """
        # code is hiden

    def neighbors(self, row, col):
        """Compute number of alive neighbors around a cell."""
        # code is hiden

在这个类里,通过两个分别代表当前状态与将来状态的dict来进行状态的更新。在开始实现时犯了一个错误,就是在方法update()中,计算完self.next后,进行了一个错误的操作:

self.now = self.next

结果导致这两个类成员变量称为了同一个对象的引用,修改其中一者,另一者也发生改变,最后导致结果不正确。最后update()方法如下所示:

def update(self):
  """Update status of world by status before.

  For each cell in world, do these things:
  1. look and find how many alive neighbors around,each cell
     have eight neighbors
  2. update status of cell according the number of its alive
     neighbors. The following are the rules
     + if cell is alive, and number of neighbors is too small
       (less than 2) or too too much(more than 3), this cell
       will die, or it will be keep alive
     + if cell is dead, and number of neighbors is three, then
       cell will be alive
  """
  self.next = self.now.copy()
  for crow in range(self.row):
      for ccol in range(self.col):
          around = self.neighbors(crow, ccol)
          if (around < 2 or around > 3):
              self.next[crow, ccol] = False

          elif ((not self.now[crow, ccol]) and
                around == 3):
              self.next[crow, ccol] = True

  self.now = self.next.copy()

图形前端的实现大致如下:

class ShowGOL(tk.Tk):
    """Diasplay the Game of Life

    Use Tkinter to draw the world of cells and the changes in
    the world.
    """
    def __init__(self, *args, **kwargs):
        """Init resource and world"""
        # init resource
        tk.Tk.__init__(self, *args, **kwargs)
        self.setup_members()    # initialize class members
        self.setup_window()     # root window
        self.setup_toolbar()    # tool bar
        self.setup_canvas()     # canvas to draw world

        # init world
        self.create_world()

        # make world alive
        self.after(5, lambda: self.life(5))

另外一些类方法就不列出来了,无非是绘图啊、事件响应啊什么的。代码我放到了Github上,如果需要,可以到这里 查看,或在本地执行:

git clone git@github.com:Linusp/pygol

后记

老实说,我也算是生命游戏的忠实粉丝了——嗯,我不太喜欢"粉丝"这个词,不过姑且就这么用吧。以前也用ncurses库实现过一个简单的终端版本,也实现过兰顿蚂蚁(Langton's Ant)这个同为元胞自动机的例子。当然,要说我的实现,都是很简陋的,比如说生命游戏,在网上有非常多的相关实现,Linux下有一个名为gtklife的软件,内置了很多的模式,其中不乏一些复杂、绚丽的模式,这款软件也是被我列为Linux上必装的软件之一。

现在虽然实现了图形化的生命游戏,但我不想止步于此。目前用的是Python的Tkinter库,这个库上手简单,但功能有限。所以我会继续完善这个生命游戏实现的!

参数类型不一致导致extern "C"不起作用

2014年5月31日 08:00

extern "C": 指示编译器以C形式进行链接

某些时候,我们需要将一些C++的库封装成C风格的接口供外部使用,所谓“C风格”的接口,是指该接口的参数、返回值中不包含类、作用域等C++的元素,但其实现中是可以有的。比如说,我们有一个类 Wav ,用来处理wave文件

#ifndef _WAV_H_
#define _WAV_H_

class Wav
{
public:
    Wav();
    Wav(string wav_file);
    virtual ~Wav();
    bool load_wav(string wav_file);
    private:
    short *data;
    int sampel_rate;
};

#endif /* _WAV_H_ */

然后我们需要利用这个类实现几个音频处理接口,提供给 C程序 使用,由于C语言中是没有“类”这个特性的,我们不能直接用C语言去实现这些接口,这个时候就要用到extern "C"了.

#ifndef _WAVPROC_H_
#define _WAVPROC_H_

#ifdef __cplusplus
extern "C"{
#endif
    int read_wav(const char *wav_file, double *data);
    int denoise(double *data);
    int cutscene(double *data);
    int feature_extract(double *data, double *feature);
#ifdef __cplusplus
}
#endif

#endif /* _WAVPROC_H_ */

然后在"wavproc.cpp"中实现这些接口,此时在"wavproc.cpp"中是可以使用上面定义的类以及其他C++语言的特性的。实现以后以静态库/动态库的形式提供接口,对于使用接口的C程序来说,只需要

  1. wavproc.h
  2. 对应的静态库/动态库,如: libwavproc.a

nm: 查看目标文件中的符号是C形式还是C++形式

nm是一个查看目标文件中符号列表的工具,其具体使用这里不会详谈,只讲一下用nm来判断使用extern "C"是否起作用。举个栗子,对下面这个函数:

#include "fs.h"

int file_size(const char *file)
{
    int fs = 0;
    // do something
    return fs;
}

以上内容在"fs.cpp"中,其对应的头文件为:

#ifndef _FS_H_
#define _FS_H_

int file_size(const char *file);

#endif

用g++将"fs.cpp"编译成目标文件后,用nm查看其中的符号信息:

nm ~/test/cppcode/fs.o

得到的输出是:

0000000000000000 T _Z9file_sizePKc

我们在"fs.c"中实现同样一个函数

#include "fs.h"

int file_size(const char *file)
{
    int fs = 0;
    /* do something */
    return fs;
}

其对应的头文件与"fs.cpp"的头文件一样。然后用gcc将"fs.c"编译为目标文件后,同样用nm查看符号信息:

nm ~/test/ccode/fs.o

得到结果为:

0000000000000000 T file_size

可以看到,同样一个函数,在C++程序和C程序中,被编译后产生的符号类型是不一样的。C++为了支持重载,在编译的时候会对函数符号添加前缀和后缀,后缀是参数列表的缩写,用来区分重载;而C编译器则不会对函数符号添加其他信息。

当我们需要将某些用C++实现的接口提供给C程序使用时,要在其头文件(接口定义)中用extern "C"来告诉编译器,按C的形式导出符号表——这个是之前提到过的,现在我们对"fs.cpp"的头文件"fs.h"进行这样的处理:

#ifndef _FS_H_
#define _FS_H_

#ifdef __cpulsplus
extern "C"{
#endif

    int file_size(const char *file);

#ifdef __cplusplus
}
#endif

#endif

然后用g++将"fs.cpp"编译为目标文件,并使用nm查看符号信息:

nm ~/test/cppcode/fs.o

得到的结果为:

0000000000000000 T file_size

可以看到,这次得到的符号类型是C形式的符号了,是可以被C程序所使用的。

"undefined reference": 诡异的错误

好了,终于到了标题提到的内容了。

事情是这样的,我负责的一个项目A是C++实现的,然后需要将其中的一些功能以接口的形式提供给另外一个项目B使用——项目B是C实现的。那么,理所当然的,我为项目A编写API时,要用extern "C"去处理头文件。在实现好接口后,我以静态库的方式将接口提供给了项目B,然而在编译项目B时,总是报错:

undeined reference: xxxx

意即有一个函数是未定义的引用,而该函数是项目A的API中提供的接口函数中的一个。我折腾了近一天时间,始终无法找到这个问题的原因所在——这个时候我还不知道使用nm来进行检查的方法。无奈之下向老大请教,老大遂用nm查了一下提供的静态库,发现导出的符号还是C++形式的,但诡异的是,同一个头文件中提供的若干个函数,只有这一个的符号还是C++形式的,而其他的都被导出成C形式的符号了。

老大也很迷惑,不过他说:先别怀疑编译器,应该还是有什么细节上处理不当。

果然,后来经过几番检查,发现是由于头文件中函数的参数类型和实现中函数的参数类型不一致导致了这个问题,在头文件中,该函数为:

void InitDiarizationSpace(Dia *env, const char *file);

而在实现中,该函数为:

void InitDiarizationSpace(Dia *env, char *file) {
    // ...
}

……

对此我表示很羞愧。

将局部变量地址作为线程函数参数导致bug

2014年5月20日 08:00

问题

在测试自己写的一个使用了多线程的API时,我发现有时候会出现一些预期之外的结果。

首先,这个API的实现中涉及多线程的部分大致是这样的:

  1. 有一个全局初始化函数,循环创建多个线程,并将每个线程和一个线程号绑定(在pthread_create中作为第四个参数传入)

    int Init(int thread_num) {
        int ret = 0;
    
        pthread_t *thread_list = (pthread_t *)calloc(thread_num, sizeof(pthread_t));
    
        pthread_attr_t a;
        pthread_attr_init(&a);
        pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
    
        for (int i = 0; i < thread_num; ++i) {
            ret = pthread_create(thread_list + i, &a, thread_func, (void *)&i);
    
            if(ret != 0) {
                return ret;
            }
        }
    
        return ret;
    }
    
  2. 不同的线程根据绑定的线程号不断查看一个全局变量数组中对应的表示任务状态的元素,当检测到状态为 READY 时就执行一些任务

    struct Task{
        int status;
        // other
    };
    
    Task tasks[MAX_TASK_NUM];
    
    void *thread_func(void *thread_id)
    {
        int i = *((int *)thread_id);
    
        int status = 0;
    
        do {
            status = tasks[i].status;
            if (status == READY) {
    
                // do something
    
                tasks[i].status = DONE;
            }
            else {
                sleep(1);
            }
        }while (status != QUIT);
    }
    
  3. 一个模块初始化函数,用来初始化处理模块,这个处理模块会做一些预处理,然后通过线程来进行最后的操作,这个初始化函数接受一个线程号作为参数

    struct Env{
        int thread_id;
        // other
    };
    
    void Prepare(Env *env, int thread_id)
    {
        env->thread_id = thread_id;
        // other
    }
    
  4. 一个模块处理函数,在初始化一些环境、进行一些预处理后,将对应线程的状态置为 READY ,然后等待直到对应线程处理完毕

    void Run(Env *env)
    {
        // do preprocessing
        int i = env->thread_id;
        tasks[i].status = READY;
    
        int status;
        do {
            sleep(1);
            status = tasks[i].status;
        } while (status != DONE);
    }
    

我遇到的问题就是,在将第 i 个线程对应的状态置为 READY 后,线程函数并没有进行相应的操作(偶尔)。

加锁

一开始,我并没有对全局变量的读写进行加锁,因此怀疑是处理函数中对状态成员变量的修改没有成功。于是我将所有对全局变量数组中状态成员的读写操作都加了锁,但是问题仍然存在。

取消编译器优化

考虑到对全局数组的状态成员的读、写操作有部分是在循环中进行的,因此怀疑gcc在编译器时对程序进行了优化,于是用 volatile 修饰了状态成员

struct Task{
    volatile int status;
    // other
};

然而这个方法仍然无效。

局部变量,循环变量

和项目组组长讨论后,他也表示暂时没发现这个问题的根源,不过他建议我将 线程函数 访问的 全局数组元素的地址处理函数 中访问的 全局数组元素的地址 打印出来对比一下。我听从了组长的建议,这样做了,然后果然发现了问题。

在出错时,这两个地址是不一样的。随后,我将 thread_func 函数与 Run 函数中的变量 i 的值打印出来,发现在出错的情况下,这两个值不相等。静下心来思考了一会后,我发现了问题的根源所在,即全局初始化函数 Init 中创建线程这一段:

for (int i = 0; i < thread_num; ++i) {
    ret = pthread_create(thread_list + i, &a, thread_func, (void *)&i);

    if(ret != 0) {
        return ret;
    }
}

pthread_create 函数接受的第四个参数是作为线程函数 thread_func 的参数的,而在这里,我将既是 局部变量 也是 循环变量i地址值 作为 pthread_create 函数的第四个参数。

在测试时,为了方便,在执行 Init 函数时,我只让它创建了一个线程,即对函数:

int Init(int thread_num);

传入的 thread_num 参数的值为 1

首先,作为循环变量, Init 中的变量 i 在循环结束时值为 1 ,而在 Run 函数中,表示线程号的变量 i 的值则为 0

其次,主线程与子线程之间运行的先后次序是不确定的。

最后, Init 函数中的变量 i 是局部变量,在 Init 函数执行完后,其对应地址的值是不能保证的。

总之这样的做法是大错特错的。在我将上述循环创建线程的代码段修改为如下内容后,本文所述的问题得到了彻底的解决:

for (int i = 0; i < thread_num; ++i) {
    tasks[i].thread_id = i;
    ret = pthread_create(thread_list + i, &a, thread_func, (void *)&(tasks[i].thread_id));

    if(ret != 0) {
        return ret;
    }
}

注意,这里的 thread_id 是在 Task 结构中新添加的 int 类型成员

C++函数内部new操作并返回对象给外部导致的内存泄漏

2014年5月9日 08:00

最近在为公司一个项目的一个模块写API,在API基本完成后,我写了几个demo把各种情况测试了一下。通过简单测试后,一时兴起,用valgrind进行了一下内存检查,真是不看不知道,一看吓一跳。

mem-leak.jpg

当时为了更快地看到结果,只拿了一个小的音频文件来进行处理,大概就几百个KB吧,然后内存泄漏的量就达到了3,481,396个字节!

花了一两天的时间,借助valgrind以及代码阅读工具(sublime text + ctags),我发现了内存泄漏的源头。

一方面,有项目本身的代码编写不规范导致的泄漏;另一方面,也有项目使用的开源库自身的问题。

项目本身存在的问题有:

  1. new/delete 操作不成对
  2. 拷贝构造函数/赋值运算符重载 在进行拷贝时只进行了浅拷贝

项目本身的问题不太多,而且容易发现,所以被我优先解决了,此时内存泄漏的量减少了一半左右。

接下来进入正题,在我们项目使用的开源库中,存在大量的同一个问题。

对于下面这个类

class A
{
public:
    A &create() {A *p = new A; return *p;}
    virtual ~A();
private:
    A();
};

首先,对于类 A 的方法 create ,如果按下面的形式调用:

A a;
A b = a.create();

是必然会发生内存泄漏的,因为 b 不是引用类型,在赋值时发生的是拷贝操作, create() 内部产生的指针已经被丢失了。所幸这种行为在项目以及项目使用的开源库中并没有发现。但在该开源库自身的代码中,大量存在另外一种错误。

A a;
A &b = a.create();

使用引用对象来接受 create() 返回的对象是正确的,但是,由于对象所使用的内存空间是通过 new 得到的,也就是说,其占用的内存,是在堆上的,对于这样的内存空间,如果不手动进行回收,则在程序退出时也不会被回收。也就是说,下面的操作才是正确的:

A a;
A &b = a.create();
// ...
delete &b;

然而很不幸的是,这个开源库里面对于大量调用 create() 及类似方法返回的对象,都没有进行回收,这是它自身的问题。如果要彻底解决目前我面临的内存泄漏问题,只能去修改这个开源库了,想想就觉得是一件很麻烦的事情_(:3」∠)_

C语言字符串操作

2014年3月6日 08:00

string.h

其实在C语言的标准库中,也有非常丰富的字符串操作函数。当然了,由于C语言中 字符串 并不是基本数据类型,也没有 这个概念,相对来说操作上可能没有Python/Java之类的语言方便。不过了解一下C语言中的字符串操作还是有意义的。

C语言中的字符串操作函数在 string.h 中,不过要了解都有什么函数,阅读string.h并不是什么好的方式。如果是在Unix/Linux系统下,只要执行:

man string

就可以了解都有哪些字符串操作函数了,如下图所示:

string_manual.png

其中一些常用的函数及其大致功能如下(具体细节后面再细说):

  • 字符串拷贝

    stcpy, strncpy

  • 字符串比较

    strcmp, strncmp, strcasecmp, strncasecmp

  • 字符串连接

    strcat, strncat

  • 字符查找

    strchr, strrchr, strchrnul, strpbrk

  • 建立字符串副本

    strdup, strndup, strdupa, strndupa

  • 字符串分割

    strsep, strtok, strtok_r

  • 字符串匹配

    strstr

下面根据功能的不同来展示各个函数的用法。这里我会用一些实例来进行示范,同时,其结果由org-babel对代码块求值得到。

字符串拷贝(strcpy, strncpy)

strcpy

函数原型:

char *strcpy(char *dest, const char *src);

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char dest[1024] = {0};
    char *src = "abcde";

    strcpy(dest, src);

    printf("%s\n", dest);
    return 0;
}

结果:

abcde

strcpy()函数会将源字符串中的结束符('\0')也拷贝到目的字符串中。

注意,strcpy()可能会导致溢出。

strncpy

函数原型:

char *strncpy(char *dest, const char *src, size_t n);

该函数从源字符串中拷贝n个字符到目的字符串;如果源字符串长度不足,则用 NULL 填充,以保证将n个字符写入目的字符串中;如果源字符串中前n个字符不包含字符串结束符,函数不会为目的字符串添加上结束符。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char dest[7] = {0};
    char *src = "abcde";

    dest[5] = 'A';
    strncpy(dest, src, 5);

    printf("%s\n", dest);
    return 0;
}

结果:

abcdeA

所以如果有需要,应该在拷贝后自己在目的字符串尾部添加结束符。

字符串比较(strcmp, strncmp, strcasecmp, strncasecmp)

strcmp

函数原型:

int strcmp(const char *s1, const char *s2);

如果s1小于s2,函数返回一个负数;如果s1等于s2,函数返回0;否则返回一个正数。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s1 = "abcde";
    char *s2 = "abcef";
    char *s3 = "ad";

    printf("compare(%s, %s) -> %d\n", s1, s2, strcmp(s1, s2));
    printf("compare(%s, %s) -> %d\n", s1, s3, strcmp(s1, s3));
    printf("compare(%s, %s) -> %d\n", s2, s3, strcmp(s2, s3));

    return 0;
}

结果:

compare(abcde, abcef) -> -1
compare(abcde, ad) -> -2
compare(abcef, ad) -> -2

从这个结果可以发现,strcmp()是根据字典序来对字符串进行比较的。进一步的,还可以发现strcmp()的返回值是比较过程中最后一次比较时两个字符的值的差,如比较"abcde"和"abcef",有:

a - a = 0
b - b = 0
c - c = 0
d - e = -1 #按字典序,大小已分,不再比较

strncmp

函数原型:

int strncmp(const char *s1, const char *s2, size_t n);

和strcmp()的区别是,strncmp()只对s1和s2的前n个字节进行比较。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s1 = "abcde";
    char *s2 = "abcfg";
    char *s3 = "abd";

    printf("compare(%s, %s, 4) -> %d\n", s1, s2, strncmp(s1, s2, 4));
    printf("compare(%s, %s, 2) -> %d\n", s1, s3, strncmp(s1, s3, 2));
    printf("compare(%s, %s, 3) -> %d\n", s2, s3, strncmp(s2, s3, 3));

    return 0;
}

结果:

compare(abcde, abcfg, 4) -> -2
compare(abcde, abd, 2) -> 0
compare(abcfg, abd, 3) -> -1

strcasecmp

函数原型:

int strcasecmp(const char *s1, const char *s2);

strcasecmp()也是用来比较字符串的,和strcmp()有两点区别:

  1. 使用strcasecmp()应该包含 strings.h 而不是 string.h
  2. strcasecmp()在比较时不区分大小写

示例:

#include <stdio.h>
#include <string.h>
#include <strings.h>

int main(int argc, char *argv[])
{
    char *s1 = "AbcdE";
    char *s2 = "abcdE";

    printf("compare(%s, %s) with case -> %d\n", s1, s2, strcmp(s1, s2));
    printf("compare(%s, %s) ignore case -> %d\n", s1, s2, strcasecmp(s1, s2));

    return 0;
}

结果:

compare(AbcdE, abcdE) with case -> -32
compare(AbcdE, abcdE) ignore case -> 0

strncasecmp

strncasecmp()之于strcasecmp()就如strncmp()之于strcmp(),不再赘述。

字符串连接(strcat, strncat)

strcat

函数原型:

char *strcat(char *dest, const char *src);

strcat()首先会覆盖掉目的字符串的结束符,然后把源字符串的内容追加到后面,并在最后添加结束符。如果目的字符串缓冲区长度不够,将导致溢出。

strcat()在操作完成后,返回目的字符串的首地址,这样可以方便地进行链式操作。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char dest[1024] = "hello ";
    char *src = "world!";

    printf("%s\n", strcat(dest, src));

    return 0;
}

结果:

hello world!

strncat

函数原型:

char *strncat(char *dest, const char *src, size_t n);

strncat()将最多n个字节的内容追加到目的字符串尾部,并且会在追加后添加终止符号。

同strcat()一样,它返回目的字符串的首地址。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char dest[1024] = "hello ";
    char *src = "world!lkjsdljsd";

    printf("%s\n", strncat(dest, src, 6));

    return 0;
}

结果:

hello world!

字符查找(strchr, strrchr, strchrnul, strpbrk)

strchr

函数原型:

char *strchr(const char *s, int c);

strchr()返回一个字符指针,指向指定字符在指定字符串中第一次出现的位置。如果在指定字符串中没有找到指定字符,则返回 NULL 。该函数的第二个参数按理来说应当是一个字符,不过标准库中确实是int类型。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s = "hello world!";
    char c = 'l';

    printf("%s\n", strchr(s, c));

    return 0;
}

结果:

llo world!

strrchr

函数原型:

char *strrchr(const char *s, int c);

strrchr()和strchr()类似,但它返回的是指定字符在指定字符串中最后一次出现的位置。如果未找到,同样返回 NULL

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s = "hello world!";
    char c = 'l';

    printf("%s\n", strrchr(s, c));

    return 0;
}

结果:

ld!

strchrnul

函数原型:

char *strchrnul(const char *s, int c);

strchrnul()的功能和strchr()只有细微的区别,那就是,当没有找到指定字符时,strchrnul()不返回 NULL ,而是返回字符串结束符的位置。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s = "abcde";
    char c = 'm';

    printf("%p, %p\n", s, strchrnul(s, c));

    return 0;
}

结果

0x40065c, 0x400661

这里由于strchrnul()的特性,没办法通过打印字符串来了解strchrnul()的操作,不过观察这两个指针的值,会发现:

0x400661 - 0x40065c = 0x5

而字符串s的第六个元素(从0开始,5即第六个),正好是结束符。

strpbrk

函数原型:

char *strpbrk(const char *s, const char *accept);

strpbrk()和strchr()的区别在于,strchr()是从字符串里搜索 一个字符 ,而strpbrk()则是在字符串里搜索 一个字符集中的字符 ,看第二个参数就明白了。strpbrk()遍历字符串,如果发现某个字符在指定的 字符集 中,则立即返回指向该字符的指针。如果最后没有找到任何在指定字符集中的字符,则返回 NULL

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s = "Hello World!";
    char *accept = "Wo";

    printf("%s\n", strpbrk(s, accept));

    return 0;
}

结果:

o World!

字符串分割(strtok, strtok_r, strsep)

strtok

函数原型:

char *strtok(char *str, const char *delim);

strtok()根据第二个参数指定的分隔符(可能存在多个不同的分隔符)将指定字符串分割成多个子串。通过多次调用strtok(),可以依次获得字符串的多个子串的首地址。要注意的是,除了第一次调用时将待分割字符串作为第一个参数,后续的调用要将第一个参数置为 NULL 。当字符串已经无法再分割时,strtok()返回 NULL

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char s[1024] = "abc;lsdk:lskdj,;slsj";
    char *delm = ";:,";
    char *result = NULL;
    int len = strlen(s);
    int i = 0;

    result = strtok(s, delm);
    while (result != NULL) {
        printf("Source:%s, Sub:%s\n", s, result);
        result = strtok(NULL, delm);
    }

    return 0;
}

结果:

Source:abc Sub:abc
Source:abc Sub:lsdk
Source:abc Sub:lskdj
Source:abc Sub:slsj

除了上面说过的strtok()的用法外,还要注意的是,作为待分割的字符串,它必须是 可更改的 。否则虽然可以通过编译,但运行会出错。要理解这个现象,首先要了解strtok()的内部机制。

了解其机制,没必要去寻找其实现源代码,只要对它的操作过程进行剖析就知道了。先看下面的代码:

#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
    char s[64] = "To be or not to be";
    char *delm = " ,.";         /* 分隔符:空格 */
    char *result = NULL;
    int i = 0, len = strlen(s);

    for (i = 0; i < len; ++i) { /* 逐个打印s中的字符 */
        printf("%c ", s[i]);
    }
    printf("\n");


    for (i = 0; i < len; ++i) { /* 逐个打印s中字符的数值 */
        printf("%d ", (int)s[i]);
    }
    printf("\n");

    result = strtok(s, delm);
    while (result != NULL) {    /* 观察s中字符数值的变化 */

        for (i = 0; i < len; ++i) {
            printf("%d ", (int)s[i]);
        }
        printf("\n");

        result = strtok(NULL, delm);
    }
    return 0;
}

结果:

T o   b e   o r   n o t   t o   b e
84 111 32 98 101 32 111 114 32 110 111 116 32 116 111 32 98 101
84 111 0 98 101 32 111 114 32 110 111 116 32 116 111 32 98 101
84 111 0 98 101 0 111 114 32 110 111 116 32 116 111 32 98 101
84 111 0 98 101 0 111 114 0 110 111 116 32 116 111 32 98 101
84 111 0 98 101 0 111 114 0 110 111 116 0 116 111 32 98 101
84 111 0 98 101 0 111 114 0 110 111 116 0 116 111 0 98 101
84 111 0 98 101 0 111 114 0 110 111 116 0 116 111 0 98 101

可以看到,s中的分隔符,逐次地被置为'\0'即字符串结束符。这就是strtok()分割字符串的内部原理了。而strtok()返回的指针,其实就是s中各个子串的起始位置了。如果s指向的内容是无法被修改的,那么strtok()自然也就无法将原先的分隔符置为字符结束符了。

当然了,由于源字符串会被修改,在实际中,如果需要,可以用strdup()来建立一个源字符串的副本。

strtok_r

函数原型:

char *strtok_r(char *str, const char *delim, char **saveptr);

strtok_r()是Linux下的strtok()的可重入版本(线程安全版本),它比strtok()多了一个参数 saveptr ,这个参数用于在分割字符串时保存上下文。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char s[64] = "Hello world";
    char *delm = " ";
    char *result = NULL, *ptr = NULL;

    printf("Source:%p\n", s);
    result = strtok_r(s, delm, &ptr);
    while (result != NULL) {
        printf("Result:%p\t", result);
        printf("Saveptr:%p\n", ptr);
        printf("---%s\t", result);
        printf("---%s\n", ptr);

        result = strtok_r(NULL, delm, &ptr);
    }
    return 0;
}

结果:

Source:0x7fff180f3de0
Result:0x7fff180f3de0 Saveptr:0x7fff180f3de6
---Hello              ---world
Result:0x7fff180f3de6 Saveptr:0x7fff180f3deb
---world              ---

可以看到,saveptr这个指针在每次调用strtok_r()后就指向了未分割的部分的首地址。相对地,strtok()则是在内部有一个静态缓冲区,通过这个静态缓冲区来记录未处理的起始位置,所以strtok()不是线程安全的。

strsep

函数原型:

char *strsep(char **stringp, const char *delim);

strsep()同样是字符串分割函数,它和strtok()的不同之处在于,它会直接修改待分割的指针的值,让它始终指向未处理部分的起始位置。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char s[64] = "To be or not to be";
    char *source = s;
    char *delm = " ";
    char *result = NULL;

    while (source != NULL) {
        printf("Source:%s | ", source);
        result = strsep(&source, delm);
        printf("result:%s\n", result);
    }
    return 0;
}

结果

Source:To be or not to be | result:To
Source:be or not to be | result:be
Source:or not to be | result: or
Source:not to be | result: not
Source:to be | result: to
Source:be | result: be

因为和strtok()的这个不同之处,strsep不需要区分第一次调用后后续的连续调用,可以用统一的操作来对字符串进行分割。

字符串匹配(strstr)

函数原型:

char *strstr(const char *haystack, const char *needle);

strstr()返回字符串needle在字符串haystack中第一次出现的位置;如果没有匹配,则返回 NULL

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s = "To be or not to be.";
    char *p = "be";

    printf("%s\n", strstr(s, p));
    return 0;
}

结果:

be or not to be.

字符串副本创建(strdup, strndup, strdupa, strndupa)

strdup

函数原型:

char *strdup(const char *s);

strdup()调用malloc()分配一块内存并将字符串s的内容拷贝进去,产生s的副本。要注意的是,在最后应该调用free()来释放副本。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s = "abcde";
    char *dup = strdup(s);
    printf("%s\n", dup);
    free(dup);
    return 0;
}

结果:

abcde

strndup

函数原型:

char *strndup(const char *s, size_t n);

strndup()和strdup()类似,但最多只拷贝s的前n个字节。如果s的长度大于n,还会在副本后添加终止符。

示例:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *s = "abcde";
    char *dup = strndup(s, 4);

    printf("%s\n", dup);

    free(dup);
    return 0;
}

结果:

abcd

strdupa

函数原型:

char *strdupa(const char *s);

strdupa()和strdup()类似,但在分配内存时,它使用alloca()而不是malloc()。

strndupa

函数原型:

char *strndupa(const char *s, size_t n);

strndupa()之于strdupa()就如strndup()之于strdup(),不再赘述。

在Heroku上部署Tornado应用

2013年12月6日 08:00

Heroku

heroku.jpg

Heroku 是一个云平台(Platform as a Service, PaaS),提供“无需准备的部署服务”。在Heroku上托管应用,所需的只是对应用本身的开发和管理,应用运行的环境以及安全防护则会由Heroku来进行,此外Heroku上还有非常丰富的Add-ons,即一些扩展服务(有些是免费的有些是收费的),对应用开发和管理非常有用。

免费的Heroku应用只有64MB的内存和512MB的存储,如果想部署大型的应用,最好还是付费的好。不过话说回来,每个账户可以创建的应用好像没有限制。对于个人而言,在Heroku上部署一些小应用,或者个人博客,都是可以一试的——目前已经有不少朋友在上面部署自己的个人博客了。

Tornado

Tornado 是一个Python的Web框架,也是一个python的异步网络库,最初由FriendFeed开发——FriendFeed现已被Facebook收购。

部署Tornado

Heroku 支持Ruby、Python、Java等多种类型的应用,不过Heroku的官方文档上只有对部署Flask的示例,并没有部署Tornado的说明。而实际上,部署Tornado应用和部署其他类型的应用一样,也是很简单的。

要部署Tornado应用到Heroku上,首先需要一个编写正确可用的Tornado应用(关于Heroku的使用将在另一文中细说),在此基础上在应用根目录下准备好以下几个配置文件:

  • requirements.txt

    该配置文件用于指示Python应用的组件依赖,详细文档 在此

    requirements.txt 的内容应该为:

    Tornado==3.1.1     # 假定应用使用3.1.1版本的Tornado,注意这里是"=="而不是"="
    ...                # 其他依赖
    
  • Procfile

    Procfile 用于告诉Heroku处理类型以及要运行的命令,详细文档 在此。 其内容通常为如下形式:

    <process type>: command
    

    假定要部署的Tornado应用的主模块为main.py,则Profile的内容可能为:

    web: python main.py --port=$PORT
    
  • runtime.txt

    runtime.txt 用于指定Python应用使用的Python版本,举例来说,若要部署的Tornado应用要使用2.7.4版本的Python,则runtime.txt的内容应该为:

    python-2.7.4
    

    Heroku支持的Python版本为:

    • python-2.7.4
    • python-3.3.2
    • pypy-1.9 (我也不知道这是什么……)

    如果应用目录下没有runtime.txt,则Heroku默认使用python-2.7.4。

《ANSI Common Lisp》第三章习题

2013年11月12日 08:00
  1. Show the following lists in box notation:

    (a) (a b (c d))
    (b) (a (b (c (d))))
    (c) (((a b) c) d)
    (d) (a (b . c) . d)
    

    My answer:

    (a) (a . (b . ((C . (D . NIL)) . NIL)))

    (b) (a . ((B . ((C . ((D . NIL) . NIL)) . NIL)) . NIL))

    (c) (((A . (B . NIL)) . (C . NIL)) . (D . NIL))

  2. Write a version of union that preserves the order of the elements in the original lists:

    > (new-union '(a b c) '(b a d))
    (A B C D)
    

    My answer:

    (defun new-union (first second)
      (dolist (obj second)
        (if (not (member obj first))
            (append first (list obj)))))
    
  3. Define a function that takes a list and returns a list indicating the number of times each (eql) element appears, sorted from most common element to least common:

    > (occurrences '(a b a d a c d c a))
    ((A . 4) (C . 2) (D . 2) (B . 1))
    

    My answer:

    (defun occurrence (lst)
      (let ((result nil))
        (dolist (obj lst)
          (if (null (assoc obj result))
          (push (cons obj 1) result)
          (incf (cdr (assoc obj result)))
        ))
        (sort result #'(lambda (a b) (if (> (cdr a) (cdr b)) t nil)))))
    
  4. Why does (member '(a) '((a) (b))) return nil?

    My answer:

    因为每次产生cons调用时系统都会分配新的对象,所以'(a)和'((a) (b))中的第一个元素虽然具有同样的值,但却不是同一个对象,而member函数使用的判定函数是eql。

  5. Suppose the function pos+ takes a list and returns a list of each element plus its position:

    > (pos+ '(7 5 1 4))
    

    Define this function using (a) recursion, (b) iteration, (c) mapcar.

    My answer:

    (a) Recursive version

    (defun rec-pos-plus (lst pos) 
      (if (not (null lst))
          (progn
        (incf (car lst) pos)
        (rec-pos-plus (cdr lst) (1+ pos)))))
    
    (defun rec-pos+ (lst)
      (rec-pos-plus lst 0)
      lst)
    

    (b) Iterative version

    (defun iter-pos+ (lst)
      (do ((i 0 (+ i 1)))
          ((= i (length lst)) lst)
        (incf (nth i lst) i)
        ))
    

    (c) Version using mapcar

    (defun pos+ (lst)
      (mapcar #'(lambda (x) (incf x (position x lst)))
          lst))
    
  6. After yeas of deliiberation, a government commission has decided that lists should be represented by using the cdr to point to the first element and the car to point to the rest of the list. Define the government versions of the following functions:

    (a) cons
    (b) list
    (c) length (for lists)
    (d) member (for lists; no keywords)
    

    My answer:

    (a) cons

    (defun cons (x y)
      (let ((result '(nil . nil)))
        (setf (cdr result) x)
        (setf (car result) y)))
    

    (b) list

    (defun list (&rest arg)
      arg)
    

    (c) length (for lists)

    (defun length (lst)
      (if (null lst)
          0
          (+ 1 (length (car lst)))))
    

    (d) member (for lists; no keywords)

    (defun member (element list)
      (cond ((null list) nil)
            ((eql element (cdr list)) list)
            (t (member element (car list)))))
    
  7. Modify the program in Figure 3.6 to use fewer cons cells. (Hing: Use dottedlists.)

    My answer:

    (defun n-elts (elt n)
      (if (> n 1)
          (cons n elt)
          elt))
    
    (defun compr (elt n lst)
      (if (null lst)
          (list (n-elts elt n))
          (let ((next (car lst)))
        (if (eql next elt)
            (compr elt (+ n 1) (cdr lst))
            (cons (n-elts elt n)
              (compr next 1 (cdr lst)))))))
    
    (defun compress (x)
      (if (consp x)
          (compr (car x) 1 (cdr x))
          x))
    
  8. Define a function that takes a list and prints it in dot natation:

    > (showdots '(a b c))
    (A . (B . (C . NIL)))
    NIL
    

    My answer:

    (defun showdots (list)
    (cond ((null list) nil)
      ((consp list) (progn
             (format t "(")
             (showdots (car list))
             (format t " . ")
             (if (null (cdr list))
                 (format t "NIL")
                 (showdots (cdr list)))
             (format t ")")
             ))
      (t (format t "~a" list))))
    
  9. Write a program to find the longest finite path through a network represented as in Section 3.15. The network may contain cycles.

    My answer(对shortest-path的改写):

    (defun new-paths (path node net)
      (mapcar #'(lambda (n) 
                   (if (not (member n path)) ;判断下一层节点是否包含在路径中以削除环的影响
                       (cons n path)))
              (cdr (assoc node net))))
    
    (defun bfs (end quene net)
      (if (null quene)
          nil
          (let ((path (car quene)))
            (let ((node (car path)))
              (if (eql node end)
                  (reverse path)
                  (bfs end
                       (sort (append (cdr quene) ;排序以保证最长的路径在队列最前方
                                     (new-paths path node net))
                             #'>
                             :key #'length)
                       net))))))
    
    (defun longest-path (start end net)
      (bfs end (list (list start)) net))
    

= =写个题比拿半生不熟的英文来记笔记还累……

《ANSI Common Lisp》第二章习题

2013年11月9日 08:00
  1. Describe what happens when the following expressions are evaluated:

    (a) (+ (- 5 1) (+ 3 7))
    (b) (list 1 (+ 2 3))
    (c) (if (listp 1) (+ 1 2) (+ 3 4))
    (d) (list (and (listp 3) t) (+ 1 2))
    

    My answer:

    (a) 14

    (b) (1 5)

    (c) 7

    (d) (NIL 3)

  2. Give three distinct cons expressions that return (a b c).

    My answer:

    (cons 'a '(b c))
    (cons 'a (cons 'b '(c)))
    (cons 'a (cons 'b (cons 'c nil)))
    
  3. Using car and cdr,define a function to return the fourth element of a list.

    My answer:

    (defun fourth (lst)
      (if (listp lst)
        (car (cdr (cdr (cdr lst))))))
    
  4. Define a function that takes two arguments and returns the greater of the two.

    My answer:

    (defun greater (a b)
      (if (equal (type-of a)
                 (type-of b))
          (cond ((typep a 'number) (if (> a b) a b)
                ((typep a 'character) (if (char> a b) a b))
                ((typep a 'string) (if (string> a b) a b))
                (t nil)))
          nil))
    
  5. What do hese functions do?

    (a)

    (defun enigma (x)
      (and (not (null x))
           (or  (null (car x))
                (enigma (cdr x)))))
    

    (b)

    (defun mystery (x y)
      (if (null y)
          nil
          (if (eql (car y) x)
              0
              (let ((z (mystery x (cdr y))))
                (and z (+ z 1))))))
    

    My answer:

    (a) 该函数用于判断一个非空的list中是否含有 NIL 元素.

    (b) 判断y中是否有元素和x相等(同一个对象),若无则返回NIL,若有则返回该元素在y中的索引(以0为初始值).

  6. What could occur in place of the x in each of the following exchanges?

    (a) > (car (x (cdr '(a (b c) d))))
        B
    (b) > (x 13 (/ 1 0))
        13
    (c) > (x #'list 1 nil)
        (1)
    

    My answer:

    (a) cdr.

    (b) or.

    (c) apply.

  7. Using only operators introduced in this Chapter, define a function that takes a list as an argument and returns true if one of its elements is a list.

    My answer:

    (defun have-list-member (lst)
      (if (null lst)
          nil
          (if (listp (car lst))
              t
              (have-list-member (cdr lst)))))
    

    注:本章出现的操作符有:

    + / - quote list cons car cdr third listp null not if and or defun > eql read format let defparameter defconstant setf remove do dolist progn function apply funcall lambda typep
    
  8. Give iterative and recursive definitions of a function that

    (a) takes a positive integer and prints that many dots.

    (b) takes a list and returns the number of times the symblo a occurs in it.

    My answer:

    (a) dot-plot

    • Iteractive version

      (defun dot-plot-iter (n)
        (do ((n n (- n 1)))
            ((= n 0) 'done)
          (format t ".")))
      
    • Recursive version

      (defun dot-plot-rec (n)
        (if (= n 0)
            'done
            (progn
              (format t ".")
              (dot-plot-rec (- n 1)))))
      

    (b) times-of-a

    • Iteractive version

      (defun bfs (lst)                     ;广度优先遍历,得到lst的所有树叶节点的列表
        (let ((stack (list lst))           ;遍历所需栈
              (leaves nil))                ;存放树叶节点的列表
          (do ()
              ((null stack) leaves)        ;当栈空时,遍历结束
            (let ((now (pop stack)))       ;将栈顶节点出栈
              (if (listp now)              ;若出栈节点不为树叶节点时,将其子树入栈
                  (progn (push (car now) stack)
                         (if (not (null (cdr now)))   ;这里要判断cdr是否为nil,否则将其入栈后
                             (push (cdr now) stack))) ;listp对其判断的结果为真,导致无穷递归
                  (push now leaves))))))   ;若出栈节点为树叶节点,将其放入leaves列表中
      
      (defun times-of-a-iter (lst)
        (let ((leaves (bfs lst))
              (times 0))
          (dolist (obj lst)
            (if (equal 'a obj)
                (setf times (+ times 1))))
          times))
      
    • Recursive version

      (defun times-of-a-rec (lst)
        (if (null lst)
             0
            (let ((now (car lst)))
              (+ (if (listp now)
                     (times-of-a-rec now)
                     (if (equal now 'a) 1 0))
                 (times-of-a-rec (cdr lst))))))
      
  1. A friend is trying to write a function that returns the sum of all the non-nil elements in a list. He has written two versions of this function, and neither of them work.Explain what's wrong with each, and give a correct version:

    (a)

    (defun summit (lst)
      (remove nil lst)
      (apply #'+ lst))
    

    (b)

    (defun summit (lst)
      (let ((x (car lst)))
        (if (null x)
            (summit (cdr lst))
            (+ x (summit (cdr lst))))))
    

    My answer:

    (a) remove是没有副作用(side-effect)的,所以lst中的 nil 元素还在 lst中,正确的代码应为

    (defun summit (lst)
      (apply #‘+ (remove nil lst)))
    

    (b) 没有递归终止条件,正确的代码应为:

    (defun summit (lst)
      (if (null lst)
          0
          (let ((x (car lst)))
            (if (null x)
                (summit (cdr lst))
                (+ x (summit (cdr lst)))))))
    

伪语音合成(2)

2013年10月20日 08:00

今天实现了中英文的选择,当然,并没有对可能出现的错误情况进行处理,比如说选项指明是英文而文本内容是中文……

首先,昨天写的那个脚本有点问题,先来看一下:

#!/bin/bash
t2s() {
    wget -q -U Mozilla -O $(tr ' ' _ <<< "$1"| cut -b 1-15).mp3 "http://translate.google.com/translate_tts?ie=UTF-8&tl=en&q=$(tr ' ' + <<< "$1")";
}

FILE=$(tr ' ' _ <<< "$1" | cut -b 1-15).mp3;
t2s $1 && play $FILE -q && rm $FILE

当文本是用空格分开时,这个脚本会出错,原因是传递给函数的参数会严格按照空格进行划分,也就是说,类似

'how are you'

这样本来预期作为t2s函数的第一个参数会被分割成 how、are、you三个部分并将其作为t2s函数的 三个参数 ,导致错误。

正确的做法是将$1用双引号包裹起来。

这是今天修改后的脚本代码:

#!/bin/bash

FILE=""

t2s()
{
    first="$1";
    lang="";
    FILE=$(tr ' ' _ <<< "$first"| cut -b 1-15).mp3;

    shift;

    while getopts "ez" arg
    do
    case $arg in
        e)
        lang="en";
        ;;
        z)
        lang="zh";
        ;;
        ?)
        echo "Usage : t2s <text> -e/-z";
        echo "   -e : in English";
        echo "   -z : in Chinese";
        exit 1;
        ;;
    esac
    done

    wget -q -U google-chrome -O $FILE "http://translate.google.com/translate_tts?ie=UTF-8&tl=$lang&q=$(tr ' ' + <<< "$first")";
}

t2s "$1" $2 && play $FILE -q && rm $FILE

当然了……脚本参数的处理是很直接简单的处理方式。

另外,Google的中文语音合成效果不怎么样啊,不过其他的语音产品貌似不提供像Google这样的API,那也没办法了。

伪语音合成(1)

2013年10月19日 08:00

今天在commandlinefu.com 上面看到一个函数的定义,挺有意思的,来看一下:

t2s() { wget -q -U Mozilla -O $(tr ' ' _ <<< "$1"| cut -b 1-15).mp3 "http://translate.google.com/translate_tts?ie=UTF-8&tl=en&q=$(tr ' ' + <<< "$1")"; }

下面注明了:

Usage: t2s 'how are you?'

这个函数很有意思,首先看这个函数的名字:t2s。

t2s是 Text to Speech 的缩写,也就是 语音合成 了,这个函数的功能正是语音合成,即将传入t2s函数的参数——文本,转换为语音。

再看看函数体:

wget -q -U Mozilla -O $(tr ' ' _ <<< "$1"| cut -b 1-15).mp3 "http://translate.google.com/translate_tts?ie=UTF-8&tl=en&q=$(tr ' ' + <<< "$1")";

没什么太复杂的东西,就是一条wget命令,不过其参数稍微复杂。

-q 选项是让wget以 quiet 模式运行, -U 指定浏览器(我也不知道为什么要指定,试了一下,不加的话结果会是错的……), -O 指定下载下来的文件名。

-O 选项的参数是一条shell命令的输出结果和 .mp3 这个后缀名的连接起来的结果:

tr ' ' _ <<< "$1" | cut -b 1-15

这条命令将函数t2s的第一个参数中的空格替换成下划线 _ ,然后最多取15个字符。

重点是后面那个网络地址:

http://translate.google.com/translate_tts?ie=UTF-8&tl=en&q=

这个是Google翻译的TTS的web api,通过它可以直接获取Google翻译界面的文本朗读功能对应的mp3文件。上面这个api要在后面加上要朗读的单词才是完整的——若有多个单词,则以 + 号连接。

然后我根据这个函数写了一个小脚本:

#!/bin/bash
t2s() {
    wget -q -U Mozilla -O $(tr ' ' _ <<< "$1"| cut -b 1-15).mp3 "http://translate.google.com/translate_tts?ie=UTF-8&tl=en&q=$(tr ' ' + <<< "$1")";
}

FILE=$(tr ' ' _ <<< "$1" | cut -b 1-15).mp3;
t2s $1 && play $FILE -q && rm $FILE

我这里用了play命令来直接在终端中播放音频文件——play命令是sox工具包里的一个命令,要正确播放,还要安装mp3格式支持:

sudo apt-get install libsox-fmt-mp3

之所以标题是 伪语音合成 ,就是因为我这里只是单纯地下载了对应的音频文件进行播放而已……真正的语音合成的工作都是Google做的。

另外,这个脚本里的t2s函数只对英文语句起作用,所以下一步我想写一个功能更丰富的脚本,能处理中英文。

使用elisp编写脚本

2013年10月9日 08:00

起因

我通过Github Pages有了自己的一个静态博客,并且开始正儿八经地写博客。

但我碰到了一个问题:每次我需要写博客,我要先打开emacs,按下C-x C-f,然后输入一长串路径名和文件名(org文件),然后手动输入YAML Front Matter[1]。路径名每次都是一样的,YAML Front Matter也是一样的,这都是重复性的工作,并且我又是一个很懒的人,所以我就想偷懒了。

起初是想写个shell脚本来简化步骤而已,开始写脚本后,我就想,应该把publish的步骤也通过脚本进行简化(虽然这并不是很复杂的步骤),然后我发现shell不能做到这一点,唯一的选择是elisp。

过程

用elisp写脚本?这事我听说过,但我没做过,也就是说我也是大姑娘上轿头一回了。

照例google。

基本形式没太大问题,将shell脚本Shebang[2]后面的 /bin/bash 改成 /usr/bin/emacs –script ,其他的东西嘛,随便就行不是么?(想当年某次实习招聘的时候我跟面试官说我会shell脚本,结果只会堆命令的真相还是被不留情面地揭穿了(╯‵□′)╯︵┴─┴ )

不过碰到了不少问题。

  1. 之所以用elisp,就是想简化publish么,但是在elisp script里写上的

    (org-publish-project "blog-linusp")
    

    完全不起作用。细想一下应该是shell脚本没有加载我的emacs相关配置。当时真是一筹莫展啊,后来发现其实这是一件不复杂的事情,在之前加上一句

    (load-file "~/.emacs")
    

    不过这样做太凶残了,什么auto-complete、yasnippet、slime、org2blog、metablog全被加载了(╯‵□′)╯︵┴─┴ 。

    好在我有将配置文件分类的习惯,org-mode的相关配置被我写在一个名为 org.el 的文件里,于是加载它就OK了,不过这个文件里还有引用其他的一些emacs插件,于是也都加载了——好吧至少比之前更好是吧。

  2. 如何传递参数给脚本?shell脚本接受参数的话还是懂的怎么做的,可是elisp脚本……

    照例google,然后发现Xah Lee[3]的博客上就有这个内容,原文点

  3. 运行脚本后,发现总会有一堆的Loading message,就像下面这样:

    2013-10-09-loading-message.png

    (╯‵□′)╯︵┴─┴

    经google,该问题没有太好的解决问题,凑合吧。

成果

嘛,虽然代码写得比较丑陋,至少能用,功能上达到了我的预期目标,所以代码的问题,以后再说吧。

附脚本代码

#!/usr/bin/emacs --script

(require 'calendar)
(defun blog (action &rest rst)
  (cond ((string-equal action "post")
     (if (equal rst nil)
         (print "Please input title")
       (let ((post-file (concat (format-time-string "%Y-%m-%d")
                     "-"
                     (car rst)
                     ".org")))
         (progn
           (with-temp-file (concat "~/blog/org/_posts/"
                       post-file)
         (insert (concat "#+begin_html\n"
                 "---\n"
                 "layout     : post\n"
                 "title      : \n"
                 "categories : \n"
                 "tags       : \n"
                 "excerpt    : \n"
                 "---\n"
                 "#+end_html\n")))
           (print (format "Create post file %s in post directory" post-file))
           (shell-command (concat "emacs "
                      "~/blog/org/_posts/"
                      post-file))))))
     ((string-equal action "publish")
      (progn
        (org-publish-project "blog-linusp")
        (print "Project has been published!")))
    (t (print "Action: post/publish"))))

(load-file "~/.emacs.d/site-lisp/xml-rpc.el")
(load-file "~/.emacs.d/site-lisp/wc-mode.el")
(load-file "~/.emacs.d/myplugins/org.el")

(blog (elt argv 0) (elt argv 1))

[1] YAML Front Matter是用来告诉Jekyll使用什么模板并可以附带一些其他信息的文件头

[2] Shebang是一个由'#'和'!'构成的字符串,在文件中存在Shebang的情况下,类Unix操作系统的程序载入器会分析Shebang后的内容,将这些内容作为解释器指令,并调用该指令,并将载有Shebang的文件路径作为该解释器的参数——来自维基百科

[3] Xah Lee是一位emacs大牛,emacs wiki上可是都有他的专门的页面哦

C++中负数取余的规则

2024年8月27日 18:10
本文探讨了C++中负数除法取余的问题,当使用取余操作符对负数进行运算时,结果可能与预期不符。原因是C++的取余操作符与数学上的运算不同。解决方案是先将负数转换为正数进行取余,再调整符号以匹配原数的正负。通过这种方法,可以得到正确的余数。

Python极简美学:一行代码完成的26个日常任务

2024年6月23日 14:24
Python以其简洁优雅著称,能够用最少的代码行数实现强大的功能。本文将通过展示Python如何以一行代码来解决常见的编程任务,从而体验Python的极简美学。通过这些实例,不仅能够学习到Python的基础知识,还能掌握一些高效编码的小技巧。
❌
❌