几个语言中的迭代器

上次有朋友说python的迭代器很好用,就是不好理解,我说其实php也有迭代器的,只是很多人不知道而已。

python迭代器:

先说说python的迭代器,python的迭代器使用yield关键字来实现,可以被for调用。

还是来拿读文件来说事,我们有一个文件,里面的数据都是TAB分隔,如果我们想实现一个一行行读取的库核心的地方可以使用迭代器来写:

tab文件(html输出后估计tab变成了空格,复制的同学请注意):

迭代器代码:

这里涉及迭代器的部分在于read方法中的yield迭代器,虽然是for循环,在yield调用一次以后会挂起等待下一次调用yield,所以很多人在这里需要理解很久。所以yield在while内自然也是一样的效果。

继续阅读

堆排序

上次写了个快排的文章,果不其然被大家鄙视了。大概平时是一个写脚本的,这东西也不会了,大学时代还能帮别人替考c语言,现在连c都不写了。

回归正题,这里使用二叉堆,二叉堆是一个近似的完全二叉树,在很多地方都会用到这个结构。

通常使用数组来表示一个堆:假设数组第一个元素为根节点,那么它的左孩子节点就是第二个元素,右孩子节点为第三个元素。以此类推,假设一个节点是数组的第n个元素,它的左孩子节点就是第2n个元素,右孩子节点就是2n+1,可以使用floor(n/2)来获取其父节点。

二叉堆分为最大堆(MAX-HEAP,大顶堆)和最小堆(MIN-HEAP,小顶堆),最大堆就是父节点的值大于或等于子节点值的堆,所以根节点应该是最大的节点,最小堆反之。这里使用了最大堆。

继续阅读

php的traits和单例

最近和同事聊天时发现很多同事并不了解php的一些特性。有时他们会说python的某些特性非常有趣,确实,但我提到php中也存在一些类似特性的时候,他们表示并不了解,于是考虑写几篇关于php特性的文章。如果其他语言有这类特性,我会尝试着对比来讲解。

phper们应该都知道在5.4以后增加了一个新特性叫做traits,其文档如下:

http://php.net/manual/zh/language.oop5.traits.php

traits感觉像是让你更方便的复制粘贴代码,这些代码统一的管理着。你可以使用trait_exists方法来检查traits是否存在,使用class_uses列出所有使用traits的方法。

网上有些文章吐槽traits这个特性破坏了封装或是如何,我的层次不高,只能默默表示有时候用这东西还是比较舒服的。

比如我们写单例方法,这东西我们的项目中经常性的用到:

为了不让别人初始化,我们需要控制构造函数好克隆方法的权限,然后让类自己检查和初始化自己。

继续阅读

scp被.bashrc中的echo打断问题

很多人喜欢在.bashrc中输出一些东西,比如一些字符画。但是,如果我们往这台机器scp东西的时候,会发现输出了短短一行数据就结束了。

这是因为echo中断了scp,scp会使用标准输入输出来传递自己的协议数据。可以通过如下方式避免这类情况:

  • 通过检查inertactive选项,可以通过这样一个判断来检查:
  • 使用tty来检查,比如:
  • 检查$SSH_TTY的值。

如此就可以区分登录的shell类型,避开对scp的影响了。

参考:SCP doesn't work when echo in .bashrc?

说说python的__del__方法

python中提供一个__del__方法,这个方法在实例释放的时候被调用,被称为析构器。

但最近使用中发现这个析构器和其他语言的析构方法不太一样,存在着一些不确定性。

比如我们在php中可以这样销毁一个对象:

此时php的析构方法__destruct()被调用。

python中我们经常会这样做

标量的销毁是没有任何问题的,但是对于对象来说,因为存在着比较复杂的引用关系,del只是把对象的引用计数减一,而不是直接调用__del__方法,这导致del方法没有达到预期内的效果。

而且,在对象的引用计数为0的时候,垃圾回收也不一定会回收此对象,而__del__方法在垃圾回收的时候才被调用。

同时,使用了__del__以后,python似乎不再追踪和释放循环引用(这点还没有经过确认)。

更多关于__del__的问题可以参考这个stack overflow question : how to call the __del__ method ? 

所以,当我们想像某些其他语言一样在调用完以后执行一些小动作的时候,比如关闭文件句柄、写点小日志之类。可以考虑使用python提供的with方案。下面是一个with的调用方法:

这个demo比较简单,python的open也支持with,但这个重复造的不完整轮子也比较容易解释with结构。这段代码在with语句结束的时候会调用__exit__方法。__enter__方法的返回值会返回给as作为f的值。

当然,__del__早晚会被调用的,如果你不着急而且控制得当,应该是没有任何问题的。

快速排序

前几天有人问起快排,我心想这不难呀,把原理讲完了以后发现自己写不出来……回去默默看着导论用ruby写了一个……

快排使用分治思想。最重要的是分区:取一个基准值,比基准值小的放到数组左边,比基准值大的放到数组右边,基准值自己放到中间。然后对左边和右边的数进行再分区,如此递归直到全部排列好。

基准值是从数组中获取到的,你可以自己选择,或者随机选择一个。如果是随机取基准值的快排,因为无法确定命中最好情况还是最坏情况,所以大量的排序情况下会趋近于期望值。

快排保持着两个变量或者指针,一个是当前迭代到的key,一个是当前需要交换的key。如果达到交换条件(比如迭代到的key对应的值相对比对值小)就和需要交换的key进行交换。这两个变量或者指针可以按照自己的意愿进行调整,不管是从左到右,还是从右到左。

这里要注意的是当前需要交换的数是什么,我们假设比基准数小的数叫小数,比基数大的叫大数,排列在数组中最左边的大数叫最左大数,那么在程序中和基准数比对时:

  • 当前数是小数,交换当前数和最左大数(此时最左大数可能被移到了其他大数右边,往下一位找到下最新的最左大数)
  • 当前数是大数,进入下一轮
  • 和基准值相等,这种情况算大数小数都是可以的,因为后面迭代时会重新排到正确位置

所有数字比对和移动完毕后,把基准数和最左大数交换,这样基准数就在所有数字的中间了。

下面是增加了随机的ruby版本