利用 in 关键字使你的数据结构更友好

最近我突然想到: 在Python中,如果有一个支持使用“in”关键字的Tree对象Tree()会不会很方便呢?这样,我就可以简单地这样做: tree = Tree.from_list([1,2,3,4,5]) ... if val in tree: ... 好消息是,实际上我们是可以做到的!为了使类Tree与“in”方法兼容,我们需要实现__iter__方法。这个方法应该递归返回其子节点,使我们能够遍历整个树。 class Tree: def __init__(self, val, left=None, right=None): self.val = val self.left = left self.right = right def __iter__(self): if self.left: # 可以使用“yield from”来委托迭代给另一个可迭代对象。 yield from self.left yield self.val if self.right: yield from self.right 现在,您可以执行条件检查了! tree = Tree(6, left=Tree(4, left=Tree(2), right=Tree(5)), right=Tree(8)) if 5 in tree: print("Yes!") 如果不用 in, 我们可能需要做一个find方法: if tree.find(5): print("Yes!") 我相信无论哪种方法都很好看懂。使用“in”关键字的好处在于它利用了内置的关键字。

March 10, 2024

什么是熵(Entropy)

互联网上关于统计学里的熵(Entropy)这个概念的解释很多,但是大多数都是用晦涩难懂的术语来解释的,这样的解释对于非数学专业的人来说,很难理解。本文试图用 大白话来解释这个概念。 “彩票中奖了”,“小区停电了”,“打游戏掉线了”,“今天会下雨”,这些都是生活中的常见的事件,哪个事件的发生能更让人感到意外呢?熵(Entropy) 就是一个度量意 外程度的值。 我们知道一个事件,发生的概率越大,我们越不觉得意外。例如,太阳从东边升起这个事件,每天都在发生。我们对它再次从东边升起的意外感应该很小;如果它突然 有一天从西边升起了,我们会大感震惊,意外感就会就很大。 那么怎么衡量这样的意外感的程度呢?最简单的办法就是用事件发生概率的倒数来代表意外程度 $$ \frac{1}{P(x)} $$ 例如中彩票的概率是千分之一。如果这个事件发生了,它的意外度就是 $\frac{1}{0.001} = 1000$;如果明天会下雨的概率是一半一半,那么它的意外程度就是 $\frac{1}{0.5} = 2$ 但是这个算法有个缺陷,那就是对于某些一定会发生的事情,例如明年我会长大一岁的概率是 100%, 它的意外度是 $\frac{1}{1} = 1$。按道理来说,这个事件必然发生,我们应该对其的意外度为0才对。 于是我们给它加上一层 log 函数 $$ log(\frac{1}{P(x)}) $$ 套用这个算法,也能反映意外程度。 中彩票的意外程度为 $log(\frac{1}{0.001}) = 9.96$ 明天会下雨的意外程度为 $log(\frac{1}{0.5}) = 1$ 明年我会长大一岁的意外程度为 $log(\frac{1}{1}) = 0$ 对于随机的事件,我们在考虑它意外程度的时候,不但要看它发生的时候令人意外的程度,还要看它不发生的时候令人感到意外的程度。例如上面的例子中,中彩票的概率是$\frac{1}{1000}$, 不中的概率是 $\frac{999}{1000}$ ,我们分别计算它们的意外程度 中彩票 $log(\frac{1}{0.001}) = 9.96$ 不中彩票 $log(\frac{1}{0.001}) = 0.001$ 中彩票固然会给我们带来意外感,但中彩票是有一个概率的($\frac{1}{1000}$)。我们将中彩票的意外感乘以它的概率;不中彩票的意外感乘以不中彩票的概率;再求和,就得到了买彩票带来意外感的期望值: $$ P(X=中) \times log(\frac{1}{P(X=中)}) + P(X=不中) \times log(\frac{1}{P(X=不中)}) $$ $$ =0.001 \times 9.96 + 0....

November 11, 2023 · Calvin

Python的多线程模块

最近我在复习并发编程相关的知识,由于日常工作我主要使用 Python, 我打算通过阅读和动手实践里面的模块来学习。 关于线程的原理和实现、线程同步问题我就不作介绍了。此类的参考书籍、文章、教学视频网上有很多。我在这里主要是列举 Python 标准库中提供的模块,以及它们的用法。如果你不了解线程、没有听说过生产者消费者模型,本文可能读起来有些吃力。 Lock Python 提供的,最简单的同步方法就是使用锁了。我们一起看看下面这个例子,两个线程一起做算术题: import threading import time count = 0 def counter(): global count while True: print(f'{count} + 1 = {count + 1}') count += 1 threads = [] for i in range(2): t = threading.Thread(target=counter) threads.append(t) t.start() for i in range(2): threads[i].join() 这个例子中,一种打印输出的情况是: 0 + 1 = 1 1 + 1 = 2 2 + 1 = 3 2 + 1 = 3 4 + 1 = 5 4 + 1 = 5 6 + 1 = 7 6 + 1 = 7 8 + 1 = 9 8 + 1 = 9 可以看到打印结果有重复的。这是由于两个线程同时访问 count 并且都执行了 count + 1。例如,两个线程同时获得 count=5,同时执行 5 + 1 = 6,就会出现重复的情况。...

