<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"><channel>
<title>Soulogic 灵魂逻辑</title><link>http://soulogic.com/blog/</link><description>硬盘里埋藏着我的光荣与梦想，我要用键盘把他们挖出来</description><language>zh-cn</language><generator>Soulogic RSS lib</generator><lastBuildDate>Wed, 08 Sep 2010 03:01:45 +0800</lastBuildDate><item><title>PHP 里用 Tokenizer 实现更好的 highlight_string</title><link>http://soulogic.com/archives/408</link><author>zhengkai@gmail.com (郑凯)</author><category>代码 Coding</category><guid>soulogic.blog.408</guid><pubDate>Fri, 03 Sep 2010 08:58:16 +0800</pubDate><description><![CDATA[<p>一个能有这么多用途的模块 <a href="http://php.net/Tokenizer">Tokenizer</a> 被我无视到现在，直到最近才醒过味来</p><p>比方说 PHP 代码高亮，一直用的 <span class="monospace">highlight_string</span>，可实际上这是一个非常粗糙的函数，只能区分四种颜色：default、string、keyword、comment，而用了 Tokenizer，（虽然没必要，但是）如果你愿意的话，可以标记出一百多种颜色</p><p>简单的代码实现如下：</p><p><a href="http://soulogic.com/upload/126">tokenizer.php</a></p><p>效果图片，左边是 PHP 页面，右边是我编辑器</p><p><img src="http://soulogic.com/upload/124" /></p><p>简单的说下过程：</p><p><span class="monospace">token_get_all</span> 把整个文件转成大数组后，每个 item 要么是基本符号（如 ; 和 = 之类的），要么是数组，下标 0 1 2 分别是 token id、原字符串、原始行号，其中 token id 可以用 token_name() 转回实体名，我将其显示为 <span class="monospace">&#60;span class=&#34;实体名&#34;&#62;原字符串&#60;/span&#62;</span>，以交给 CSS 来控制</p><p>其中需要注意一点，我认为 HTML 最合适的行号显示方式是 <span class="monospace">&#60;ol&#62;&#60;li&#62;</span> 标签，我见过一个 JS + CSS 的高亮控件，其行号是文本的，也就是说如果你直接鼠标划一段拷贝的话，会连行号一起拷上，如果不想这样就必须点最上面的 <span class="monospace">&#60;div&#62;</span> 画的拷贝按钮，虽然最终可以不拷贝行号了，但方式实在是别扭（就像 Flash 里没法右键一样恼人）。但基于 <span class="monospace">&#60;li&#62;</span> 的话就有个问题，需要你自己来处理转出来的 token 中的换行问题，需要写成两个 <span class="monospace">&#60;li&#62;</span> 而不是简单的在 <span class="monospace">&#60;li&#62;</span> 中加 <span class="monospace">&#60;br /&#62;</span></p><p>我的最终效果可以保证，当你在浏览器里看我那个 PHP 生成的 HTML 高亮代码的话，你直接 Ctrl + A Ctrl +C，得到的文本跟源代码是完全一致的，包括缩进。</p><p>代码只是个简单 demo，因为 token name 还需要自己来重新归类，例如我们在编辑器里设定高亮的时候只需要说，关键字是什么颜色，函数是什么颜色，而在这里你需要自己来找出哪些是<a href="http://php.net/manual/en/reserved.keywords.php">关键字</a>，你会在我的代码的结尾看到巨长的 CSS 选择。又或者如果需要区分内置函数和自定义函数，那肯定需要字典了。</p><hr /><p>关于缩进是另外一个话题，我认识的绝大部分人都是用 4 个空格代替缩进，这是让我非常疑惑的问题，除非按照 Pear 里那种傻逼的缩进规则外（如果 <span class="monospace">array(</span> 后有换行，则下一行不是比前一行多一个缩进，而是一直多到 array 的第一个字母 a 的位置，我见过由于过长的命名导致一个仅二维的数组就有五十多个空格做缩进），其他情况下缩进所代表的空格数无论怎么设都是一致的。用空格取代真正的缩进有点像用一张图片来取代 <span class="monospace">&#60;hr&#62;</span>——你本来可以在 CSS（编辑器）里设定它的表现的。当然，这个问题只有硬性规定，没有讨论结果的。就像 <a href="http://www.kernel.org/doc/Documentation/CodingStyle">Linux</a> 要求 tab，而 <a href="http://www.python.org/dev/peps/pep-0008/">Python</a> 建议4空格，可他们都很有影响力</p><p>我在代码里是额外对缩进加了层 <span class="monospace">&#60;span class=&#34;tab&#34;&#62;</span>，并定义样式 <span class="monospace">font-size</span> 是正常字体的一半，以控制它看起来像 4 个空格</p><hr /><p>其实是看到老王说到 <a href="http://hi.baidu.com/thinkinginlamp/blog/item/17476d22661ee6a94623e8d7.html">代码审计</a> 时才想起 Tokenizer</p><p>另外，如果 <a href="http://www.phpdoc.org">phpDoc</a> 能及时改用 Tokenizer 而不是自己解析字符串的话，就算更新缓慢，至少也能向后兼容 PHP 5.3 的代码了（只是新语法忽略，至少不会异常中止）</p><p><img src="http://rss.soulogic.com/track/408" /></p>]]></description></item>
<item><title>中等规模网站的UGC图片存放规划</title><link>http://soulogic.com/archives/407</link><author>zhengkai@gmail.com (郑凯)</author><category>代码 Coding</category><guid>adc267e42269b6ad4514b4f633dc01cb</guid><pubDate>Wed, 28 Jul 2010 15:55:19 +0800</pubDate><description><![CDATA[<p>先声明，本文借鉴了很多刘涛（Tarkus）和 Druggo Yang 的实战经验，特此感谢</p><hr /><p>好像现在是个网站就允许用户上传头像，其中一部分还允许上传相册、个性背景图之类的东西。对图片的规划各村都有各村的高招，这里只是抛砖引玉、提个醒：当文件膨胀到一定规模的时候再去改就来不及了，在一个项目的草创时期，让一个人多花两个星期的时间来琢磨这个“小”问题也绝对称不上是过度设计。</p><p>我对中等的定义：图片所占空间在 1T - 数十 T 之间。</p><p><span style="font-weight: bold; font-family: '微软雅黑'">功能需求</span></p><p>基本的就两点：排除重复，和可扩展性。</p><p>排重并不为很多人所重视，因为对很多人来说短期可以承受，实际经验重复的占了 50%（一些流行的图片会被重复上传很多次），但问题是这里还关系到另外一件事：审核。例如一些很流行的黄图会被频繁的被不同人重复上传，这也是不小的审核工作量。</p><p><span style="font-weight: bold; font-family: '微软雅黑'">具体设计</span></p><p>简单的说就是 MySQL 单点来保证唯一，将文件 MD5 转换为递增ID，再将固定数量的文件分成组，例如最简单的 MySQL 表可以这样。</p><blockquote class="code"><p>CREATE TABLE IF NOT EXISTS `upload_pic` (<br /> &#160;`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,<br /> &#160;`hash` binary(16) NOT NULL,<br /> &#160;PRIMARY KEY (`id`),<br /> &#160;UNIQUE KEY `hash` (`hash`)<br />) ENGINE=MyISAM DEFAULT CHARSET=ucs2 AUTO_INCREMENT=1;</p></blockquote><p>每次有上传，先 MD5、INSERT，成功就返回 ID，失败就重新 SELECT 找以前的 ID</p><p>文件就是整个 ID 的补零切三段，如 12345 保存为实际的文件 /upload/000/012/345，不保留扩展名，统一发送 Header Content-Type application/octet-stream</p><p>首要原则是只新增，不改写（除了审核后删除的）</p><p><span style="font-weight: bold; font-family: '微软雅黑'">调优</span></p><p>上面所述只为了方便理解的最精简的方案，还有很多事情可以做。</p><p>用于发号的 MySQL 仅仅是写入单点，对于已经生成的 hash，可以定期生成 <a href="http://dev.mysql.com/tech-resources/articles/storage-engine.html">Archive 引擎</a> 的只读表。然后新上传时以 SELECT/INSERT/SELECT 的顺序获取 ID（这时候，第三步的 SELECT 是极其罕见的）。</p><p>如果是 nfs 挂载，初期可以挂整个 /upload，后期可以以一级子目录为单位，分散在不同机器上。如 /upload/001 和 /upload/002 挂在机器 A 上，诸如此类。这样我们可以每 M 个图片为一个挂载单位（下面简称 MU——Mounted Unit）。如果图片的平均大小是 200K 左右，则每个 MU 的平均大小应该在 1M * 200K = 200G 左右。由于内容不再改写，老的 MU 可以到处搬动、甚至分散到不同机房，其实是类似 flickr 的 farm*.static 子域名。</p><p>实际磁盘问题是个大问题，这方面网友 Druggo Yang 给了我很多经验，他最早面临的问题是大量随机读写、几乎没热点，大概是文件实在太碎，硬盘反倒在网卡之前成为瓶颈，银弹是有：SSD，不过太tmd贵了。最后他们是用的 LVS，那已经是我知识体系以外的东西了。</p><p>我想到的解决方法被 Druggo Yang 评价为自己做软 RAID：不同 MU 挂到不同盘，用 nginx 访问，或者系统本身瓶颈，那就若干个 Server 有相同的 MU，做轮询。但总之我不推荐做磁盘 RAID，这可能是我还没搞明白，也可能真的如此，就拿最简单的两块硬盘做 RAID 0 来说，按我的理解，每读一个文件的时候，两块磁盘都需要定位，而两块独立的磁盘却可以分别各定位一个文件，据说也会受磁盘控制器的限制，但大体上不影响我的我的结论：RAID 的优势应该在连续读写上，而随机读写反而会因为短板效应而略微降低 IOPS。总之这问题我只能纸上谈兵，有条件应该拿几块硬盘测一下的。</p><p>此外，诸如目录分级是 1000 还是 100 个文件一组，还是用其他进制，都可以细测。记得以前看过一个单目录文件数的 Benchmark，可惜后来再也找不到了（有谁能提示一下？），隐约记得 500 比较合适，因为这里需要频繁手工搬动，索性就弄个人脑容易识别的数了</p><p>另外为了防止遍历需要加个扰码，这也很容易了。</p><p>至于缩略图接口，另算</p><p><span style="font-weight: bold; font-family: '微软雅黑'">应用特例：头像</span></p><p>其实每个网站都有的图片机制是头像，现在普遍的规则是用户 ID + 补码，补码可以是更新次数（如 douban）、时间戳（如 t.sina）、文件名（如 twitter）之类的。补码的主要目的是为了靠改变 URL 而绕开 Header Expires</p><p>但直到上家公司的产品部门在全面模仿 Facebook 运动中，想要实现 Facebook 里的一个功能：所有图片的大一统，如新上传的图片进相册、可以将任意一张图片设为头像，而收藏功能的流程跟自己上传几乎是一样的，等等，虽然这个改动过于底层以至于辞职一年多后公司彻底黄掉也没能看到它实现，但我还是很喜欢这个大一统的：不去考虑一张图片被哪些地方引用，只管存就是了，而所有涉及到图片的地方，都保存的是图片 ID，因此只看某一张图片的 URL，你不知道头像的主人是谁：有可能其他人也用相同的图片做头像，或者在某人的相册里。</p><p><span style="font-weight: bold; font-family: '微软雅黑'">问题</span></p><p>再次强调，这<span style="font-weight: bold; font-family: '微软雅黑'">仅仅</span>是个例子，针对每个网站、每个应用的需求，方案也是千变万化的。大家都有各自关注的重点。</p><p>所有图片都是不删的，删的只是针对图片的引用页而已，也就是说，如果一个人传了几张私人图片、由自己删除后，如果别人知道图片 URL，则还是可以访问的（或者更简单的情况，某个被设置为限少数人访问的相册的图片 URL 被公开），我不清楚实际运营时这个问题是否会很严重，总之不好回避。</p><p>对 MU 的调配是基于人工的，而不是任何高科技的自维护系统。老实说我认为就目前（2010 年）来说，除了 SSD 在某些特殊情况下能值回票价，其他的一些高科技玩意真的看不懂，我目前还很排斥云××类东西，不仅仅是贵，最主要的原因是，即使钱花到了，它们做的还没说的那么好。一个外行可以把技术视为魔法和银弹，但作为从业者，基本的问题是应该知道的，云存储再牛逼，他也是跑在机械硬盘上的，而我们现有的工作，有很重要的一部分是在约定的系数下，加减成本和可靠性两个指标，使其乘积最大。如果磁盘数量在可控范围内，偶尔找人花几个工时维护一下就够，我相信普通的热备就已经是好的选择。除非容量已经上 PB 了，或者到了<a href="http://twitter.com/calon/status/19044301129">无缘无故</a>的程度，可反过来说，那些魔法软件又有多少 PB 级案例？总之云××可以等等，第一批冲上去的大都是炮灰。现阶段我还是觉得每GB平均半毛钱不到的硬盘真他妈便宜好用</p><p><img src="http://rss.soulogic.com/track/407" /></p>]]></description></item>
<item><title>Chrome 里 Max-age 和 ETag 的古怪逻辑</title><link>http://soulogic.com/archives/406</link><author>zhengkai@gmail.com (郑凯)</author><category>代码 Coding</category><guid>cd2fb9a9bd33dd46d9856f075a1d01d8</guid><pubDate>Wed, 21 Jul 2010 15:02:52 +0800</pubDate><description><![CDATA[<p><span style="font-weight: bold; font-family: '微软雅黑'">Update in 2010-08-11</span><br /><span style="font-weight: bold; font-family: '微软雅黑'">注意：本文已失效，看来这是 Chrome 的 BUG，且已被修复，我还以为是就是想这么设定呢</span></p><hr /><p>简单的说，对于 Header 里同时有 Max-age 和 ETag 的情况，Chrome 跟所有其他浏览器的解释都是相反的</p><p>按我的理解，如果同时设置了 Max-age 和 ETag，在 Max-age 的有效期内，浏览器是不再发请求的，等过了有效期，再在请求里带上 ETag。但是在 Chrome 里却会起反效果：如果一个网页里的图片只有 ETag，那么在 Chrome 当前 tab 页的整个生存周期，可能只会偶尔被重新读取，可如果同时包含了 ETag 和 Max-age，那么你每点一次链接，那些图片都要被重新读取（也就是比没设 Max-age 时更糟糕了）。这只有网很慢的时候才会被观察到，就像我昨晚用 ssh -D 连接一个远程 phpMyAdmin 时，每点一步操作，都会看到所有图标白上那么一两秒，然后才被读出来，直到调用了内置的 Developer Tools &#62; Resources 时，看到一大堆图片的 304 返回时，才明白是 ETag 跟 Max-age 冲突了，索性在 Apache 里设置全局的 FileETag none</p><p>由于 Chrome 的出格，导致 ETag 更加鸡肋了，我怀疑只有极少数的 AJAX POST 才需要像<a href="http://soulogic.com/archives/395">以前介绍过的</a>那样由脚本自己做 ETag。当页面被 squid/CDN 缓存了两层之后，最简单有效的方法还是设置超大 Max-age、一旦有更改就变文件名。</p><p><img src="http://rss.soulogic.com/track/406" /></p>]]></description></item>
<item><title>活该如此</title><link>http://soulogic.com/archives/405</link><author>zhengkai@gmail.com (郑凯)</author><category>铂 Platinum</category><guid>d3e6532cbd3dd00ccb1342f6ba5fe197</guid><pubDate>Sun, 20 Jun 2010 02:44:17 +0800</pubDate><description><![CDATA[<p>几年前在家门口的一家小饭馆吃饭的时候，我想明白了“为什么所有的服务员都这么糟糕”的问题：老板只愿意为一个服务员的职位出这些钱，而一个服务员好到远不止这些钱的时候，她就不会去干服务员了。当然，引申的还有为什么老板只愿意出这些钱，因为你去吃饭给他带去的利润只值这些，等等。</p><p>佐证我这个观点的是去年跟河北某 IDC 托管主机的时候，那个客服很不错，办事很麻利很干净，也试着自己搭站点什么的，偶尔还会问我一些问题，我当时就奇怪这样的人怎么会甘心当客服，果不其然，几个月之后他就辞职了，之后换过两三个客服，不用说，都很糟糕，我还投诉过其中的一个。</p><p>后来看到《卧底经济学》的时候，还知道有人做过正统的归纳、论证，提出一个词叫“比较优势”，末了作者还自嘲自己之所以可以靠写经济专栏为生，是因为更牛逼的人虽然可以写经济专栏写得比他更好，但是人家可以靠别的方式挣到更多的钱。</p><p>在充分的市场竞争环境下，所有人都会坐到相对于自己来说最正确的位置，我还是喜欢更为简短且粗俗的描述：所有人活该如此。</p><p>举个例子，公司和员工个人的利益有很大分歧，<a href="http://twitter.com/lifesinger/status/994933942">如 lifesinger 所说</a></p><blockquote><p>偶尔去蓝色理想转转，感慨JS版为啥这么多人在重复造轮子？感慨之后又感慨不重复造轮子的话，又怎么能学会JS？对于老板来说，复用性节约money，对于程序员来说，重复造轮子是学习的好途径。</p></blockquote><p>而最近碰到些事情，让我第一次认真琢磨一个以前从来不认为是问题的问题：为什么有人愿意屈就公司的需要而自毁前程。当然，答案也是上面说过的，活该如此。</p><p>具体事情没法多说，只是一直做网站的我，从来没细想过，对倭外包、或者对 discuz/uchome 做二次开发的人，会有这么这么多，其实他们才是所谓 IT 行业的主流，而自己实际是被边缘化了的。</p><p>在不同角度看这个问题是不一样的，首先事情本身到没到“毁前程”这种程度，以及对“屈就”的定量问题，就像物理性质的“弹性”概念，玻璃也有弹性的，看你跟什么东西比了。</p><p>我最大的“屈就”是在 05 年的时候写了几个月的 JavaScript，不过相信真正对 JS 很懂的人不会认为我这句话是冒犯。当时公司的那个客户端，本身界面要经常改动，还要跟网站本身有一些交互，所以我把这部分工作接下来了，软件的 GUI 部分只有一个 IE 窗口控件，除了常见的浮动窗口之类的效果，还要做个进度条效果，而且是很多进度条，总之我在给我自己定目标、找动力。但无论如何那都不是一个让人愉快的工作，会在各种各样的地方出现所谓的“偶然复杂度”（不知道现学现卖的这个词是否恰当），比方说，当时蹩脚的杀毒软件会让我绘制的 DIV 菜单消失、某个版本的 msn activex 会导致页面出现离奇错误（尽管那页没用到 activex，但是禁用 msn activex 后就一切都好了）、某特定语言的 win 98 下的正则不能用两个 / 而必须用 new RegExp，等等等等，并且当时除了 prototype 刚刚在世界掀起波澜，还没有任何框架，我也只是在学习，也不可能马上用在产品上。等做得差不多了，也有一两个人可以勉强接替我这部分工作的人，我跟经理说，我不想干这个了，假设接替的人胜任不了，界面的变动也只能暂时放放了。</p><p>我的理由是基于直觉的：干这个有天花板。前几天还在一次面试的时候解释过这问题：互联网对高水平的 JS/CSS 的需求量太少了，如果全国需要 1000 个能解决 c10k 问题的人，可能相同等级的 JS 程序员也就需要 3 个，显然后者更有竞争难度。</p><p>但直到两天前，我在买阮一峰翻译的《软件随想录》时顺便买的一本<a href="http://book.douban.com/review/1449189/">《Joel 谈优秀软件开发方法》</a>里，重新看到了 Paul Graham 的《伟大的黑客》，我早就忘了作者和文章标题，也忘了是什么时候看到的了，但我记得其中每一段话的要素，下面这两段对我尤其重要，我一直把他作为判断工作的准则之一：</p><blockquote><p>所有这类讨厌的小问题都有一个共同特征，既您从中学不到任何东西。写一个编译器很有趣，那是因为您可以从中学会编译原理。为充满 BUG 的软件编写接口程序您却什么也学不到，因为那些 BUG 是随机的。因此，优秀的黑客会尽量回避那些讨厌的小问题。这不是挑三拣四，更主要的是为了自保——长期面对那样的小问题会使人变得愚蠢。黑客们的做法其实与模特们戒食乳酪的道理是一样的。</p></blockquote><blockquote><p>我不知道您能否培养出这些品质，但至少可以不去抑制它们。如果您有希望成为一名伟大的黑客，那么我建议您最好做到：一方面，绝不在枯燥的项目上浪费生命（除非不这么做您和家人就会饿死）；另一方面，做事必须有始有终、滴水不漏。我所认识的所有伟大黑客似乎都是如此，也许他们根本就没想过还有其他什么选择。</p></blockquote><p>纵使自己最终无法达到 Paul Graham 笔下的黑客标准，起码，你不遵守这些就肯定无法达到了。另外这还是我不沾酒的一个漂亮的理由：我觉得那玩意伤脑子，哪怕只损伤万分之一我也无法接受。半年前基于同样的理由我连可乐都戒了。</p><p>回到最初的问题上，让我直白（可能会有尴尬）地把话说出来，如果你觉得你干的活挺傻逼的，马上跟老板说辞职，然后再考虑找工作的事，除非你自己也觉得，你只配干这种傻逼活。这是对我的几个同事想说而没能说出口的话。</p><p>最近常常在想，03 年急于找工作的时候，是由于幸运，还是性格使然，而没沦落到改模板/做外包的工人，在目睹了像<a href="http://jerrylovesrebol.blogspot.com/2010/05/blog-post_26.html">蔡學鏞所说</a>的“大多數的工程師並不是「做了五年的系統開發」，而是「做了一年的系統開發，然後重複五次」”的实例后，还是有些后怕。</p><hr /><p><span style="font-weight: bold; font-family: '微软雅黑'">Update in 2010.07.29</span></p><p>刚看完《美丽新世界》，里面有句话叫“伊普西隆式的牺牲要由伊普西隆来完成（Only an Epsilon can be expected to make Epsilon sacrifices）”</p><p><img src="http://rss.soulogic.com/track/405" /></p>]]></description></item>
<item><title>扔硬币锦标赛</title><link>http://soulogic.com/archives/404</link><author>zhengkai@gmail.com (郑凯)</author><category>记事本 Notebook</category><guid>71750fee57fbcfc275c85e1f619fa408</guid><pubDate>Thu, 17 Jun 2010 23:04:18 +0800</pubDate><description><![CDATA[<p>我是从 <a href="http://book.douban.com/subject/1949744/">对冲基金风云录</a> 里看到的，可当我四处引用这个故事的时候，又忘了出处，直到前几天偶然重新翻到，说是这段文字是引自巴菲特的</p><hr /><p>假设有一个“全美扔硬币锦标赛”，每名选手要在报名参赛时交上 10 美元，凑成奖金发给最后入围的 8 名获胜者。想一想吧，如果有 2 亿人参赛，奖金就是 20 亿美元。每星期会有一场比赛，好让人们实现梦想。6 个月过后，将有 32 名常胜将军脱颖而出，他们中的每个人差不多都已连续扔对硬币 25 次。想想媒体能煽起多大热潮吧！</p><p>到这时候，事态开始变得疯狂。有人成了杂志上大肆报道的草根英雄；有人跑到电视脱口秀上大谈扔硬币技巧，讲解自己如何让硬币在半空中获得神力，又是如何在它落地前用意念施法，每辑节目要向电视台收费 5 万美元；另一些人开始争先恐后出书，书名诸如《扔硬币扔成百万富翁》、《上帝为何让我赢》之类。这时，愤怒的大学教授们拍案而起，在《华尔街日报》上大谈“有效市场”、“扔硬币原理”、“零和游戏”等理论，力图证明大赛的结果只是出于随机。当然，32 名常胜将军会挺身反击，质问他们：“如果没有人能做到，为何我们 32 个人做到了？”在第 16 轮比赛开始前的数周内，入围选手们对异性的吸引力显著提高，有些人还成了阿斯彭滑雪屋和佛罗里达房地产的推销对象。</p><p><img src="http://rss.soulogic.com/track/404" /></p>]]></description></item>
<item><title>费马检查</title><link>http://soulogic.com/archives/403</link><author>zhengkai@gmail.com (郑凯)</author><category>记事本 Notebook</category><guid>681d3aaf9e03a74b44cf813151ce4543</guid><pubDate>Thu, 10 Jun 2010 23:26:25 +0800</pubDate><description><![CDATA[<p>去年看到这段的时候就想扒到网上，却一直懒得动手。</p><p>一直读到注解的时候，还是有点震撼的。我知道费马等一些人都热衷于“纯数学”，那些被看起来毫无实用价值的“纯理论”，可这费马检查，却是全世界的服务器每秒中都要运行无数次的 RSA 算法的理论基石。就我自己而言，每天使用 SSH 的时候都要用到。而几位科学家把这这一切联系起来的过程，实在称得上是“玄妙”了。</p><hr /><p><a href="http://book.douban.com/subject/1148282/">《计算机程序的构造和解释》</a> 第二版中文版 P34-35</p><p>费马小定理：如果 n 是一个素数，a 是小于 n 的任意正整数，那么 a 的 n 次方与 a 模 n 同余。（两个数称为是模 n 的同余，如果它们除以 n 的余数相同。数 a 除以 n 的余数称为 a 取模 n 的余数，或简称为 a 取模 n）。</p><p>如果 n 不是素数，那么，一般而言，大部分的 a &#60; n 都将满足上面的关系。这就引出了下面这个检查素数的算法：对于给定的整数 n，随机任取一个 a &#60; n 并计算出 an 取模 n 的余数。如果得到的结果不等于 a，那么 n 就肯定不是素数。如果它就是 a，那么 n 是素数的机会就很大。现在再另取一个随机的 a 并采用同样的方式检查。如果它满足上述等式，那么我们就能对 n 是素数有更大的信心了。通过检查越来越多的 a 值，我们就可以不断增加对有关结果的信心。这一算法称为费马检查。</p><p>……</p><p>概率方法</p><p>从特征上看，费马检查与我们前面已熟悉的算法都不一样。前面那些算法都保证计算的结果一定正确，而费马检查得到的结果则只有概率上的正确性。说得更准确些，如果数 n 不能通过费马检查，我们可以确信它一定不是素数。而 n 通过了这一检查的事实只能作为它是素数的一个很强的证据，但却不是对 n 为素数的保证。我们能说的是，对于任何数 n，如果执行这一检查的次数足够多，而且看到 n 通过了检查，那么就能使这一素数检查出错的概率减小到所需要的任意程度。</p><p>不幸的是，这一断言并不完全正确。因为确实存在着一些能骗过费马检查的整数：某些数 n 不是素数但却具有这样的性质，对任意整数 a &#60; n，都有 a<sup>n</sup> 与 a 模 n 同余。由于这种数极其罕见，因此费马检查在实践中还是很可靠的 <sup>注1</sup>。也存在着一些费马检查的不会受骗的变形，它们也像费马方法一样，在检查整数 n 是否为素数时，选择随机的整数 a &#60; n 并去检查某些依赖于 n 和 a 的关系。另一方面，与费马检查不同的是可以证明，对任意的数 n，相应条件对整数 a &#60; n 中的大部分都不成立，除非 n 是素数。这样，如果 n 对某个随机选出的 a 能通过检查，n 是素数的机会就大于一半。如果 n 对两个随机选择的 a 能通过检查，n 是素数的机会就大于四分之三。通过用更多随机选择的 a 值运行这一检查，我们可以使出现错误的概率减小到所需要的任意程度。</p><p>能够证明，存在着使这样的出错机会达到任意小的检查算法，激发了人们对这类算法的极大兴趣，已经形成了人所共知称为概率算法的领域。在这一领域中已经有了大量研究工作，概率算法也已被成功应用于许多重要领域 <sup>注2</sup>。</p><hr /><p>注1：能够骗过费马检查的数称为 Carmichael 数，我们对它们知之甚少，只知其非常罕见，在 100 000 000 之内有 255 个 Carmichael 数，其中最小的几个是 561、1105、1729、2465、2821 和 6601。在检查很大的数是否为素数时，所用选择是随机的。撞上能欺骗费马检查的值的机会比宇宙射线导致计算机在执行“正确”算法中出错的机会还要小。对算法只考虑第一因素而不考虑第二因素恰好表现出数学与工程的不同。</p><p>注2：概率素数检查的最惊人应用之一是在密码学的领域中。虽然完成 200 位数的因数分解现在在计算机上还是不现实的，但用费马检查却可以在几秒内判断这么大的数的素性。这一事实成为 Rivset、Shamir 和 Adleman (1977) 提出的一种构造“不可摧毁的密码”的技术基础，这一 RSA 算法已经成为提高电子通信安全性的一种使用广泛的技术。因为这项研究和其他相关研究的发展，素数研究这一曾被认为是“纯粹”数学的缩影，是仅仅因为其自身原因而被研究的课题，现在已经变成在密码学、电子资金流通和信息查询领域里有重要实际应用的问题了。</p><p><img src="http://rss.soulogic.com/track/403" /></p>]]></description></item>
<item><title>几个常见的初级问题</title><link>http://soulogic.com/archives/402</link><author>zhengkai@gmail.com (郑凯)</author><category>代码 Coding</category><guid>0a1caf9bed06bc6f7a7b039dc36f7efa</guid><pubDate>Wed, 09 Jun 2010 23:24:34 +0800</pubDate><description><![CDATA[<h2>Password Hash 应该加 Salt</h2><p>不加 Salt 是很常见、危害很大的问题。我一直是加的，但是到了去年才知道这个词叫 <a href="http://en.wikipedia.org/wiki/Salt_(cryptography)">Salt</a></p><p>通常，密码存在数据库里时用的是 MD5，但绝不应该是原文的 MD5，应该加个字符串（随便什么都可以，但是要永久固定，不能每次随机）再 MD5，例如 <span class="monospace">md5($sPassword . &#34;somesalt&#34;);</span></p><p>是否加 Salt 的区别在于，如果用户数据库被黑了，或者被内鬼 dump 走了，你不会暴露用户的密码原文，因为常见密码的 md5 随便用 Google 都可以找到，而绝大部分人又都是一个或最多几个密码，导致用户的其他帐号也有危险。</p><p>同时这个问题又是无法纠正的，一旦用户表开始记录真实用户，就没法改了。</p><p>如果你有机会从头开始构建一个新项目的 Passport，记得加 Salt，哪怕是最简单的。</p><h2>对于中文内容，MySQL 存储的字符集用 UCS-2</h2><p>没错，我们显示页面时用的 UTF-8、跟 MySQL 连接的时候也用的 <span class="monospace">SET NAMES utf8</span>，但是存储的时候，如果你打算在表和字段的字符集上用 UTF-8，那么应该改成 UCS-2。</p><p>因为 UTF-8 本身是不定长的，而预留位置总要按最坏的打算来，因此如果你定义了一个 CHAR(3) 的字段的字符集是 UTF-8，那么它占用的空间是 9 个字节，而不是 3 个或者平均值 6 个，这样才能保证你无论是存三个汉字还是三个字母，都能保证装的下，但我们知道，有些空间被浪费了。而 UCS-2 的基本特性是任何字符都按两个字节来存，也就是每个 CHAR(3) 占 6 个字节。</p><p>要验证可以不断的更改字符集，并用类似下面的 SQL 来查看 Avg_row_length</p><blockquote class="code"><p>SHOW TABLE STATUS FROM `database_name` LIKE &#39;table_name&#39;</p></blockquote><p>对于定宽（<span class="monospace">Row_format == Fixed</span>）的表，尤其应该如此。</p><p>不是说这事有多大的性能提升，而主要在于简单并且无副作用。如果一个表因为这个设置而从 8G 变成 6G，怎么着也应该有些好处吧。</p><h2>RSS 的删除</h2><p>虽然说出来弊大于利，但从技术角度讲是应该知道的</p><p>如果是数据库里关于删除有个标记位，比方说 <span class="monospace">WHERE</span> 条件里有个 <span class="monospace">del != &#34;Y&#34;</span> 之类的，在页面显示上是应该如此，但 RSS 作为一种给机器读的文本，则是另一套机制。</p><p>RSS 本来就只是显示有哪些变化，而非显示总共有哪些内容，因此如果跟网页一样只是不显示，就会被认为是没有变化，而在 Google Reader 或者别的什么 RSS 阅读器里永久保存。</p><p>因此不应该是在 WHERE 条件的时候滤掉删除的内容，而是在显示 RSS 的时候判定，如果是已被删除的条目，则相应的 title 和 description（或者叫 content，取决于你的 RSS 版本）显示为空字符串或者“deleted”之类的</p><p>如果新浪把这个问题纠正了，韩寒的 blog 估计就真得搬家了。这非我的本意，但韩寒换 BSP 容易，更多人碰到自己的文章想删删不掉的时候会很麻烦。</p><p><img src="http://rss.soulogic.com/track/402" /></p>]]></description></item>
<item><title>劝</title><link>http://soulogic.com/archives/401</link><author>zhengkai@gmail.com (郑凯)</author><category>记事本 Notebook</category><guid>e32d38b3320db994e5856d32d3a751fc</guid><pubDate>Mon, 07 Jun 2010 22:52:29 +0800</pubDate><description><![CDATA[<p>Zheng Kai 22:35<br />你那狗怎么办，这个要小心<br />刚想跟你说，其实养孩子一开始挺金贵，现在觉得养孩子跟养狗差不多，也是每天拉出去溜……只是比狗溜的时间长……</p><p>我那朋友，把 gtalk 的 status 改了，叫 饲养员 22:37<br /> <br />Sunmast 22:38<br />我那狗还是放在上海阿<br />会对小孩不利？ 22:38<br /> <br />Zheng Kai 22:39<br />我是觉得非常不好	</p><p>Sunmast 22:39<br />为啥？	</p><p>Zheng Kai 22:39<br />要么娃很惨，要么狗很惨	</p><p>Sunmast 22:40<br />。。。<br />照顾不过来吗 22:40<br /> <br />Zheng Kai 22:40<br />我老婆怀孕的时候我还想着把我家猫留着养，等孩子生出来我就非常利索的送人了	</p><p>Sunmast 22:41<br />有病毒吗？	</p><p>Zheng Kai 22:41<br />1.猫毛很多，对成人没事，对孩子我接受不了，最主要原因<br />2.孩子小的时候怕猫抓孩子，得经常关阳台，那猫活着也挺憋屈 22:42<br /> <br />Sunmast 22:42<br />对孩子的呼吸系统有影响吧	</p><p>Zheng Kai 22:43<br />甚至都可能没影响，但就是万分之一几率，这理由都成立了	</p><p>Sunmast 22:43<br />嗯	</p><p>Zheng Kai 22:43<br />一只猫或者一条狗顶你孩子的万分之一么？完全顶不了	</p><p>Sunmast 22:44<br />嗯，本来想着让孩子玩玩的<br />呵呵 22:44<br /> <br />Zheng Kai 22:45<br />我当时也想的特别好，我还盘算着猫活到孩子十来岁的时候就该到寿命了，也算给孩子个挫折教育，结果孩子抱回来，我突然就对从来没在乎过的猫毛非常敏感了<br />以前经常我打游戏的时候猫趴我肚子上，一趴两个小时，全身猫毛 22:46<br />当然，孩子在半懂半不懂事哇哇直闹的时候，你可能会怀念你那很懂事的狗……</p><p><img src="http://rss.soulogic.com/track/401" /></p>]]></description></item>
<item><title>《百姓网公开笔试题：查询条件的子集判断》的一份 PHP 答卷</title><link>http://soulogic.com/archives/400</link><author>zhengkai@gmail.com (郑凯)</author><category>代码 Coding</category><guid>017beed6f62b6531212935eda884f7f5</guid><pubDate>Tue, 11 May 2010 14:16:11 +0800</pubDate><description><![CDATA[<p>原题见 <a href="http://home.wangjianshuo.com/cn/20100505_ccceeieeaece.htm">百姓网公开笔试题：查询条件的子集判断</a></p><p>我的答卷在 <a href="http://soulogic.com/subset_test/subset_test.tar.gz">http://soulogic.com/subset_test/subset_test.tar.gz</a></p><p>演示地址为 <a href="http://soulogic.com/subset_test/">http://soulogic.com/subset_test/</a></p><hr /><p>碰到这道题时才意识到自己的见识浅薄，非等到这种题出来才能明白，高等数学对于程序员而言是多么重要。其中最难最关键的部分是在留言里看到了 <a href="http://qmigh.blogspot.com/2010/05/blog500-querybuilderqueryandor-tyler.html">qmigh</a> 的解释才搞定的。</p><p>这道题分三部分：把查询语句转成数组结构，然后把层级混乱的条件最终分解成 以 OR 关联的 AND 合集（也就是 qmigh 所解释的），以及按规则来读取并判断两个数组。在我的代码里，Class TreeStore 负责前两步，Class SetCheck 负责后一步。</p><p>由于我完全说不出任何术语，只能把数组的转换过程列一下了。</p><p>原始语句</p><blockquote class="code"><p>a = 1 AND (b = 1 OR (c = 1 AND (e &#62; 1 OR f = 1)))</p></blockquote><p>第一步，循环用正则，从最里面的括号开始，分解出每一级。这样每一级里面都没括号了</p><blockquote class="code"><p>Array<br />(<br /> &#160; &#160;[1] =&#62; e &#62; 1 OR f = 1<br /> &#160; &#160;[2] =&#62; c = 1 AND _1<br /> &#160; &#160;[3] =&#62; b = 1 OR _2<br /> &#160; &#160;[4] =&#62; a = 1 AND _3<br />)</p></blockquote><p>按照 AND OR 的优先级进一步分解</p><blockquote class="code"><p>Array<br />(<br /> &#160; &#160;[AND] =&#62; Array<br /> &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; a = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[OR] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; b = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[AND] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; c = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[OR] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; e &#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; f = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160;)</p><p>)</p></blockquote><p>将上述数组最终分解成 OR 下的一堆 AND</p><blockquote class="code"><p>Array<br />(<br /> &#160; &#160;[OR] =&#62; Array<br /> &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[AND] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; a = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; b = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[AND] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; a = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; c = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[2] =&#62; e &#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160;[2] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[AND] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[0] =&#62; a = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; c = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[2] =&#62; f = 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160;)</p><p>)</p></blockquote><p>其实就是按 qmigh 说的，做一下转换，把最开始的表达式转换为</p><blockquote class="code"><p>(a = 1 AND b = 1) OR (a = 1 AND c = 1 AND e &#62; 1) OR (a = 1 AND c = 1 AND f = 1)</p></blockquote><p>如果只有一个表达式，可以理解为只有一个元素的 AND</p><p>如果</p><blockquote class="code"><p>(a &#62; 3) AND (b &#60; 4)</p></blockquote><p>可以用数组表示为</p><blockquote class="code"><p>array(<br />	[0] =&#62; a &#62; 3<br />	[1] =&#62; b &#60; 4<br />)</p></blockquote><p>那么</p><blockquote class="code"><p>a &#62; 3</p></blockquote><p>可以用数组表示为</p><blockquote class="code"><p>array(<br />	[0] =&#62; a &#62; 3<br />)</p></blockquote><p>OR 也同理</p><p>用来判断时的数组样子，也就是 Class TreeStore 的最终输出</p><blockquote class="code"><p>Array<br />(<br /> &#160; &#160;[0] =&#62; Array<br /> &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160;[a] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[=] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160;[b] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[=] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160;)</p><p> &#160; &#160;[1] =&#62; Array<br /> &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160;[a] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[=] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160;[c] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[=] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160;[e] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[&#62;] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160;)</p><p> &#160; &#160;[2] =&#62; Array<br /> &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160;[a] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[=] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160;[c] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[=] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160;[f] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[=] =&#62; Array<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;(<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;[1] =&#62; 1<br /> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;)</p><p> &#160; &#160; &#160; &#160;)</p><p>)</p></blockquote><p>至于判断，应该不算难，按照上面的数组挨个字段判断就可以了。以及判断父集合是否有子集合没有的字段，如果有，那肯定不是子集关系了。</p><p>写完之后一直没把握到底做的对不对，要是有类似 <a href="http://www.webstandards.org/files/acid2/test.html">ACID 2</a> 或者 <a href="http://en.wikipedia.org/wiki/Man_or_boy_test">Man or boy test</a> 那样的东西就好了。如果能有人告诉我有什么复杂条件是我的代码判断不了的，实在感激不尽（当然，用 10MB 长的条件语句搞溢出不算……）</p><p>看了看 <a href="http://home.wangjianshuo.com/cn/20100511_ccceecc.htm">其他人的答案</a>，“从离散数学到编译原理”，这标题总结得不错，核心也就是这两个问题吧。我用正则来把“编译”那部分绕过去了，但从执行效率上讲是很蠢的。</p><p>看来大家都知道画蛇添足的故事，所以都只做了满足要求（“为了简单起见，只需要实现最简单的AND, OR逻辑操作，大于，等于，小于三种比较操作就好”）的最小实现。</p><p>如果能有 PHP 同好的题解能相互学习学习那是最好了，其他语言的看不懂 <span style="white-space: nowrap;">-_-</span></p><p>原来还自诩为程序员，被这道题臊的不行，我现在的定义是，没法独自实现一个 C Compiler 的人充其量也不过是 coding fans。</p><p>给自己定下了三年目标：学离散数学、学微积分、学<a href="http://book.douban.com/subject/1148282/">《SICP》</a></p><p><img src="http://rss.soulogic.com/track/400" /></p>]]></description></item>
<item><title>PHP for Twitter OAuth 教学演示</title><link>http://soulogic.com/archives/399</link><author>zhengkai@gmail.com (郑凯)</author><category>代码 Coding</category><guid>63c36e6a5a6357539377e4cedd2cad06</guid><pubDate>Mon, 03 May 2010 02:52:54 +0800</pubDate><description><![CDATA[<p>说来话长，两三年前只是想搞个 Jabber 的 PHP Class，顺便就用上了 Livid 的 v2ex 的山寨 twitter，他起名叫 doing，我觉得这名字挺好，后来，鸭被墙了，我就琢磨着挪到 twitter 上，而且真倒了一部分数据，但我不用 twitter 的原因不是因为墙，是因为当时老看见鲸鱼，而且由于压力过大，twitter 关掉了一部分外围功能，其中就包括 GTalk 机器人，这搞得我严重不爽，因为我只用 GTalk 发，于是就自己写了一个自娱自乐了。地址是 <a href="http://plan.soulogic.com/">http://plan.soulogic.com/</a>（还有其他原因，如字数限制、以及我喜欢大量摘引一些文字，我希望有更明显的显示上的区分）</p><p>前几天在想，可以考虑做个同步，也不是很费事，一看文档，直接用 cURL 就可以发，但唯独一点不能容忍，就是发完后在 twitter.com 看到的附属信息结尾写的是“via API”，凭什么别人用的都是有名的我的就不行啊！看到 FAQ 才明白需要注册个 app，再通过 OAuth 发，才能显示 via app，于是这一天多的时间就耗在这上了。效果参见 <a href="http://twitter.com/soulogic/status/13261231824">http://twitter.com/soulogic/status/13261231824</a></p><p>其实麻烦就麻烦在搞明白整个过程，实现倒都很简单了，我是参考的 <a href="http://github.com/abraham/twitteroauth">TwitterOAuth</a>，把所有 cURL 过程和生成参数的过程输出到日志文件上，才算通了。</p><p>把这几行代码放上来的原因，就是为了帮别人更容易看明白整个交互过程，不用找一个完整实现再在所有地方下钩子。</p><p>还有个原因是，无论用谁的框架，都需要改一下把走代理加进去的……</p><p>把文档说明也放上来吧</p><p><a href="http://soulogic.com/upload/120">点击这里下载代码 toauth.tar.gz</a></p><blockquote class="code"><p><br />	PHP for Twitter OAuth 教学演示</p><p><br />注意：要求 PHP 版本 5.3 以上</p><p><br />如果你对 OAuth 还一无所知（就和我前天一样），<br />希望这份代码可以帮你更快的理解整个交互过程，而且也只是为了这个<br />如果要找一个完整实现，这个并不合适<br />可以考虑 http://github.com/abraham/twitteroauth 或者 Zend Framework</p><p>----------------------------------------------</p><p>首先确认你已经注册了 Application</p><p>注册地址 http://twitter.com/oauth_clients/new</p><p>你会得到 Consumer key 和 Consumer secret<br />把这两个字符串写到 common.inc.php 相应位置</p><p>顺便你还可以改下 callback 地址<br />这个地址只是引导用户访问，Twitter 并不直接访问你的 Web Server<br />所以在测试阶段直接用内网地址好了，这份代码暴露在公网是很危险的</p><p>对照 oauth.net 上的图<br /><img src="http://oauth.net/core/diagram.png" /><br />过程如下：</p><p>	1. 执行 1_start.php，得到 Request Token（步骤 A、B）<br />	2. 带着 Request Token 访问 twitter.com 上页面，得到用户确认<br />	 &#160; 并带着参数跳转到你指定的 2_callback.php 页面（步骤 B、C、D）<br />	3. 2_callback.php 发起请求，用 Request Token 换得 Access Token<br />	 &#160; （步骤 E、F）<br />	4. 3_work.php 根据 Consumer key/secret 和 Access Token/secret 这四个参数<br />	 &#160; 以用户身份推一条消息（步骤 G）</p><p>整个 OAuth 的过程就是为了得到不同用户所相对应的 Access Token/secret<br />把这两个参数存起来后，就可以用 3_work.php 不断发了</p></blockquote><p><img src="http://rss.soulogic.com/track/399" /></p>]]></description></item>
<item><title>所谓血汗工厂</title><link>http://soulogic.com/archives/398</link><author>zhengkai@gmail.com (郑凯)</author><category>铂 Platinum</category><guid>a1dda951b1c1cbfd74ee382b24b288e9</guid><pubDate>Wed, 21 Apr 2010 22:37:17 +0800</pubDate><description><![CDATA[<p>几个月前看《卧底经济学》，收获颇多，但是其中的很多问题一直在琢磨，还没法全盘接受</p><p>比方说，美国对中国的贸易抵制并不是两国工厂之间的竞争，本质上是美国的优势产业跟美国的夕阳产业之间的竞争，这个我好理解。再比方说血汗工厂，作者的观点是，这是必由之路，是那些工人的后代摆脱贫困的起点，不受苦是无法缩小和其他地区的经济上的差距，其次就是，血汗工厂里装的不是奴隶，他们愿意在血汗工厂干，说明其他的营生可能比去血汗工厂更糟糕，那些抵制血汗工厂产品的行为，最终可能导致工人的生活条件更差。</p><p>我起初不太愿意接受这种可以把血汗工厂描述得完全没有罪恶感的说法，同时也实在不了解那些所谓有血汗工厂的地区到底是什么样的——如果传说中的血汗工厂都是像黑煤窑那样真的在养奴隶呢？</p><p>但起码就最近的富士康来说，那些工人应该也都是自愿报名、经过竞争筛选之后才去的，今天在 cnBeta 上看到篇转载的文章，说的是这个意思</p><blockquote><p>廉价劳动力供给充足就应当被资本盘剥吗？答案显然是否定的。富士康员工向媒体坦言， “我们生产的是全世界最好的产品，但是却拿着差不多最低的待遇。”“如果不加班，每个月的工资都不够花。”然而，这样的评价却没有浇灭后来者的应聘热情。调查显示，其中的原因是“富士康能按时发工资，福利和工厂环境好点”。以此观之，其他的招工企业居然连按时发放工资都无法保障，劳动者权益保障的法律底限未能得到遵守，由此才会使得富士康具备了明显的“比较优势”。</p><p>via <a href="http://www.dfdaily.com/node2/node24/node224/userobject1ai219176.shtml">《东方早报》</a> 马红漫</p></blockquote><p>（另外再补充一点，他们居然有加班费！）</p><p>另外还有一个问题，人类无法理解太大数目的数字，我真的怀疑，很多人把富士康、华为这样有着几万几十万员工的公司，跟自己所见过的几千人的“大厂”来相比较（打个岔，03年在天通苑租房子，我当时没能理解所谓的“天通苑小区”是多大，打车到正门下了，结果去东一区我又在小区里走了三站地——货真价实的三个公交站牌，不是我打比方）。世界平均自杀率是每年每十万人自杀 10 个，中国是高于这个水平的。这意味着，如果所有自杀者全部被报道了，那富士康和华为是非常牛逼的公司，这些年几十万员工才自杀了几十个，远低于中国平均水平，反过来说，实际自杀人数需要达到被报道人数的几倍，才能“追上”中国平均自杀水平。</p><p>不排除有非法限制自由或者别的什么事，真有这些问题是需要政府来执法的。但也可以推倒出来，这么多年了，哪怕第一批进去的是被骗了，但后继者中大部分应该还是对其有所了解的，但他们还是去了。</p><hr /><p>这片被诅咒的土地从来不缺头脑简单的生物，一阵阵的浪潮就像《1984》里的仇恨周一样不可思议，从来没有慈善行为（或者傻逼呵呵的以为给<a href="http://soulogic.com/archives/173">乞丐</a>钱是慈善）的人们在喊着口号进行“运动式捐款”，同时义愤填膺的谈论某公司怎么捐的这么少，或者热衷于研究所谓的血汗工厂又自杀了几个员工。</p><p>比这更有趣的事情，例如我 04 年注意到的一个<a href="http://soulogic.com/archives/185">数字游戏</a>，为什么就没人关心呢？</p><hr /><p><span style="font-weight: bold; font-family: '微软雅黑'">Update in 2010.05.13</span></p><p>《南方周末》上有一篇文章，<a href="http://www.infzm.com/content/44878">http://www.infzm.com/content/44878</a> 可以说符合我的预测</p><p><img src="http://rss.soulogic.com/track/398" /></p>]]></description></item>
</channel>
</rss>