December 10, 2022

理解 Rust 的 None 和 Some(T)

Rust 语言没有 NULL, None, null, nil 这些表示“无”的值,那么 Rust 怎么表示和处理“无”呢? 在比较对象的时候,需要先校验两个对象的是否存在。例如下面的 Python 语句 def inorder(p, q): if p is None and q is None: return true if p is None or q is None: return False if not inorder(p.left, q.left): return False if p.val != q.val: return False if not inorder(p.right, q.right): return False 这是一段用于检查两棵二叉树是否一致的算法,为了能够顺利执行后面三行代码,必须要先检查p 和 q 是否为“无”。 Rust 给了另一个稍微不同的思考切入点。 Rust 的答卷 在 Rust 中,有一种枚举类叫做Option<T>。它的定义如下 enum Option<T> { Some(T), None, } 熟悉泛型的同学不用我多做介绍 T 是什么了。不熟悉的同学, T 是 Template 的缩写,可以被替换成 任何类型。Option<T> 有两个成员,Some(T) 和 None。Some(T) 表示“有值”,None 表示“无值”。 通过后面的阐述,你会更加清楚 T 的作用。...

December 3, 2022

Rust 里的所有权

十分遗憾,我从未认真学习过一门底层语言。这两年底层的语言最火的,要数 Rust 了。据说 Linux 官方也认可这门语言,希望可以利用这门语言代替一些 C 语言的模块。我早认为自己需要认真学习一门底层语言。顺着 Rust 的大火,我翻看了 The Rust Programming Language。其中第四章的 所有权(ownership) 吸引了我。 在 Rust 中,每个值都有一个所有者 Rust 认为所有值(values) 都有且只有一个所有者。 let s = String::from("hello"); 上面的语句中 “hello” 这个 String 归 s所有。 所有权可以出借 有且仅有一个所有者的情况下,把值赋给另一个变量会导致变量的转移(moved),这被称为所有权的借出(borrow) 。 fn main() { let s = String::from("hello"); // 所有权借给了 `_s` let _s = s; println!("{:?}", s); } 执行这段代码会报错,原因是我们试图打印 s,但 s的值此刻被 _s 借走了。想要再次使用 s 也简单,let s = _s 还回来就好了。 这和很多语言的拷贝(copy)不同。传统上,编程语言提供两种拷贝方式 浅拷贝(shallow copy) 深拷贝(deep copy) 以上面的代码为例 在深拷贝的情况下,_s 的创建会完整的复制s的内容,对_s 的操作和更改不会影响s。...

November 27, 2022

我记账的一些心得

我个人记账有很长一段时间了,自以为记账给我带来了很大的改变。在此分享一些记账的心得,供你参考。 为什么要记账 我认为记账有以下几个重要的原因 记账的习惯会反过来推动你养成好的消费习惯。 账本数据为作出人生抉择提供多一个参考维度。 准确性 $\neq$ 一致性 一致 不一致 准确 账本可信度最高,耗费精力最多 账本可信度适中,耗费精力适中 不准确 - 账本可信度最低,需要耗费极大精力修复坏账 所谓准确,是指账本上的净资产正确的反应了我的真实净资产;所谓一致,是指账本的交易记录和真实流水账一模一样。举个例子,这一次记账,我的净资产是100块,上一次记账我的净资产是900块。我可以在这两次记账之间先花了900,又挣了100;我也可以花了800。他们都导致了本次记账的净资产余额 100 块。我更关心从 900块 变成了 100 块这个事实,至于是怎么变的,我总是可以查看自己的银行流水账。我认为比起一致性,准确性更为重要。 基于这样的一个认识,我不必将交易的每项都记录。例如:一次购物中,我购买了大量的食品和一支笔。如果我实在没有时间挨个记录每项开销,我就可以笼统的将这笔支出记录为食物开销。当然,我会尽最大努力保证即准确又一致,但是在没有时间的情况下,我倾向于保证准确性而忽视一致性。 也许你会有这样的顾虑:每次都有一点点偏差,积累下来也会变得很大,最终账本将无法正确反应实情。对此我有三个见解。第一,一本有偏见的账本,总比没有账本强。第二,如果我账本是周期性地归档(例如一年一次),我的误差就会在周期的开始重新归零。第三,我们始终没有办法保证完美的一致性。例如请朋友吃冰淇淋,银行账单上可不会注明此笔开销是”人情来往“。 还是基于准确性不等于一致性的事实,那些只在一个记账周期内发生的借贷,是可以被省略的。例如:朋友找你临时借了钱,没过几分钟他就还回来了。一笔一笔记录这些快速产生又消失的债权会耗费许多精力。 少用现金 使用现金是个人记账最大的灾难。使用现金导致了交易的时间、地点和交易的对象无法被记录。这也是电影中的毒枭乐于使用现金交易的原因。对于个人,这个道理同样适用。使用现金意味着你无法知道自己交易的时间、地点和对象,间接地会导致坏账。如果非要使用现金,一个好的做法是养成每次消费的时候就索要小票,有机会就立马记上的习惯。即便如此,我还是建议少用现金。且不说养成消费就索要小票的习惯需要时间,大多数情况下这些揣在裤兜里的小票都会变成隔天在洗衣机里的一团的废纸。 控制资金的流向 有记账习惯的人,会很好的利用银行的资金归集/划转的功能。下图是一个简单的资金流向图。我们通过自己拥有的账户进行合理的资金流向规划。 工资卡 首先,收入从工资卡工资卡流入。个人的开销会在这一层进行一个规划。那些周期性的,固定金额的开销例如房贷,房租,物业费,定投,话费等等,会从这一层级支出。因其固定金额和时间的属性,这个账户的交易记录极不容易出错,即便出错,也能快速地修复。单独查看这个账户的账单,我们能了解到自己开销的大致规划。 Checking Account 接下来是从工资卡流到的第二个账户 ( Checking Account)。这些开销可以在自己设定规则,定期转入到这个账户中。 据我理解,美国银行设置的 Saving Account 和 Checking Account 就是这样划分的,开销从 Checking Account 出,Saving Account 用于存款和定期开销及投资。在中国,这样的概念并不普及。所以当我说 Checking Account 的时候,我们可以简单的理解为另一张银行卡。 我们看 Checking Account。在这一层,周期性的,但不是固定金额的开销会直接从这个账户中支出。例如水电气账单,燃油,停车费,信用卡还款等等都属于这一类的开销。如果你生活稳定而规律,大多数开销都会被集中在这一层里,因为稳定和规律的生活也会反应在你的账本里。想想你脑海里的会计师是怎么样的吧,他们做事谨慎,生活规律,即便是有应酬也是周期性地做而不会一时兴起,他们还会做规划,不管是财务规划还是出门旅游。究其原因,还是记账带来的习惯,记账的人不希望自己的账单没有规律,无法预测。这也应证了我说的:记账能反向推动你的个人消费习惯。 代理账户 还有一类账户很有意思,我愿意称他们为代理账户。他们本身可能没有银行做为依托,但是他们允许我们存款、收款或支出。在线的支付工具如支付宝,微信支付,Apple Pay, Google Wallet 是这一类账户;京东白条,花呗和借呗也是这一类账户。这类账户通常提供方法连接到你的银行账户,在需要的时候从银行账户上扣钱。 我们的支出可以是从工资卡和 Checking Account 直接流出的,也有可能通过这样的代理账户间接地进行交易。在中国,由于在线支付的发达,代理账户的使用十分普遍。典型的两个代表有支付宝和微信支付。 对于带有储蓄功能的代理账户,我的建议是尽量避免将资金放到代理账户上。我有如下理由: 其一,将资金放到这些账户上,在记账的时候我们需要分别记录 Checking Account 和代理账户的开销。假如有一笔开销是从 Checking Account 支出,实际上通过了某个代理账户,在账本上你需要记录两笔开销,这带来了复杂性。...

October 23, 2022

公司的类型

按股东责任大小来分类 什么是有限和无限公司? 有限和无限只有在有公司有股东的时候有意义。 有限公司中,股东承担有限责任。举个例子:公司欠钱是公司欠钱,和股东没有关系。 无限公司中,股东承担所有责任。举个例子:公司欠的钱就是股东欠的钱。 股份有限公司 简称股份公司。A、B、C三人打算注册一家股份公司。他们首先要确定公司的股价和股本总数,通常来说都是一元一股,但是要是想分得细一些也是可以的。假设以1毛一股的价格,成立一家股本总数为1000股的公司。A、B、C三人作为发起人,要先认购股份。这个过程被称为等额拆分。股份公司的股票因为可以拆分,而且股东可以随意出售手里的股份,其具有强流通性。因为股份公司的资金一般很大,涉及到的面更广,流通性又强,所以监管机构的重点关注对象是股份公司。 有限责任公司 简称有限公司,一般来说都是中小规模的公司。因为规模小,就没有必要进行等额拆分,根据投入的资金多少来确定股份的大小就好了。在有限责任公司中,股东如果想出售自己的股份,要经过半数股东的同意,且要先在内部收购,确定没人认购了才可以对外出售。

February 8, 2021