<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" version="2.0">

<channel>
	<title>Free Mind</title>
	
	<link>http://lifegoo.pluskid.org</link>
	<description>The Free Mind of pluskid</description>
	<pubDate>Fri, 06 Feb 2009 16:15:20 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.7</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/FreeMind" type="application/rss+xml" /><item>
		<title>Blog migration</title>
		<link>http://lifegoo.pluskid.org/?p=478</link>
		<comments>http://lifegoo.pluskid.org/?p=478#comments</comments>
		<pubDate>Thu, 05 Feb 2009 16:46:17 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Life]]></category>

		<guid isPermaLink="false">http://lifegoo.pluskid.org/?p=478</guid>
		<description><![CDATA[My blog (http://pluskid.lifegoo.com) has been down for several months. It disappeared so long that now I can not even found it in google. I&#8217;m very sorry to find that. However, it will (finally) be back online again. I hope the situation will recover gradually.
Now I&#8217;m hosting my new blog through DreamHost and I&#8217;ve got my [...]]]></description>
			<content:encoded><![CDATA[<p>My blog (http://pluskid.lifegoo.com) has been down for several months. It disappeared so long that now I can not even found it in google. I&#8217;m very sorry to find that. However, it will (finally) be back online again. I hope the situation will recover gradually.</p>
<p>Now I&#8217;m hosting my new blog through DreamHost and I&#8217;ve got my own host name (pluskid.org). Here&#8217;s the migration plan for my blog:</p>
<ul>
<li><span style="color: #000000;">All new posts</span> will go to <a href="http://blog.pluskid.org">http://blog.pluskid.org</a>, if you are still interested in my posts, please update your bookmark/subscription.</li>
<li><span style="color: #000000;">All the posts in my old blog</span> will be kept on <a href="http://lifegoo.pluskid.org">http://lifegoo.pluskid.org</a>. I kept the old posts because there are many useful posts (due to my carelessness, several posts are lost). And I use the new domain name because it is easier for me to do the management.</li>
<li>The old domain <a href="http://pluskid.lifegoo.com">http://pluskid.lifegoo.com</a> is still available. I kept this DNS because there are many incoming links to that URL. All visits to that host will get redirected automatically to <a href="http://lifegoo.pluskid.org">http://lifegoo.pluskid.org</a>.</li>
</ul>
<p>I&#8217;ve setup those two blogs and changed the DNS for pluskid.org. <span style="text-decoration: line-through;">Modifications for the DNS entry of lifegoo.com will be applied withing days. However, it might take a day or two for the changes to spread over the whole internet</span> The new DNS are beginning to take effect now.</p>
<p>So, thanks for reading/subscribing my blog! <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/lol.png' alt=':lol:' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=478</wfw:commentRss>
		</item>
		<item>
		<title>pluskid.lifegoo.com 大迁移</title>
		<link>http://lifegoo.pluskid.org/?p=477</link>
		<comments>http://lifegoo.pluskid.org/?p=477#comments</comments>
		<pubDate>Wed, 29 Oct 2008 03:24:31 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Life]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=477</guid>
		<description><![CDATA[事情确实发生得有点突然，昨天晚上登上 Google Talk ，Jack 突然告诉我托管的服务器在 11 月份就要拿回来了。那代表所有 lifegoo.com 上的应用都会下线吧。这个 blog 是去年五月份的时候我请求帮忙开通的，非常好用，真的要感谢 Jack 和 sishen 了！
不过，现在得到这个消息还真有些伤心，毕竟是用了这么长时间的 blog ，incoming link 也是有不少的。但是总之还是先把数据备份下来吧。我想先用 wordpress 的方式备份一份数据库，再用普通网页抓取的方式把网站爬下来，不过好像 quark 昨天晚上已经帮我爬过了，还做成了一个 chm ，待会找他要去。
备份下来之后还要考虑今后的去处呢。一时之间都没有想到哪个地方比较好用又稳定的。也许是该去注册一个域名了，不知道现在注册域名费用如何。可是如果单单是为了放一个 blog 去租一个虚拟空间的话，似乎太浪费了，而且这些价格啊、在哪里租比较好啊之类的也都还很不了解。时间比较仓促，似乎现在 blogger 还没有被封掉，于是暂时用 blogger 上的那个吧： http://pluskid.blogspot.com ，如果我找到了新的地方，会在那里贴出来。lifegoo 这里也不清楚具体哪天会下线，所以我待会把这篇文章也贴过去。
我想 blog 我还是希望继续写的，虽然现在频率比以前已经低了很多了吧。moonykily 曾经跟我说他觉得写技术 blog 的人都是脑子进水了，blog 本来就是用来抒发自己心情的。当然我是不会同意他的这个观点的，且不说写技术 blog 能够让知识得到分享这样的话吧，更自私的观点就是：如果你能把自己知道的东西给别人描述清楚的话，你会掌握得更加牢靠。而有些东西当你准备要写下来的时候，你才会发现原来自己根本没有弄清楚。我写 blog 一般会花掉半天到两天不等的时间，但是大部分时候我还是觉得是有收获的。
所以，我还是先去备份数据了，之后怎么迁移还真是个麻烦事，一时也想不清楚，不知道大家有什么好的主意没有？
]]></description>
			<content:encoded><![CDATA[<p>事情确实发生得有点突然，昨天晚上登上 Google Talk ，<a href="http://jack.lifegoo.com">Jack</a> 突然告诉我托管的服务器在 11 月份就要拿回来了。那代表所有 lifegoo.com 上的应用都会下线吧。这个 blog 是去年五月份的时候我请求帮忙开通的，非常好用，真的要感谢 Jack 和 <a href="http://sishen.lifegoo.com">sishen</a> 了！</p>
<p>不过，现在得到这个消息还真有些伤心，毕竟是用了这么长时间的 blog ，incoming link 也是有不少的。但是总之还是先把数据备份下来吧。我想先用 wordpress 的方式备份一份数据库，再用普通网页抓取的方式把网站爬下来，不过好像 quark 昨天晚上已经帮我爬过了，还做成了一个 chm ，待会找他要去。</p>
<p>备份下来之后还要考虑今后的去处呢。一时之间都没有想到哪个地方比较好用又稳定的。也许是该去注册一个域名了，不知道现在注册域名费用如何。可是如果单单是为了放一个 blog 去租一个虚拟空间的话，似乎太浪费了，而且这些价格啊、在哪里租比较好啊之类的也都还很不了解。时间比较仓促，似乎现在 blogger 还没有被封掉，于是暂时用 blogger 上的那个吧： <a href="http://pluskid.blogspot.com">http://pluskid.blogspot.com</a> ，如果我找到了新的地方，会在那里贴出来。lifegoo 这里也不清楚具体哪天会下线，所以我待会把这篇文章也贴过去。</p>
<p>我想 blog 我还是希望继续写的，虽然现在频率比以前已经低了很多了吧。moonykily 曾经跟我说他觉得写技术 blog 的人都是脑子进水了，blog 本来就是用来抒发自己心情的。当然我是不会同意他的这个观点的，且不说写技术 blog 能够让知识得到分享这样的话吧，更自私的观点就是：如果你能把自己知道的东西给别人描述清楚的话，你会掌握得更加牢靠。而有些东西当你准备要写下来的时候，你才会发现原来自己根本没有弄清楚。我写 blog 一般会花掉半天到两天不等的时间，但是大部分时候我还是觉得是有收获的。</p>
<p>所以，我还是先去备份数据了，之后怎么迁移还真是个麻烦事，一时也想不清楚，不知道大家有什么好的主意没有？</p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=477</wfw:commentRss>
		</item>
		<item>
		<title>用 dvipng 来快速生成公式图片</title>
		<link>http://lifegoo.pluskid.org/?p=475</link>
		<comments>http://lifegoo.pluskid.org/?p=475#comments</comments>
		<pubDate>Tue, 28 Oct 2008 04:29:23 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Art]]></category>

		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=475</guid>
		<description><![CDATA[用 LaTeX 可以生成漂亮的公式，这是众所周知的。但是并不是总是会用 LaTeX 来做整个文档。例如，虽然我也用 beamer 做过 slides ，但是我还是觉得用 PowerPoint 或者类似的工具可以更方便地做出漂亮的幻灯片来。又比如我在写 blog 的时候想要插入一个公式，等等。这个时候我通常会临时建一个 TeX 文档，输入这个公式，生成出 PDF ，用阅读器打开，然后截图。虽然已经这样做了许多许多次了，但是其实这个过程非常无聊，而且我喜欢透明的背景，截图是白色的背景的话，有时候不能很好地融入到场景之中。
但是其实输出为 PDF 格式只是 LaTeX 文档的一种渲染方式，要得到一个透明背景的 png 其实也是非常方便的事情。我以前在用 Muse 做笔记的时候就有一个像 MediaWiki 那样的 latex 标签可以直接书写 LaTeX 公式，结果会自动转化为图片嵌入到生成的文档中去，效果就像这个页面显示的那样。
虽然不会像 Muse 那么方便，不过我想至少可以写一个简单的脚本来简化这个过程。首先是要新建一个空的 LaTeX 模板，并且需要把框架写好（例如，将页面布局设置为 empty ，没有页眉页脚之类的，因为只需要一个公式所占的那部分内容），引用一些常用的宏包：

\documentclass{article}
\usepackage{amsmath}
\usepackage{fullpage}
\usepackage{amssymb}
\usepackage[usenames]{color}
\usepackage{latexsym}
\usepackage[mathscr]{eucal}
\pagestyle{empty}
&#160;
\begin{document}
&#160;
\end{document}

然后是用 latex 命令将其编译为 dvi 文件，最后用 dvipng 命令转化为 png 格式的图片即可。整个脚本如下：

#!/bin/bash
&#160;
TMPFILE=/tmp/tex2png.tmp.tex
if ! &#91;&#91; -e $TMPFILE &#93;&#93;; then
     [...]]]></description>
			<content:encoded><![CDATA[<p><img src='/wp-content/uploads/2008/10/eq.png' alt='LaTeX' style="float:right; margin-left: 10px;" />用 LaTeX 可以生成漂亮的公式，这是众所周知的。但是并不是总是会用 LaTeX 来做整个文档。例如，虽然我也用 beamer 做过 slides ，但是我还是觉得用 PowerPoint 或者类似的工具可以<b>更方便地</b>做出漂亮的幻灯片来。又比如我在写 blog 的时候想要插入一个公式，等等。这个时候我通常会临时建一个 TeX 文档，输入这个公式，生成出 PDF ，用阅读器打开，然后截图。虽然已经这样做了许多许多次了，但是其实这个过程非常无聊，而且我喜欢透明的背景，截图是白色的背景的话，有时候不能很好地融入到场景之中。</p>
<p>但是其实输出为 PDF 格式只是 LaTeX 文档的一种渲染方式，要得到一个透明背景的 png 其实也是非常方便的事情。我以前在用 Muse 做笔记的时候就有一个像 MediaWiki 那样的 <code>latex</code> 标签可以直接书写 LaTeX 公式，结果会自动转化为图片嵌入到生成的文档中去，效果就像<a href="/wiki/MuseLatex2Png.html">这个页面</a>显示的那样。</p>
<p><span id="more-475"></span>虽然不会像 Muse 那么方便，不过我想至少可以写一个简单的脚本来简化这个过程。首先是要新建一个空的 LaTeX 模板，并且需要把框架写好（例如，将页面布局设置为 empty ，没有页眉页脚之类的，因为只需要一个公式所占的那部分内容），引用一些常用的宏包：</p>

<div class="wp_syntax"><div class="code"><pre class="latex" style="font-family:monospace;"><span style="color: #800000; font-weight: normal;">\<span style="color: #800000; font-weight: bold;">documentclass</span></span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;">article</span><span style="color: #E02020; ">}</span>
<span style="color: #800000; font-weight: normal;">\usepackage</span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;">amsmath</span><span style="color: #E02020; ">}</span>
<span style="color: #800000; font-weight: normal;">\usepackage</span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;">fullpage</span><span style="color: #E02020; ">}</span>
<span style="color: #800000; font-weight: normal;">\usepackage</span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;">amssymb</span><span style="color: #E02020; ">}</span>
<span style="color: #800000; font-weight: normal;">\usepackage</span><span style="color: #E02020; ">[</span><span style="color: #C08020; font-weight: normal;">usenames</span><span style="color: #E02020; ">]{</span><span style="color: #2020C0; font-weight: normal;">color</span><span style="color: #E02020; ">}</span>
<span style="color: #800000; font-weight: normal;">\usepackage</span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;">latexsym</span><span style="color: #E02020; ">}</span>
<span style="color: #800000; font-weight: normal;">\usepackage</span><span style="color: #E02020; ">[</span><span style="color: #C08020; font-weight: normal;">mathscr</span><span style="color: #E02020; ">]{</span><span style="color: #2020C0; font-weight: normal;">eucal</span><span style="color: #E02020; ">}</span>
<span style="color: #800000; font-weight: normal;">\pagestyle</span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;">empty</span><span style="color: #E02020; ">}</span>
&nbsp;
<span style="color: #C00000; font-weight: normal;">\begin</span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;"><span style="color: #0000D0; font-weight: normal;">document</span></span><span style="color: #E02020; ">}</span>
&nbsp;
<span style="color: #C00000; font-weight: normal;">\end</span><span style="color: #E02020; ">{</span><span style="color: #2020C0; font-weight: normal;"><span style="color: #0000D0; font-weight: normal;">document</span></span><span style="color: #E02020; ">}</span></pre></div></div>

<p>然后是用 <code>latex</code> 命令将其编译为 dvi 文件，最后用 dvipng 命令转化为 png 格式的图片即可。整个脚本如下：</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">#!/bin/bash</span>
&nbsp;
<span style="color: #007800;">TMPFILE</span>=<span style="color: #000000; font-weight: bold;">/</span>tmp<span style="color: #000000; font-weight: bold;">/</span>tex2png.tmp.tex
<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #000000; font-weight: bold;">!</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span><span style="color: #7a0874; font-weight: bold;">&#91;</span> <span style="color: #660033;">-e</span> <span style="color: #007800;">$TMPFILE</span> <span style="color: #7a0874; font-weight: bold;">&#93;</span><span style="color: #7a0874; font-weight: bold;">&#93;</span>; <span style="color: #000000; font-weight: bold;">then</span>
        <span style="color: #c20cb9; font-weight: bold;">cat</span> <span style="color: #000000; font-weight: bold;">&lt;&lt;</span>eof <span style="color: #000000; font-weight: bold;">&gt;</span> <span style="color: #007800;">$TMPFILE</span>
\documentclass<span style="color: #7a0874; font-weight: bold;">&#123;</span>article<span style="color: #7a0874; font-weight: bold;">&#125;</span>
\usepackage<span style="color: #7a0874; font-weight: bold;">&#123;</span>amsmath<span style="color: #7a0874; font-weight: bold;">&#125;</span>
\usepackage<span style="color: #7a0874; font-weight: bold;">&#123;</span>fullpage<span style="color: #7a0874; font-weight: bold;">&#125;</span>
\usepackage<span style="color: #7a0874; font-weight: bold;">&#123;</span>amssymb<span style="color: #7a0874; font-weight: bold;">&#125;</span>
\usepackage<span style="color: #7a0874; font-weight: bold;">&#91;</span>usenames<span style="color: #7a0874; font-weight: bold;">&#93;</span><span style="color: #7a0874; font-weight: bold;">&#123;</span>color<span style="color: #7a0874; font-weight: bold;">&#125;</span>
\usepackage<span style="color: #7a0874; font-weight: bold;">&#123;</span>latexsym<span style="color: #7a0874; font-weight: bold;">&#125;</span>
\usepackage<span style="color: #7a0874; font-weight: bold;">&#91;</span>mathscr<span style="color: #7a0874; font-weight: bold;">&#93;</span><span style="color: #7a0874; font-weight: bold;">&#123;</span>eucal<span style="color: #7a0874; font-weight: bold;">&#125;</span>
\pagestyle<span style="color: #7a0874; font-weight: bold;">&#123;</span>empty<span style="color: #7a0874; font-weight: bold;">&#125;</span>
&nbsp;
\begin<span style="color: #7a0874; font-weight: bold;">&#123;</span>document<span style="color: #7a0874; font-weight: bold;">&#125;</span>
&nbsp;
\end<span style="color: #7a0874; font-weight: bold;">&#123;</span>document<span style="color: #7a0874; font-weight: bold;">&#125;</span>
EOF
<span style="color: #000000; font-weight: bold;">fi</span>
&nbsp;
<span style="color: #c20cb9; font-weight: bold;">vim</span> <span style="color: #007800;">$TMPFILE</span> +<span style="color: #000000;">11</span>
<span style="color: #7a0874; font-weight: bold;">cd</span> <span style="color: #000000; font-weight: bold;">/</span>tmp
latex <span style="color: #007800;">$TMPFILE</span>
dvipng <span style="color: #800000;">${TMPFILE%.tex}</span>.dvi <span style="color: #660033;">-T</span> tight <span style="color: #660033;">-x</span> <span style="color: #000000;">5000</span> <span style="color: #660033;">-bg</span> transparent <span style="color: #660033;">-o</span> eq.png
<span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;Equation generated to /tmp/eq.png&quot;</span></pre></div></div>

<p>不过这其实是一个相当 ad hoc 的脚本了，虽然没有立即删掉上一次编辑的公式的 TeX 源码，但是如果下次做的时候会覆盖前一次的代码，之后再要想做一点小修改也是需要整个公式重新写一遍的。应该最方便的还是像 Muse 那样直接嵌入 LaTeX 标签的 WordPress 插件，不过一来我没有精力去做那样的插件，二来就算有现成的插件，我也不想再服务器上瞎折腾什么 LaTeX 之类的。总之能用就行吧！ <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/happy.png' alt=':)' class='wp-smiley' /> </p>
<p>最后，其实还可以用一下<a href="/?p=70">这里</a>的 ImageMagick 脚本给公式加个阴影什么的，就像本文一开头的那个 LaTeX 的字样一样。</p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=475</wfw:commentRss>
		</item>
		<item>
		<title>百度印象</title>
		<link>http://lifegoo.pluskid.org/?p=472</link>
		<comments>http://lifegoo.pluskid.org/?p=472#comments</comments>
		<pubDate>Sun, 26 Oct 2008 02:22:23 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Life]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=472</guid>
		<description><![CDATA[其实参加百度面试已经是在好久以前的事情了，那个时候我还在十字路口徘徊。一直想把自己的面试经历和对百度的印象写下来，但是一来是因为最近都比较忙碌，二来好像是有一些东西不能到处去说之类的吧。不过现在面试结果已经确定有一段时间了，而我也已经逐渐进入了准研究生的生活，所以我想写写也无大碍吧，反正也不是要说具体的面试题目。
百度的校园招聘比微软和 Google 都要早许多，我想大概是由于有内部推荐的原因（因为有许多朋友都在百度  ），我在宣讲会之前就被叫过去参加了一次面试。这算我求职生涯中的第一次面试吧，因为以前听马铃鼠说他的第一面悲剧了，所以我也比较紧张。面试的地点就在学校正门附近的一个宾馆里，时间是早上九点。似乎是前一天收到通知约时间的，要说准备嘛，虽然那段时间一直都说要准备找工作，但是其实没有什么具体的准备，我想大概还是不知道准备什么吧，总不至于临时去找一本《算法导论》来啃吧？  
由于我没有去过那个地方，所以时间估计有些失误，在楼下逛了几圈之后才进去，还是提前了大约十分钟左右。和面试官打了招呼，他给了我一瓶水，说你来得好早。然后就开始跟我聊一些比较随意的话题，比如我对搜索引擎是否有了解以及做项目会用什么编程语言、环境之类的。似乎我都是还有自己的一些想法的，就自然地说出来了，好像也不太紧张了。不过过了一会，他说那我们现在开始面试吧。我才发现原来刚才那个真的是在闲聊。顿时又有些紧张起来，不过已经比先前好多了，因为闲聊之中我发现这个面试官也是非常和善的。
面试并没有像我想象的那样以“请你做一下自我介绍吧”开始，而是直接问一些具体的问题。主要是一些题目吧，也不能说是算法题，大致就是那种考察逻辑思维的题目，而且在你没有头绪的时候面试官往往都会给你进一步的提示，引导你找出问题的答案来。我觉得这样的感觉挺不错的，甚至都有点像是在和面试官讨论的情形了，让人觉得和面试官的地位是平等的，而且紧张的感觉很快就会消失掉。面试时间是一个小时，由一个面试官面试。与此形成鲜明对比的就是保研的面试：六个左右的面试官，面试时间十五分钟左右，你能明显地感觉到老师们在以一种居高临下的姿态在和你对话，感觉非常不好。
一个小时的时间很快就过去了，中途问了一点编程方面的基础知识，涉及到函数调用时的 active record 以及 call stack 之类的东西，大概我答得比较干脆，也没有深入问下去，所以主要都是一些逻辑性的题目。不过最后有一个问题，我把“最大 32”听错当作“最大 32 位”了，本来是可以用一个很小的 bitmap 来解决问题的，我一直觉得不可行，想了很多乱七八糟的办法，面试官一直提醒我有更好的办法，最后还是没有找出来。面试结束下了楼之后我又跑上去问他那个问题到底是怎么回事，才发现自己犯了一个很笨的错误。 *^_^* 然后他叫我别忘了参加晚上的宣讲会和笔试。
总的来说还是比较紧张的，或者说可以用“投入”这个词来形容？因为面试之前他给我看过他的胸卡，可是之后我却再也想不起他的名字了。^_^bb
接下来就是晚上的笔试了。因为我不知道在笔试之前还有宣讲会的，还以为去的人不是很多呢，临近开始的时候才到那边，发现教七影视厅已经人满为患了，然后还有百度的电话提醒，看样子他们还是蛮辛苦的。不过我现在已经是彻底练就了随地大小睡的本领了，前不久微软校园行张宏江过来一些人在一个很小的会议室里面开座谈会我还差点睡着了，所以这次宣讲会我也毫无悬念地大睡了一场。之后才开始发笔试的卷子，拿到卷子的时候真吓了我一跳，大张大张的纸，还那么厚一叠，仔细一看才发现原来我拿了两份。而且每一份卷子其实是分成若干种的，根据自己感兴趣的部门做不同的卷子就可以了。我报的是网页搜索研发部，就看了下对应的卷子，大致有三道题吧，第一题如果知道倒排表的话，很容易就做出来了；第二题题目说是算法题，不过我觉得不像；最后一题说是编程题，不过看起来似乎更像设计一个系统架构，那样的东西不可能一下子写出整个程序来了。之后还看了看其他的卷子，发现有许多非常具体的题目，比如前端研发那边好像要写一些很具体的 javascript 代码之类的，我一下子肯定是做不出来了。
哦！对了，baidu 校园招聘团队刚到学校的时候就做了一次类似于讨论组的东西，反正邀请了一些人在教室里，主要是有人讲了一点东西，还有互动提问环节。不过我觉得效果好像不是很好，一开始讲的东西好像是很技术，但是又没法展开讲的那种，所以听完了又感觉好像什么都没有听一样。而后面的互动环节，不知道是中国大学的通病还是只是浙大的通病还是只是浙大 CS 相关一些专业的通病，反正就是比较冷的了。后来和百度面试官聊的时候他们问我有没有去参加这次活动，我想了想就问：“哦！是不是有桔子吃的那次？”他们就汗了，似乎现场有桔子吃是给我留下的最深刻的印象了。 ^_^bb 不过其实给我留下印象的还有他们的海报，原来的图片我找不到了，在他们 hr 的网站上截了一个类似的图：

很大的一个齐天大圣的图片，我在去的路上看到了好几次这个海报，可是直到我走到那个活动的教室门口，发现那里也摆着这个海报的时候，才发现这个是 baidu 校园招聘的海报。好了，不扯太远了，继续说面试。第二天接到电话约了二面的时间。之前我好像不太清楚原来公司招聘是要面好几次的，后来才知道原来 baidu 算很好的了，有些公司要面五六次或者连续面一两天的……-.-bb
二面我似乎也提前了一点到，在另外一个房间，不过这次是直接开始的。这次主要是问一些工程方面的问题吧，我看到面试官拿着似乎是我昨晚做的笔试卷子，不过大致就聊一聊我做过的一些东西，其中谈到我 C++ 大程的时候做的一个即时通讯的小软件，其实我觉得那是我做得最失败的一个大程，不过那只是一个话题的引导了，后面有很大一部分时间是在讨论构建一个 IM 通讯网络的问题，比如群组聊天的时候的网络架构等等。再到后面也谈论了一些其他方面的问题，不过大都是关于分布式系统的很开放的话题，而不是一些很具体的你问我答的问题。时间也是大致一个小时，后来才在 88 MSTC 版上看到原来这个面试官就是 Mick …… -_-// 不过他当时好像也不知道我是 MSTC 的。
面完之后他让我在那里坐一会，我很奇怪，然后他告诉我第三面那边还在弄，稍等一会。竟然紧接着就是第三面啊……于是我坐在那里和他聊天，聊一下将来的打算之类的吧，因为那个时候我已经定下来要读研了的，所以就那样说了。反正感觉和 baidu 的面试官聊天都是很亲切的那种，好像大家是同一类型的人，说起话来就没有那么多顾忌了。  
第三面又是在另一个房间，这次终于要让我自我介绍了…… -.-bb 还真有些突如其来，要不是他说第三面就是随便聊一聊，不会像前面那样问很技术的问题，我还真又要开始紧张起来了。不过自我介绍我还是没有说些什么来。然后大家就开始聊吧，主要是他提一些问题，总结起来可以算是在讨论人生观、价值观之类的啦！  我也慢慢开始健谈起来，因为有些问题我自己平时也是时常在思考的，也有一些自己的想法吧。
比如，我始终觉得周围的人大家如果从智力上来说的话，是不会相差太多的，而你要做什么事情，如果真的肯花时间和精力去做的话，很多时候都还是能成功的，然而最关键的还是把时间和精力花在哪里吧。除非真的是那种超级天才，否则各方面都做到最好几乎是不可能的吧，所以总是会有各种各样的取舍的过程。可是当问及我的打算的时候，总是还是觉得和自己的想法还是有些矛盾吧。他说像这样的情况，如果能有机会出去历练的话，当然是最好的，待在学校固然是很安逸，可是两年以后再走到一起，有可能就会发现和当时的那些人已经不是在同一起跑线上了。在这个时候选择读研，大概确实是一个大转折吧，虽然我从来没有想过要完全转向做 research [...]]]></description>
			<content:encoded><![CDATA[<p>其实参加百度面试已经是在好久以前的事情了，那个时候我还在十字路口徘徊。一直想把自己的面试经历和对百度的印象写下来，但是一来是因为最近都比较忙碌，二来好像是有一些东西不能到处去说之类的吧。不过现在面试结果已经确定有一段时间了，而我也已经逐渐进入了准研究生的生活，所以我想写写也无大碍吧，反正也不是要说具体的面试题目。</p>
<p>百度的校园招聘比微软和 Google 都要早许多，我想大概是由于有内部推荐的原因（因为有许多朋友都在百度 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/happy.png' alt=':)' class='wp-smiley' /> ），我在宣讲会之前就被叫过去参加了一次面试。这算我求职生涯中的第一次面试吧，因为以前听马铃鼠说他的第一面悲剧了，所以我也比较紧张。面试的地点就在学校正门附近的一个宾馆里，时间是早上九点。似乎是前一天收到通知约时间的，要说准备嘛，虽然那段时间一直都说要准备找工作，但是其实没有什么具体的准备，我想大概还是不知道准备什么吧，总不至于临时去找一本《算法导论》来啃吧？ <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/tongue.png' alt=':p' class='wp-smiley' /> </p>
<p><span id="more-472"></span>由于我没有去过那个地方，所以时间估计有些失误，在楼下逛了几圈之后才进去，还是提前了大约十分钟左右。和面试官打了招呼，他给了我一瓶水，说你来得好早。然后就开始跟我聊一些比较随意的话题，比如我对搜索引擎是否有了解以及做项目会用什么编程语言、环境之类的。似乎我都是还有自己的一些想法的，就自然地说出来了，好像也不太紧张了。不过过了一会，他说那我们现在开始面试吧。我才发现原来刚才那个真的是在闲聊。顿时又有些紧张起来，不过已经比先前好多了，因为闲聊之中我发现这个面试官也是非常和善的。</p>
<p>面试并没有像我想象的那样以“请你做一下自我介绍吧”开始，而是直接问一些具体的问题。主要是一些题目吧，也不能说是算法题，大致就是那种考察逻辑思维的题目，而且在你没有头绪的时候面试官往往都会给你进一步的提示，引导你找出问题的答案来。我觉得这样的感觉挺不错的，甚至都有点像是在和面试官讨论的情形了，让人觉得和面试官的地位是平等的，而且紧张的感觉很快就会消失掉。面试时间是一个小时，由一个面试官面试。与此形成鲜明对比的就是保研的面试：六个左右的面试官，面试时间十五分钟左右，你能明显地感觉到老师们在以一种居高临下的姿态在和你对话，感觉非常不好。</p>
<p>一个小时的时间很快就过去了，中途问了一点编程方面的基础知识，涉及到函数调用时的 active record 以及 call stack 之类的东西，大概我答得比较干脆，也没有深入问下去，所以主要都是一些逻辑性的题目。不过最后有一个问题，我把“最大 32”听错当作“最大 32 位”了，本来是可以用一个很小的 bitmap 来解决问题的，我一直觉得不可行，想了很多乱七八糟的办法，面试官一直提醒我有更好的办法，最后还是没有找出来。面试结束下了楼之后我又跑上去问他那个问题到底是怎么回事，才发现自己犯了一个很笨的错误。 *^_^* 然后他叫我别忘了参加晚上的宣讲会和笔试。</p>
<p>总的来说还是比较紧张的，或者说可以用“投入”这个词来形容？因为面试之前他给我看过他的胸卡，可是之后我却再也想不起他的名字了。^_^bb</p>
<p>接下来就是晚上的笔试了。因为我不知道在笔试之前还有宣讲会的，还以为去的人不是很多呢，临近开始的时候才到那边，发现教七影视厅已经人满为患了，然后还有百度的电话提醒，看样子他们还是蛮辛苦的。不过我现在已经是彻底练就了随地大小睡的本领了，前不久微软校园行张宏江过来一些人在一个很小的会议室里面开座谈会我还差点睡着了，所以这次宣讲会我也毫无悬念地大睡了一场。之后才开始发笔试的卷子，拿到卷子的时候真吓了我一跳，大张大张的纸，还那么厚一叠，仔细一看才发现原来我拿了两份。而且每一份卷子其实是分成若干种的，根据自己感兴趣的部门做不同的卷子就可以了。我报的是网页搜索研发部，就看了下对应的卷子，大致有三道题吧，第一题如果知道倒排表的话，很容易就做出来了；第二题题目说是算法题，不过我觉得不像；最后一题说是编程题，不过看起来似乎更像设计一个系统架构，那样的东西不可能一下子写出整个程序来了。之后还看了看其他的卷子，发现有许多非常具体的题目，比如前端研发那边好像要写一些很具体的 javascript 代码之类的，我一下子肯定是做不出来了。</p>
<p>哦！对了，baidu 校园招聘团队刚到学校的时候就做了一次类似于讨论组的东西，反正邀请了一些人在教室里，主要是有人讲了一点东西，还有互动提问环节。不过我觉得效果好像不是很好，一开始讲的东西好像是很技术，但是又没法展开讲的那种，所以听完了又感觉好像什么都没有听一样。而后面的互动环节，不知道是中国大学的通病还是只是浙大的通病还是只是浙大 CS 相关一些专业的通病，反正就是比较冷的了。后来和百度面试官聊的时候他们问我有没有去参加这次活动，我想了想就问：“哦！是不是有桔子吃的那次？”他们就汗了，似乎现场有桔子吃是给我留下的最深刻的印象了。 ^_^bb 不过其实给我留下印象的还有他们的海报，原来的图片我找不到了，在他们 hr 的网站上截了一个类似的图：</p>
<p><center><img src='/wp-content/uploads/2008/10/baidu_hr.jpg' alt='baidu_hr.jpg' /></center></p>
<p>很大的一个齐天大圣的图片，我在去的路上看到了好几次这个海报，可是直到我走到那个活动的教室门口，发现那里也摆着这个海报的时候，才发现这个是 baidu 校园招聘的海报。好了，不扯太远了，继续说面试。第二天接到电话约了二面的时间。之前我好像不太清楚原来公司招聘是要面好几次的，后来才知道原来 baidu 算很好的了，有些公司要面五六次或者连续面一两天的……-.-bb</p>
<p>二面我似乎也提前了一点到，在另外一个房间，不过这次是直接开始的。这次主要是问一些工程方面的问题吧，我看到面试官拿着似乎是我昨晚做的笔试卷子，不过大致就聊一聊我做过的一些东西，其中谈到我 C++ 大程的时候做的一个即时通讯的小软件，其实我觉得那是我做得最失败的一个大程，不过那只是一个话题的引导了，后面有很大一部分时间是在讨论构建一个 IM 通讯网络的问题，比如群组聊天的时候的网络架构等等。再到后面也谈论了一些其他方面的问题，不过大都是关于分布式系统的很开放的话题，而不是一些很具体的你问我答的问题。时间也是大致一个小时，后来才在 88 MSTC 版上看到原来这个面试官就是 Mick …… -_-// 不过他当时好像也不知道我是 MSTC 的。</p>
<p>面完之后他让我在那里坐一会，我很奇怪，然后他告诉我第三面那边还在弄，稍等一会。竟然紧接着就是第三面啊……于是我坐在那里和他聊天，聊一下将来的打算之类的吧，因为那个时候我已经定下来要读研了的，所以就那样说了。反正感觉和 baidu 的面试官聊天都是很亲切的那种，好像大家是同一类型的人，说起话来就没有那么多顾忌了。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/wink.png' alt=';)' class='wp-smiley' /> </p>
<p>第三面又是在另一个房间，这次终于要让我自我介绍了…… -.-bb 还真有些突如其来，要不是他说第三面就是随便聊一聊，不会像前面那样问很技术的问题，我还真又要开始紧张起来了。不过自我介绍我还是没有说些什么来。然后大家就开始聊吧，主要是他提一些问题，总结起来可以算是在讨论人生观、价值观之类的啦！ <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/joyful.png' alt='^_^' class='wp-smiley' /> 我也慢慢开始健谈起来，因为有些问题我自己平时也是时常在思考的，也有一些自己的想法吧。</p>
<p>比如，我始终觉得周围的人大家如果从智力上来说的话，是不会相差太多的，而你要做什么事情，如果真的肯花时间和精力去做的话，很多时候都还是能成功的，然而最关键的还是把时间和精力花在哪里吧。除非真的是那种超级天才，否则各方面都做到最好几乎是不可能的吧，所以总是会有各种各样的取舍的过程。可是当问及我的打算的时候，总是还是觉得和自己的想法还是有些矛盾吧。他说像这样的情况，如果能有机会出去历练的话，当然是最好的，待在学校固然是很安逸，可是两年以后再走到一起，有可能就会发现和当时的那些人已经不是在同一起跑线上了。在这个时候选择读研，大概确实是一个大转折吧，虽然我从来没有想过要完全转向做 research ，而且我也完全没有信心做这一行，但是我始终觉得如果我读研的时候只是在做一些工程性的东西的话，那么我就是走到一条错误的道路上去做错误的事情了——为何不直接去工作呢？我曾经开玩笑说像 moonykily 这样将来要做 research 的人，已经是半只脚踏进棺材了，要终日与 <a href="/?p=197">Dynamic Logic</a> 为伴了。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/tongue.png' alt=':p' class='wp-smiley' /> 不过，只要不是已经完全跳进棺材了，似乎比正常人更可怕呢，或曰：“我都半只脚踏进棺材了，不怕死的你就放马过来啊！”或许会干劲无穷啊！ <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/biggrin.png' alt=':D' class='wp-smiley' /> </p>
<p>我还被问到一个问题，自己倒是从来没有想过，就是问我觉得大学里对我影响最大的是什么。我想来想去，还是觉得是 MSTC 了。我大学生活中最精彩的部分，都是和 MSTC 有关的。之后我们聊了许多关于 MSTC 的事情，也许具体地从中得到了什么并不能说清楚吧，但是我在这里看着 MSTC 成长，到最后，自己也逐渐成长起来了。后来面试官说从他和我聊的感觉来看，我很不像一个本科生，也许许多人在技术上都很厉害，但是从面试的经历来看，本科生和研究生的最大的一个区别就是本科生通常给人一种很“嫩”的感觉。而从我对许多问题的看法和态度来看，似乎没有那样的感觉，相反，好像感觉是和他们一样的人。说这样的话真的让我很受鼓舞，我想如果我真的有很大的成长的话，大部分还是要归功于 MSTC 吧。其实我也有类似的感觉，本身我是一个很不喜欢和陌生人说话的人，能在第一次见面就让我觉得可以很亲切地说个滔滔不绝的，大概也是因为大家是同一路人吧。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/tongue.png' alt=':p' class='wp-smiley' /> </p>
<p>后来还问了我一个对于百度的印象的问题。也正好是本文的标题了，写到这么后面才进入正题。一开始我说了一堆乱七八糟的，比如自己搜索的时候几乎是不会去用 baidu 的，等等。然后他说他的意思并不是要问我关于百度公司这样一个外部形象或者 www.baidu.com 这个网站的印象。不过我这样说是因为我后面会说另一方面，就好象我们最开始听到 MSTC (Microsoft Technology Club) 这个名字一样，不同的人也许会首先想到 Microsoft ，或者想到 Technology ，然而我觉得去年的纳新宣讲会上萝卜做过的一个 presentation 里面讲得非常好，他把三个词列在一起，然后依次去掉了 Microsoft 和 Technology （今年纳新宣讲会的时候我也用了类似的套路）。就是说，对于一个“外人”来说，你问他关于百度或者关于 MSTC 的印象，他肯定是首先想到的就是这些东西，但是 MSTC 真正吸引我的，还是在我参加过几次他们的小课堂，接触到里面的人之后。这里 baidu 也是一样的，虽然只是三次面试，和三个面试官分别聊了一个小时，但是我觉得对于做技术的人来说，baidu 这个名号或者这个网站以及相关的产品，可以并不是那么重要的，比如海报做得不太形象，笔试题不太合理，然而只要和这样一群人在一起工作，就是一件非常令人向往的事情。关于这个，其实 MSTC 也一直在思考这样一个问题：我们把这样一群人聚集在了一起——这一点我们做得很好，可是我们聚在一起的目的是什么呢？我们想要做一件什么样的事情？大家似乎都有各自的答案，或者说根本就没有答案。不过我最近听到一个说法说“存在”本身就是一种意义的，觉得也是很有趣的见解。</p>
<p>顺便，贴一下今年宣讲会的时候 <a href="http://wyest.blogbus.com/">wyest</a> 做的一个宣传用的签名档，虽然后来有调整以及上色了一下，不过我还是最喜欢这张黑白的：</p>
<p><center><img src='/wp-content/uploads/2008/10/mstc.png' alt='mstc.png' /></center></p>
<p>因为本来下午有事要去 ZJG 和周 core 聊聊选导师的事情，结果没想到连续被面了两次，结束之后面试官问我有没有什么问题，我忙问一共要面多少轮啊。面试官笑着说，一般情况下就三轮了。</p>
<p>由于有 Rhythm 的好朋友，所有后来新产品研发部的几个人又和我一起出去吃了一顿饭。他们叫我推荐地方，实在是找错人了，一来我也是刚搬来玉泉不久，二来我对 FB 实在是没有经验，最后很汗地带他们去了留学生食堂…… ^_^bb 相比起来，觉得网页搜索研发部的几个人给人感觉是比较沉稳亲切一点，而新产品研发部的人似乎更加亢奋热情一点，好像说不了几句话就要开始诸如 flame war 或者说宗教战争的争论了。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/tongue.png' alt=':p' class='wp-smiley' /> 如果用俱乐部里面的人来对应的话，给我的感觉就好象分别是彭鹏和 gbb21 。</p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=472</wfw:commentRss>
		</item>
		<item>
		<title>Google Reader 在 Firefox 下一片空白</title>
		<link>http://lifegoo.pluskid.org/?p=470</link>
		<comments>http://lifegoo.pluskid.org/?p=470#comments</comments>
		<pubDate>Fri, 10 Oct 2008 10:20:48 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Life]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=470</guid>
		<description><![CDATA[其实并不完全是一片空白，上面的一行诸如“Gmail”、“Calendar”以及右边的账户以及注销等按钮都在，只是内容页面完全是空白。刷新、重启 Firefox 、重启系统都没有作用。其实我以前碰到过同样的问题，但是那时好像是 Google Gears 和 Faster Firefox 这样的修改网络参数的扩展冲突的缘故。可是这次我两个扩展都没有装，还是遇到了这个问题。
在 Google 上搜索了一下，发现有不少人遇到这个问题，而且好像相当随机，各个版本的 Firefox 都有遇到过，甚至连 Safari 也有。那看来是相当地和人品相关了！不过我也找到了一个非常简便的解决办法：按住 Shift 键然后点刷新按钮。终于正常了！看样子似乎是缓存损坏掉了吧？
]]></description>
			<content:encoded><![CDATA[<p>其实并不完全是一片空白，上面的一行诸如“Gmail”、“Calendar”以及右边的账户以及注销等按钮都在，只是内容页面完全是空白。刷新、重启 Firefox 、重启系统都没有作用。其实我以前碰到过同样的问题，但是那时好像是 Google Gears 和 Faster Firefox 这样的修改网络参数的扩展冲突的缘故。可是这次我两个扩展都没有装，还是遇到了这个问题。</p>
<p>在 Google 上搜索了一下，发现有不少人遇到这个问题，而且好像相当随机，各个版本的 Firefox 都有遇到过，甚至连 Safari 也有。那看来是相当地和人品相关了！不过我也找到了一个非常简便的解决办法：按住 Shift 键然后点刷新按钮。终于正常了！看样子似乎是缓存损坏掉了吧？</p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=470</wfw:commentRss>
		</item>
		<item>
		<title>Hadoop 实战：谁是最倒霉的人？</title>
		<link>http://lifegoo.pluskid.org/?p=468</link>
		<comments>http://lifegoo.pluskid.org/?p=468#comments</comments>
		<pubDate>Sat, 04 Oct 2008 06:04:31 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=468</guid>
		<description><![CDATA[
上一次介绍了 MapReduce 的工作方式以及 Hadoop 这个开源的 MapReduce 实现，这次尝试用 Hadoop 来写一个简单的应用。要解决的问题是这样的：现在我手里有大量的邮件数据，并且我知道每封邮件是正常邮件还是垃圾邮件，现在我想要找出收到的邮件中垃圾邮件最多的人，亦即找出&#8220;谁是最倒霉的人&#8221;。
首先是 Map 的过程，输入数据是一封一封的邮件，彼此之间没有任何关联，因此可以很自然地分组处理。Map 将邮件转化到以邮件的收件人进行分组，如果邮件是垃圾邮件，则映射到收件人的垃圾邮件数&#8220;+1&#8221;。Reduce 的过程就是将各个收件人的邮件数统计结果加起来。

在 Hadoop 中实现一个 map 过程只需要实现 Mapper 接口就行了，一般同时继承自 MapReduceBase ，可以省下不少力气。map 接受 key, value 的 pair ，这是按照初始输入数据进行分组的，通常 map 方法从 value 中解析感兴趣的内容，并进行重新分组。map 的另一个 OutputCollector 类型的参数就是专门用于收集 map 之后的新分组的。map 方法看起来是这个样子：

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void map&#40;LongWritable key, Text value,
		OutputCollector&#60;text, LongWritable&#62; output, Reporter reporter&#41;
		throws IOException &#123;
	String header = value.toString&#40;&#41;;
&#160;
	if &#40;isSpam&#40;header&#41;&#41; &#123;
		String addr = [...]]]></description>
			<content:encoded><![CDATA[<p><img src='/wp-content/uploads/2008/10/hadoop-logo.gif' alt='hadoop-logo.gif' style="float:right; margin: 0 3px 7px 7px;" />
<p><a href="/?p=461">上一次</a>介绍了 MapReduce 的工作方式以及 Hadoop 这个开源的 MapReduce 实现，这次尝试用 Hadoop 来写一个简单的应用。要解决的问题是这样的：现在我手里有大量的邮件数据，并且我知道每封邮件是正常邮件还是垃圾邮件，现在我想要找出收到的邮件中垃圾邮件最多的人，亦即找出&#8220;谁是最倒霉的人&#8221;。</p>
<p>首先是 Map 的过程，输入数据是一封一封的邮件，彼此之间没有任何关联，因此可以很自然地分组处理。Map 将邮件转化到以邮件的收件人进行分组，如果邮件是垃圾邮件，则映射到收件人的垃圾邮件数&#8220;+1&#8221;。Reduce 的过程就是将各个收件人的邮件数统计结果加起来。</p>
<p><span id="more-468"></span>
<p>在 Hadoop 中实现一个 map 过程只需要实现 Mapper 接口就行了，一般同时继承自 MapReduceBase ，可以省下不少力气。map 接受 key, value 的 pair ，这是按照初始输入数据进行分组的，通常 map 方法从 value 中解析感兴趣的内容，并进行重新分组。map 的另一个 OutputCollector 类型的参数就是专门用于收集 map 之后的新分组的。map 方法看起来是这个样子：</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre class="java" style="font-family:monospace;">@Override
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> map<span style="color: #009900;">&#40;</span>LongWritable key, Text value,
		OutputCollector<span style="color: #339933;">&lt;</span>text, LongWritable<span style="color: #339933;">&gt;</span> output, Reporter reporter<span style="color: #009900;">&#41;</span>
		<span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">IOException</span> <span style="color: #009900;">&#123;</span>
	<span style="color: #003399;">String</span> header <span style="color: #339933;">=</span> value.<span style="color: #006633;">toString</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>isSpam<span style="color: #009900;">&#40;</span>header<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		<span style="color: #003399;">String</span> addr <span style="color: #339933;">=</span> getToAddr<span style="color: #009900;">&#40;</span>header<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>addr <span style="color: #339933;">!=</span> <span style="color: #000066; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
			output.<span style="color: #006633;">collect</span><span style="color: #009900;">&#40;</span><span style="color: #000000; font-weight: bold;">new</span> Text<span style="color: #009900;">&#40;</span>addr<span style="color: #009900;">&#41;</span>, <span style="color: #000000; font-weight: bold;">new</span> LongWritable<span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
	<span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>由于数据需要在网络上传输，Hadoop 要求 Key 和 Value 的类型都必须是可以序列化的，不过这不是 Java 自带的那个序列化接口 <code>Serializable</code> ，而是 Hadoop 自己定义的一个更加简易的 <code>Writable</code> 接口。另外，由于 Key 是需要用于进行排序和分组的，所以需要实现的是更加具体的一个叫做 <code>WriteComparable</code> 的接口。不过，常用的数据类型 Hadoop 都提供了现成的支持，比如 <code>Text</code> 可以用于存放文本，<code>LongWritable</code> 可以用于存放长整型数据。</p>
<p>在这个任务中，我 map 的输入 key 是长整型，在这里是分组时在文件中的 offset ，这里不需要用到它，直接忽略。而 value 是一封邮件的邮件头的内容。首先我要判断邮件是否是垃圾邮件。原本我可以用作好的分类器进行在线分类，或者使用一个已经做好的 index 来进行判断，不过为了让示例简单一些，我对邮件内容进行了预处理，直接将垃圾邮件标记作为一个邮件头域插入了邮件首部。因此判断是否是垃圾邮件的代码是直接从邮件头里搜索相应标签：</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
</pre></td><td class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000066; font-weight: bold;">boolean</span> isSpam<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> header<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
	Matcher matcher <span style="color: #339933;">=</span> labelPattern.<span style="color: #006633;">matcher</span><span style="color: #009900;">&#40;</span>header<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>matcher.<span style="color: #006633;">find</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">&amp;&amp;</span> matcher.<span style="color: #006633;">group</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span>.<span style="color: #006633;">equalsIgnoreCase</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;spam&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>
		<span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000066; font-weight: bold;">true</span><span style="color: #339933;">;</span>
	<span style="color: #000000; font-weight: bold;">else</span>
		<span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000066; font-weight: bold;">false</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>判断出是垃圾邮件之后，就解析出邮件的收件人地址，这里暂时不考虑多个收件人或者有抄送之类的情况。得到收件人之后（比如，是 <code>foo@bar.com</code>），就算得到了一组结果。这将作为后面 reduce 任务的输入：key 的类型是 <code>Text</code> ，亦即收件人地址；value 的类型是 <code>LongWritable</code> ，即收到的垃圾邮件数目。当发现一封垃圾邮件时，就将这个中间结果 <code>(foo@bar.com, 1)</code> 收集起来。</p>
<p>这就完成了 map 的过程，之后应该是 reduce ，这个过程很简单，系统会按照 map 的结果将各个分组的结果传递到 reduce 函数，这里 reduce 只要把各个 <code>LongWritable</code> 加起来得到总和就可以了。在 Hadoop 自带的 Word Count 的示例中有一个类似的例子（不过这里加的是 <code>IntWritable</code>）：</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">class</span> Reduce <span style="color: #000000; font-weight: bold;">extends</span> MapReduceBase
                    <span style="color: #000000; font-weight: bold;">implements</span> Reducer<span style="color: #339933;">&lt;</span>text, IntWritable, Text, IntWritable<span style="color: #339933;">&gt;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> reduce<span style="color: #009900;">&#40;</span>Text key, Iterator<span style="color: #339933;">&lt;</span>intWritable<span style="color: #339933;">&gt;</span> values,
                    OutputCollector<span style="color: #339933;">&lt;</span>text, IntWritable<span style="color: #339933;">&gt;</span> output,
                    Reporter reporter<span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">IOException</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000066; font-weight: bold;">int</span> sum <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span>
        <span style="color: #000000; font-weight: bold;">while</span> <span style="color: #009900;">&#40;</span>values.<span style="color: #006633;">hasNext</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
            sum <span style="color: #339933;">+=</span> values.<span style="color: #006633;">next</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>.<span style="color: #006633;">get</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        <span style="color: #009900;">&#125;</span>
        output.<span style="color: #006633;">collect</span><span style="color: #009900;">&#40;</span>key, <span style="color: #000000; font-weight: bold;">new</span> IntWritable<span style="color: #009900;">&#40;</span>sum<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>不过因为 Hadoop 自带了一个 <code>LongSumReducer</code> 可以完成我们需要做的事情，就不用自己再费力写一个了。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/tongue.png' alt=':p' class='wp-smiley' /> 另外，Hadoop 在 map 和 reduce 之间还有一个叫做 combine 的步骤，可以看作是“本地的 reduce ”。除了某些特殊情况，一般 combine 和 reduce 做的事情是一样的（因此这两个任务通常也是通过同一段代码来实现的），只是 combine 只在本地运行，将当前节点得到的局部结果进行一下局部的 reduce ，这样通常可以减少需要进行网络传输的数据量。例如，如果当前节点发现了 5 封发给 <code>foo@bar.com</code> 的垃圾邮件，需要对 5 个 <code>(foo@bar.com, 1)</code> 进行 reduce 调度，而经过本地 combine 之后，只需要处理一个 <code>(foo@bar.com, 5)</code> 就可以了。</p>
<p>map 和 reduce 做好之后，新建一个任务，并告知用于完成 map 和 reduce 任务的类：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;">countJob.<span style="color: #006633;">setMapperClass</span><span style="color: #009900;">&#40;</span>CountMapper.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
countJob.<span style="color: #006633;">setCombinerClass</span><span style="color: #009900;">&#40;</span>LongSumReducer.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
countJob.<span style="color: #006633;">setReducerClass</span><span style="color: #009900;">&#40;</span>LongSumReducer.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>再设置输入输出的 key 和 value 的类型：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;">countJob.<span style="color: #006633;">setInputKeyClass</span><span style="color: #009900;">&#40;</span>LongWritable.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
countJob.<span style="color: #006633;">setInputValueClass</span><span style="color: #009900;">&#40;</span>Text.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
countJob.<span style="color: #006633;">setOutputKeyClass</span><span style="color: #009900;">&#40;</span>Text.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
countJob.<span style="color: #006633;">setOutputValueClass</span><span style="color: #009900;">&#40;</span>LongWritable.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>配置好之后调用 <code>JobClient.runJob(countJob)</code> 就可以开始 MapReduce 了。大致的流程就是这样，不过中间还有一些细节需要处理。一个问题就是结果如何输出？我们最终得到的是一些 <code>(Text, LongWritable)</code> 的 pair ，一个办法是让它以文本方式按行输出到文本文件中，这样只需要使用内置的 <code>SequenceFileOutputFormat</code> 即可：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;">countJob.<span style="color: #006633;">setOutputFormat</span><span style="color: #009900;">&#40;</span>SequenceFileOutputFormat.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>另一个问题则是输入的格式，Hadoop 需要理解了输入文件的格式才能将其解析并作为参数传递给 map 函数，更重要的是：进行合适的分割，将任务分配到各个节点上去。Hadoop 使用 <code>InputFormat</code> 来控制输入格式，默认情况下，将输入目录中每个文件当作以行为单位的文本进行处理和分割，除此之外 Hadoop 也内置了二进制记录等文件类型的支持。不过我这里的情况比较特殊：一个文件中只有一封邮件，需要当作一个不可分割的原子来处理。因此我实现了一个 <code>AtomFileInputFormat</code> ，并在 <code>isSplitable</code> 方法中总是返回 <code>false</code> ：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">class</span> AtomFileInputFormat <span style="color: #000000; font-weight: bold;">extends</span> FileInputFormat<span style="color: #339933;">&lt;</span>longWritable, Text<span style="color: #339933;">&gt;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
	@Override
	<span style="color: #000000; font-weight: bold;">public</span> RecordReader<span style="color: #339933;">&lt;</span>longWritable, Text<span style="color: #339933;">&gt;</span> getRecordReader<span style="color: #009900;">&#40;</span>InputSplit genericSplit,
			JobConf job, Reporter reporter<span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">IOException</span> <span style="color: #009900;">&#123;</span>
		reporter.<span style="color: #006633;">setStatus</span><span style="color: #009900;">&#40;</span>genericSplit.<span style="color: #006633;">toString</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000000; font-weight: bold;">new</span> FileRecordReader<span style="color: #009900;">&#40;</span>job, <span style="color: #009900;">&#40;</span>FileSplit<span style="color: #009900;">&#41;</span>genericSplit<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
&nbsp;
	@Override
	<span style="color: #000000; font-weight: bold;">protected</span> <span style="color: #000066; font-weight: bold;">boolean</span> isSplitable<span style="color: #009900;">&#40;</span>FileSystem fs, Path path<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		<span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000066; font-weight: bold;">false</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>另外，我定制了一个 <code>FileRecordReader</code> （需要实现 <code>RecordReader</code> 接口），从每个文件中读取邮件头，丢掉邮件的内容部分。</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;">@Override
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">boolean</span> next<span style="color: #009900;">&#40;</span>LongWritable key, Text value<span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">IOException</span> <span style="color: #009900;">&#123;</span>
	<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>hasRead<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		<span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000066; font-weight: bold;">false</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span> <span style="color: #000000; font-weight: bold;">else</span> <span style="color: #009900;">&#123;</span>
		Path file <span style="color: #339933;">=</span> split.<span style="color: #006633;">getPath</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		FileSystem fs <span style="color: #339933;">=</span> file.<span style="color: #006633;">getFileSystem</span><span style="color: #009900;">&#40;</span>job<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
		key.<span style="color: #006633;">set</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
		FSDataInputStream in <span style="color: #339933;">=</span> fs.<span style="color: #006633;">open</span><span style="color: #009900;">&#40;</span>file<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #003399;">BufferedReader</span> reader <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">BufferedReader</span><span style="color: #009900;">&#40;</span><span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">InputStreamReader</span><span style="color: #009900;">&#40;</span>in<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
		StringBuilder sb <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> StringBuilder<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000000; font-weight: bold;">while</span> <span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
			<span style="color: #003399;">String</span> line <span style="color: #339933;">=</span> reader.<span style="color: #006633;">readLine</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
			<span style="color: #666666; font-style: italic;">// empty string, email headers and body are separated by an</span>
                        <span style="color: #666666; font-style: italic;">// empty line</span>
			<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>line <span style="color: #339933;">==</span> <span style="color: #000066; font-weight: bold;">null</span> <span style="color: #339933;">||</span> line.<span style="color: #006633;">length</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span>
				<span style="color: #000000; font-weight: bold;">break</span><span style="color: #339933;">;</span>
			sb.<span style="color: #006633;">append</span><span style="color: #009900;">&#40;</span>line<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
			sb.<span style="color: #006633;">append</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'<span style="color: #000099; font-weight: bold;">\n</span>'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
		value.<span style="color: #006633;">set</span><span style="color: #009900;">&#40;</span>sb.<span style="color: #006633;">toString</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
		reader.<span style="color: #006633;">close</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
		pos <span style="color: #339933;">=</span> split.<span style="color: #006633;">getLength</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		hasRead <span style="color: #339933;">=</span> <span style="color: #000066; font-weight: bold;">true</span><span style="color: #339933;">;</span>
		<span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000066; font-weight: bold;">true</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>这样差不多任务就完成了，不过还有一点就是最后的结果是按照 key 进行排序的，亦即按照收件人地址排序，而我期望找到“最倒霉”的人，甚至需要按照“倒霉程度”进行排序，所以我再启动另一个 MapReduce 任务，将 key 和 value 颠倒过来，并进行排序。由于这是一个非常常见的任务，Hadoop 内置了相应的支持，因此只需要把内置的 <code>InverseMapper</code> 和 <code>IdentityReducer</code> 组装起来即可：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;">sortJob.<span style="color: #006633;">setInputFormat</span><span style="color: #009900;">&#40;</span>SequenceFileInputFormat.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
sortJob.<span style="color: #006633;">setMapperClass</span><span style="color: #009900;">&#40;</span>InverseMapper.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
sortJob.<span style="color: #006633;">setNumReduceTasks</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// write a single file</span>
FileOutputFormat.<span style="color: #006633;">setOutputPath</span><span style="color: #009900;">&#40;</span>sortJob, <span style="color: #000000; font-weight: bold;">new</span> Path<span style="color: #009900;">&#40;</span>args<span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
sortJob.<span style="color: #006633;">setOutputKeyComparatorClass</span><span style="color: #009900;">&#40;</span>LongWritable.<span style="color: #006633;">DecreasingComparator</span>.<span style="color: #000000; font-weight: bold;">class</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>这样就能得到最终结果了！完整的代码可以从<a href="/upload/468/worst_luck_people.jar">这里</a>下载。</p>
<p>最后，总结一下：这样一个简单的 <code>AtomFileInputFormat</code> 似乎没有达到预期的目的，Hadoop 默认情况下似乎把“一个文件”当作一个“很大”的单位了，通常都是考虑将文件进行分割，再分派到各个节点，因此每个文件最少启动一个 map 任务，而现在我的情况是一个文件中只有一条数据，结果似乎是每一个 map 操作都启动了一个新的 map 任务。如果要改进的话，一个是更加深入地定制 <code>InputFormat</code> 的风割方法；另一个办法是对输入数据进行预处理，后者要方便一些，但是在实际应用中如果遇到数据量大到进行预处理本身就需要 MapReduce 的支持的话，就有些不现实了。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/wink.png' alt=';)' class='wp-smiley' /> 另外，这种分布式的应用出了问题之后不知道要怎样调试才好？且不说调试器的方法肯定不管用了，就算最原始的 <code>printf</code> 大法，也不知道最终的输出会 print 到苍穹的哪一个角落里去了 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/tongue.png' alt=':p' class='wp-smiley' /> 必须要有一个强大的 log 系统才行，而且如果是在单机环境下也能成功重现的那种 bug 的话，应该也会好姐姐一些。</p>
<p>总之，在上一次介绍的时候我就说了：MapReduce 是对分布式任务进行了一定的限制而得到的结果，考虑到 80%/20% 原则，它可以相当棒地完成一部分类型的任务（也许还不到 80% 吧），但是如果妄图任何事情都要用它来搞，大概也就是削足而适履了。还有就是，没有像样的硬件环境，玩 MapReduce 真的一点都不好玩！ <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/biggrin.png' alt=':D' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=468</wfw:commentRss>
		</item>
		<item>
		<title>CS 科普：计算机如何做分类</title>
		<link>http://lifegoo.pluskid.org/?p=462</link>
		<comments>http://lifegoo.pluskid.org/?p=462#comments</comments>
		<pubDate>Fri, 03 Oct 2008 04:07:25 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Computer Science]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=462</guid>
		<description><![CDATA[本来是发在校内的 cc98 论坛上为 MSTC 的 staff 们科普用的，顺便转到这里来一下。
分类是一项非常基本的任务，例如，拿到一包奶粉，要判断它是不是三鹿牌的，“是”与“不是”，这是一个二元分类的问题。在杀毒的时候判断一个文件是否是病毒，也是一个二元分类问题。再比如，在玩杀人游戏的时候，对每一个人，你要判断出他是“平民”、“警察”或者是“杀手”，这是一个分做三类的问题，类别多余二的情况都可以通过组合多个二元分类来完成，比如，一种组合方式可以是：首先使用“是否是平民”进行分类，如果是，则分类完成，否则，再“是否是警察” 进行分类，如果是则分类完成，否则归为“杀手”类。所以通常二元分类是需要解决的最基本的问题。
然而相对于做大量重复、复杂的计算来讲，做分类并不是电脑所擅长的事情——在反思过我们人类如何来做这样的事情之后大概会明白为什么了。那么试想这样的场景：现在假设有一包奶粉……恩，还是换个例子吧，一包方便面，让你判断它是不是康师傅的方便面。你会如何做判断呢？
在看到这包方便面之前，你只能胡猜了，因为你没有得到任何关于这包面的可以有助于你判断的信息。但是其实也并不是胡猜，而是根据你事先已经知道的一些知识来进行猜测，如果说要你猜测这包面是康师傅的概率的话，这个可以叫做“先验概率”。比如，我如果让你猜测这包面试不是“kid 牌”的，你八成会猜不是，因为根据你事先已经知道的知识，自己并没有听说过有“kid 牌”方便面，所以这包方便面是“kid 牌”的（先验）概率非常小。因此，这是人相对于计算机的最大的优势：有非常庞大并且组织良好易于检索的记忆系统，存储了非常非常多的知识作为后备。
那么接下来，你看到了这包方便面，似乎就不用做猜测了，可以肯定地告诉我这是不是康师傅的方便面。其实还是在做猜测，只是这次得到了更多关于要分类的这个物品的信息，让你对于它的分类问题更加有信心了。对于观察到对象的相关“特征”之后再得到的概率，我们称之为“后验概率”。比如，你看到常见的包装的颜色、花样以及差不多的 Logo ，于是断定这是康师傅方便面，却没有注意到这其实是一个叫做“康帅博”的牌子的方便面。

所以，人在做这样的分类的时候，也是通过观察一些“特征”来进行“猜测”的。对于电脑来说，它也可以采用和人类是的方法来进行“猜测”。令 P(t) 是这包方便面“是康师傅方便面”的概率，取一个阈值 P0 （比如，0.5），计算机在设法估计出 P(t) 之后，再和 P0 进行比较，如果大于 P0 ，则认为“是”，否则“不是”。这里的难点在于“估算 P”，如果当作一个先验概率而不考虑当前“输入”的话，基本上又回到了“胡猜”的场景。所以将其看作后验概率是更常见的做法：P(t&#124;x) 是条件概率，表示“当观察到 x 已经发生的时候，t 发生的概率”，用到这里就是“当观察到这包方便面的各种特征的时候，这包方便面是康师傅方便面的概率”。其中，x 是一个向量，向量的每个分量表示一个特定的特征，例如：

x = (有没有出现“康师傅”的字样, 包装是不是红色的, 有没有“统一”的商标, ...)

当观察到特征取如下值

x = (有“康师傅”的字样, 包装是红色的, 没有“统一”的商标, ...)

的时候是康师傅方便面的概率比较大。当然，计算机需要更加形式化的方法：有一个叫做贝叶斯定理的公式：

其中 P(t&#124;x) 是要估算的概率，而等式右边分母上的 P(x) 是特征出现的概率，比如，如果我们知道市面上的方便面包装的颜色只有红色和黄色两种，并且两种数量差不多，那么“包装是红色”这个特征出现的概率就可以认为是 0.5 。这些通常都作为参数通过人自己先验的“知识”显式地告知给计算机。而分子上的两项：P(t) 是我们最开始在没有观察到任何特征的情况下得到的那个“胡猜”的先验概率；而 P(x&#124;t) 则表示假如这包方便面已知是康师傅方便面了，在这样的情况下它出现这些特征的概率（比如，康师傅方便面的包装是红颜色的概率）。
因此，通过贝叶斯定理，可以把问题从求 P(t&#124;x) 转化为了求 P(x&#124;t) 。许多分类算法都是通过直接求 P(t&#124;x) 或者先求 P(x&#124;t) [...]]]></description>
			<content:encoded><![CDATA[<p><small>本来是发在校内的 cc98 论坛上为 MSTC 的 staff 们科普用的，顺便转到这里来一下。</small></p>
<p>分类是一项非常基本的任务，例如，拿到一包奶粉，要判断它是不是三鹿牌的，“是”与“不是”，这是一个二元分类的问题。在杀毒的时候判断一个文件是否是病毒，也是一个二元分类问题。再比如，在玩杀人游戏的时候，对每一个人，你要判断出他是“平民”、“警察”或者是“杀手”，这是一个分做三类的问题，类别多余二的情况都可以通过组合多个二元分类来完成，比如，一种组合方式可以是：首先使用“是否是平民”进行分类，如果是，则分类完成，否则，再“是否是警察” 进行分类，如果是则分类完成，否则归为“杀手”类。所以通常二元分类是需要解决的最基本的问题。</p>
<p><span id="more-462"></span>然而相对于做大量重复、复杂的计算来讲，做分类并不是电脑所擅长的事情——在反思过我们人类如何来做这样的事情之后大概会明白为什么了。那么试想这样的场景：现在假设有一包奶粉……恩，还是换个例子吧，一包方便面，让你判断它是不是康师傅的方便面。你会如何做判断呢？</p>
<p>在看到这包方便面之前，你只能胡猜了，因为你没有得到任何关于这包面的可以有助于你判断的信息。但是其实也并不是胡猜，而是根据你事先已经知道的一些知识来进行猜测，如果说要你猜测这包面是康师傅的概率的话，这个可以叫做“先验概率”。比如，我如果让你猜测这包面试不是“kid 牌”的，你八成会猜不是，因为根据你事先已经知道的知识，自己并没有听说过有“kid 牌”方便面，所以这包方便面是“kid 牌”的（先验）概率非常小。因此，这是人相对于计算机的最大的优势：有非常庞大并且组织良好易于检索的记忆系统，存储了非常非常多的知识作为后备。</p>
<p>那么接下来，你看到了这包方便面，似乎就不用做猜测了，可以肯定地告诉我这是不是康师傅的方便面。其实还是在做猜测，只是这次得到了更多关于要分类的这个物品的信息，让你对于它的分类问题更加有信心了。对于观察到对象的相关“特征”之后再得到的概率，我们称之为“后验概率”。比如，你看到常见的包装的颜色、花样以及差不多的 Logo ，于是断定这是康师傅方便面，却没有注意到这其实是一个叫做“康帅博”的牌子的方便面。</p>
<p><center><img src='/wp-content/uploads/2008/10/ksb.jpg' alt='ksb.jpg' /></center></p>
<p>所以，人在做这样的分类的时候，也是通过观察一些“特征”来进行“猜测”的。对于电脑来说，它也可以采用和人类是的方法来进行“猜测”。令 P(t) 是这包方便面“是康师傅方便面”的概率，取一个阈值 P0 （比如，0.5），计算机在设法估计出 P(t) 之后，再和 P0 进行比较，如果大于 P0 ，则认为“是”，否则“不是”。这里的难点在于“估算 P”，如果当作一个先验概率而不考虑当前“输入”的话，基本上又回到了“胡猜”的场景。所以将其看作后验概率是更常见的做法：P(t|x) 是条件概率，表示“当观察到 x 已经发生的时候，t 发生的概率”，用到这里就是“当观察到这包方便面的各种特征的时候，这包方便面是康师傅方便面的概率”。其中，x 是一个向量，向量的每个分量表示一个特定的特征，例如：</p>
<pre>
x = (有没有出现“康师傅”的字样, 包装是不是红色的, 有没有“统一”的商标, ...)
</pre>
<p>当观察到特征取如下值</p>
<pre>
x = (有“康师傅”的字样, 包装是红色的, 没有“统一”的商标, ...)
</pre>
<p>的时候是康师傅方便面的概率比较大。当然，计算机需要更加形式化的方法：有一个叫做贝叶斯定理的公式：</p>
<p><center><img src='/wp-content/uploads/2008/10/bayes.jpg' alt='bayes.jpg' /></center></p>
<p>其中 P(t|x) 是要估算的概率，而等式右边分母上的 P(x) 是特征出现的概率，比如，如果我们知道市面上的方便面包装的颜色只有红色和黄色两种，并且两种数量差不多，那么“包装是红色”这个特征出现的概率就可以认为是 0.5 。这些通常都作为参数通过人自己先验的“知识”显式地告知给计算机。而分子上的两项：P(t) 是我们最开始在没有观察到任何特征的情况下得到的那个“胡猜”的先验概率；而 P(x|t) 则表示假如这包方便面已知是康师傅方便面了，在这样的情况下它出现这些特征的概率（比如，康师傅方便面的包装是红颜色的概率）。</p>
<p>因此，通过贝叶斯定理，可以把问题从求 P(t|x) 转化为了求 P(x|t) 。许多分类算法都是通过直接求 P(t|x) 或者先求 P(x|t) 再得到 P(t|x) 的办法来做的。然而，人之所以能根据特征进行判断，是因为人有一个知识系统，可以帮助判断。计算机要做同样的事，就必须也要建立一个系统——或者称之为模型，这个模型要经过“训练”——或者称之为“学习”之后才能用来做分类。所谓学习的过程，就是给它一包一包的方便面，然后告诉它，这是康师傅、这不是康师傅……在“阅方便面无数”之后，模型逐渐完整起来，就可以用来进行分类了。</p>
<p>而学习的过程——亦即训练模型的过程，通常因为各种算法的模型各异而各不相同。在这里介绍一种叫做“朴素贝叶斯”的算法。根据上面的贝叶斯定理，其中的 x 是一个各种特征组成的向量，“朴素贝叶斯”的“朴素”在于假定各个特征之间是相互无关的，比如说，“方便面的包装是否红色”和“方便面是否标有康师傅标志 ”这两个特征之间不会相互影响。虽然事实上许多特征之间或多或少都会相互关联，但是从朴素贝叶斯算法的实际应用来看，效果还是相当不错的。</p>
<p>在各个特征相互独立的情况下，根据概率论里的乘法原理：</p>
<p><center><img src='/wp-content/uploads/2008/10/naive_bayes.jpg' alt='naive_bayes.jpg' /></center></p>
<p>这样只要求出 P(xi|t) ，就能得到 P(t|x) 。而 P(xi|t) 则可以很方便地通过学习得到一个估计值。比如，假设学习的时候给定了 1000 包方便面，其中 800 包是康师傅方便面，而 800 包康师傅方便面中有 600 包的包装是红色的，因此“包装是红色的”这个特征在“康师傅方便面”的条件下发生的概率可以被估计为 600/800 = 0.75 。类似的办法可以在学习的过程中求得特征向量 x 的所有分量的条件概率估计值，这样就完成了一个可用的朴素贝叶斯分类器的模型。</p>
<p>其中一个重要的步骤就是使用从训练数据中的统计结果（0.75）来近似真是概率，然而这个近似是否足够好，很大程度上取决于学习用的训练数据是否能如实反映出真实世界了。亦即是说，分类器的好坏不仅取决于算法和模型的好坏，训练数据的好坏通常也是至关重要的，就算厉害如欧阳峰，给他一本乱写的九阴真经，他也最终走火入魔了。</p>
<p>到此打住吧，再往下就不是科普了，不知道是否说清楚了。</p>
<p>最后，由于今天听了不少钢琴曲，所以涂一幅钢琴的吧，顺便在贴一下 7 月份刚搬到玉泉时画的一幅笛子的图：</p>
<p><center><img src='/wp-content/uploads/2008/10/piano.jpg' alt='piano.jpg' /></center></p>
<p><center><img src='/wp-content/uploads/2008/10/flute.jpg' alt='flute.jpg' /></center></p>
<p>恩，其实不太了解钢琴的结构，第一幅图似乎画错了，钢琴后面的盖子都忘记画了 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/tongue.png' alt=':p' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=462</wfw:commentRss>
		</item>
		<item>
		<title>MapReduce 初体验</title>
		<link>http://lifegoo.pluskid.org/?p=461</link>
		<comments>http://lifegoo.pluskid.org/?p=461#comments</comments>
		<pubDate>Wed, 01 Oct 2008 15:55:16 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Life]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=461</guid>
		<description><![CDATA[最近一直都比较忙，blog 都好久没有更新了，不过正好今天十一开始放假，趁机了解了一下 MapReduce 相关的东西，想着还是把自己的经历记下来吧。
MapReduce 最先由 Google 实现并在论文“MapReduce: Simplified Data Processing on Large Clusters”中描述。差不多就是对计算模型做一些限制，让情况得以简化，在有 MapReduce 库支持的情况下，可以非常方便地写出一个应用，并且能够自如地在大型分布式系统里 scale 自如。
map 和 reduce 两个词来源于 Lisp 里的两个基本的函数，其中 map 是将一组元素映射到另一组元素的操作，而 reduce 则是讲一组元素组合起来得到最终结果的过程。举一个简单的例子，对一个数组 arr 里的数求平方和，就是先将 arr 通过“求平方”的 map 操作，然后再通过“加”操作 reduce 为最终结果，在 Ruby 里可以这样写（Ruby 里 reduce 被叫做 inject）：

arr.map &#123; &#124;e&#124; e*e &#125;.inject&#40;0&#41; &#123; &#124;sum, e&#124; sum + e &#125;

但是 MapReduce 里的 map 其实做的事情比 [...]]]></description>
			<content:encoded><![CDATA[<p>最近一直都比较忙，blog 都好久没有更新了，不过正好今天十一开始放假，趁机了解了一下 MapReduce 相关的东西，想着还是把自己的经历记下来吧。</p>
<p><a href="http://en.wikipedia.org/wiki/MapReduce">MapReduce</a> 最先由 Google 实现并在论文“<a href="http://labs.google.com/papers/mapreduce-osdi04.pdf">MapReduce: Simplified Data Processing on Large Clusters</a>”中描述。差不多就是对计算模型做一些限制，让情况得以简化，在有 MapReduce 库支持的情况下，可以非常方便地写出一个应用，并且能够自如地在大型分布式系统里 scale 自如。</p>
<p>map 和 reduce 两个词来源于 Lisp 里的两个基本的函数，其中 map 是将一组元素映射到另一组元素的操作，而 reduce 则是讲一组元素组合起来得到最终结果的过程。举一个简单的例子，对一个数组 <code>arr</code> 里的数求平方和，就是先将 <code>arr</code> 通过“求平方”的 map 操作，然后再通过“加”操作 reduce 为最终结果，在 Ruby 里可以这样写（Ruby 里 reduce 被叫做 <code>inject</code>）：</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">arr.<span style="color:#9900CC;">map</span> <span style="color:#006600; font-weight:bold;">&#123;</span> <span style="color:#006600; font-weight:bold;">|</span>e<span style="color:#006600; font-weight:bold;">|</span> e<span style="color:#006600; font-weight:bold;">*</span>e <span style="color:#006600; font-weight:bold;">&#125;</span>.<span style="color:#9900CC;">inject</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#006666;">0</span><span style="color:#006600; font-weight:bold;">&#41;</span> <span style="color:#006600; font-weight:bold;">&#123;</span> <span style="color:#006600; font-weight:bold;">|</span>sum, e<span style="color:#006600; font-weight:bold;">|</span> sum <span style="color:#006600; font-weight:bold;">+</span> e <span style="color:#006600; font-weight:bold;">&#125;</span></pre></div></div>

<p>但是 MapReduce 里的 map 其实做的事情比 Lisp 里的 map 要稍微多一点：在分布式的世界里，数据量经常都非常大，一个最常用的处理办法就是分组处理，而分组的规则可以是各种各样，原始数据的分组方式也许并不适合问题的处理，需要换一种方式进行分组，而这个分组变换的工作也在 map 里完成。</p>
<p><span id="more-461"></span>reduce 拿到的数据就是已经按照适合问题处理的分组方式分好的数据，因此就可以很简单地把它们组合到一起，得到最终结果了。</p>
<p>再举一个很简单的例子：统计文档中各个单词出现的次数。文档的数量会很大，因此可能被分组进行存储，分组的方式是（比如）按照文档的来源进行分类，从同一个 host 下载的文档被放到一起。完成这个任务的 map 函数要做两件事情：</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #008000;">map</span><span style="color: black;">&#40;</span>doc, words<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">for</span> word <span style="color: #ff7700;font-weight:bold;">in</span> words:
        emit<span style="color: black;">&#40;</span>word, <span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span></pre></div></div>

<p>一是将单词映射为“1”，亦即表示单词出现了一次；二是将原来的分组方式从“按照 doc 分组”变为“按照 word 分组”。按照 word 被分到同一组的数据会最终传输给 reduce ，它则负责进行化归得到结果：</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #008000;">reduce</span><span style="color: black;">&#40;</span>key, values<span style="color: black;">&#41;</span>:
    result = <span style="color: #ff4500;">0</span>
    <span style="color: #ff7700;font-weight:bold;">for</span> v <span style="color: #ff7700;font-weight:bold;">in</span> values:
        result += v
    emit result</pre></div></div>

<p>由于最初的数据是分组的，因此可以将各个组的数据在不同的节点上并行地处理；又由于 map 的结果也是分组的，所以各个组的 reduce 工作也可以在不同的节点上并行执行。这便是 MapReduce 的威力所在。当然这只是问题的一个大致描述，真正实现一个可以在实际系统中应用的 MapReduce 系统实际上还是会碰到很多棘手的问题，Google 的那篇论文可以作为一个参考，另外，也有若干开源的 <a href="http://en.wikipedia.org/wiki/MapReduce#Implementations">MapReduce 实现</a>可以参考。</p>
<p>不过大部分人都不会想要自己去实现这样一个系统，如果只是想要用这样的系统的话，有不少开源的实现可以用，例如：</p>
<ul>
<li><a href="http://discoproject.org/">disco</a>：核心由 erlang 写成，外部接口是 Python 的。</li>
<li><a href="http://skynet.rubyforge.org/">skynet</a>：一个 Ruby 的 MapReduce 实现。</li>
<li><a href="http://en.wikipedia.org/wiki/Hadoop">Hadoop</a>：一个非常成熟的 Java 的 MapReduce 实现。</li>
</ul>
<p>我首先尝试了一下 disco ，至少它看起来是很 promising 的：核心是用 erlang 写的，erlang 目前也是越来越红火，许多分布式系统都采用 erlang 来实现，也正是证明了它自己的价值。而接口采用 Python 的方式，也是处于易用性的考虑，用起来会非常方便。而且这个项目是由 <a href="http://research.nokia.com/">Nokia Research Center</a> 发起的。</p>
<p>不过 disco 这个项目似乎还是处于相当早期的阶段，现在可以下载到的发布版本是 0.1 ，虽然对版本号的解释方式各有喜好，也有像 Google 这样的<a href="http://tech.slashdot.org/article.pl?sid=08/09/25/1235216">千年 beta</a> 存在。 ^_^bb</p>
<p>不过其实 disco 的目的就是要“轻量级”，引用其官方主页上的原话是：</p>
<blockquote><p>
&#8230;a <b>lightweight</b> framework for <b>rapid</b> <b>scripting</b> of distributed data processing tasks.
</p></blockquote>
<p>于是我便去下载下来尝试了一下。我没有两千个节点的 cluster 可以玩，不过我还是能方便地弄到两三台 Linux 机器（其实也可以在单机上玩，或者用数个虚拟机组成一个虚拟网络）。按照 disco 主页上的 setup instructions 来安装。</p>
<p>一开始安装的时候就出了一点问题，erlang 在源码的某一行报了一个很专业的错误，搜索了一下似乎没有什么结果，最后在 disco 的邮件列表里看到似乎是由于 erlang 的版本太老了。那台 Linux 机器似乎是一个比较老版本的 Ubuntu 了，源里的 erlang 版本相当老。去下载了最新版本的 erlang 源码包编译安装上之后，disco 也能正常安装了。第二台机器是 Arch ，源里的 erlang 已经是非常新的版本了，所以安装很容易。</p>
<p>之后按照指南上进行配置，建立用户、创建相应的目录、修改权限、修改 hosts 文件、配置 ssh 访问等等，实在是相当麻烦，就不在这里一一详述了。最后，一切准备就绪，便开始尝试其指南里给的那个 count word 的例子：</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">sys</span>
<span style="color: #ff7700;font-weight:bold;">from</span> disco <span style="color: #ff7700;font-weight:bold;">import</span> Disco, result_iterator
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> fun_map<span style="color: black;">&#40;</span>e, params<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: black;">&#91;</span><span style="color: black;">&#40;</span>w, <span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">for</span> w <span style="color: #ff7700;font-weight:bold;">in</span> e.<span style="color: black;">split</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span><span style="color: black;">&#93;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> fun_reduce<span style="color: black;">&#40;</span><span style="color: #008000;">iter</span>, out, params<span style="color: black;">&#41;</span>:
    s = <span style="color: black;">&#123;</span><span style="color: black;">&#125;</span>
    <span style="color: #ff7700;font-weight:bold;">for</span> w, f <span style="color: #ff7700;font-weight:bold;">in</span> <span style="color: #008000;">iter</span>:
        s<span style="color: black;">&#91;</span>w<span style="color: black;">&#93;</span> = s.<span style="color: black;">get</span><span style="color: black;">&#40;</span>w, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span> + <span style="color: #008000;">int</span><span style="color: black;">&#40;</span>f<span style="color: black;">&#41;</span>
    <span style="color: #ff7700;font-weight:bold;">for</span> w, f <span style="color: #ff7700;font-weight:bold;">in</span> s.<span style="color: black;">iteritems</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
        out.<span style="color: black;">add</span><span style="color: black;">&#40;</span>w, f<span style="color: black;">&#41;</span>
&nbsp;
master = <span style="color: #dc143c;">sys</span>.<span style="color: black;">argv</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">1</span><span style="color: black;">&#93;</span>
<span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;Starting Disco job..&quot;</span>
<span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;Go to %s to see status of the job.&quot;</span> <span style="color: #66cc66;">%</span> master
results = Disco<span style="color: black;">&#40;</span>master<span style="color: black;">&#41;</span>.<span style="color: black;">new_job</span><span style="color: black;">&#40;</span>
                name = <span style="color: #483d8b;">&quot;wordcount&quot;</span>,
                <span style="color: #008000;">input</span> = <span style="color: black;">&#91;</span><span style="color: #483d8b;">&quot;http://discoproject.org/chekhov.txt&quot;</span><span style="color: black;">&#93;</span>,
                <span style="color: #008000;">map</span> = fun_map,
                <span style="color: #008000;">reduce</span> = fun_reduce<span style="color: black;">&#41;</span>.<span style="color: black;">wait</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;Job done. Results:&quot;</span>
<span style="color: #ff7700;font-weight:bold;">for</span> word, frequency <span style="color: #ff7700;font-weight:bold;">in</span> result_iterator<span style="color: black;">&#40;</span>results<span style="color: black;">&#41;</span>:
        <span style="color: #ff7700;font-weight:bold;">print</span> word, frequency</pre></div></div>

<p>可是运行不起来，似乎是 disco 在安装的时候并没有把 Python 相关的库安装到 Python 的 site-packages 下面。手工指定 <code>PYTHONPATH</code> 之后，脚本可以跑起来了，但是还是报错了，从一大堆错误输出中找到了“Internal Server Error”的关键词。通过 disco server 的 web 页面登录上去修改节点的配置之类的也是只是给一个简单的提示说有错误，也不说具体是什么错误。弄了半天也没弄清楚是怎么回事，只好先放弃了。</p>
<p>然后我又尝试了 Hadoop ，相比起来，Apache 社区下的 Hadoop 应该算是非常成熟的项目了，雅虎做了很多贡献，并将其成功应用到了自己的大规模集群上。不过仔细一看，最新的 Hadoop 其实版本号也只有 0.18 ^_^bb 而且还有一个比较寒的地方：下载下来的 hadoop-0.18.1.tar.gz 其实是被用 gzip 压缩了两次，应该是 hadoop-0.18.1.tar.gz.gz 才对，要用 gunzip 解压两次才行。-.-bb</p>
<p>不过，按照 <a href="http://hadoop.apache.org/core/docs/current/quickstart.html">Quickstart</a> 中的步骤，很容易就能把 Hadoop 跑起来，它有三种模式：</p>
<ul>
<li>Standalone Operation：只有一个 Java 虚拟机在跑，完全没有分布式的成分。</li>
<li>Pseudo-Distributed Operation：在同一台机器上启动独立数个 JVM 进程，进行“分布式”操作。</li>
<li>Fully-Distributed Operation：真正的可以运行于多台机器上的分布式模式。</li>
</ul>
<p>前面两种模式都很容易测试，要进行完全分布式的测试则相对麻烦一些。由于我手上的 Linux 机器都是不同的系统（Debian、Ubuntu、Arch），配置各个节点不能统一地进行，一一配置太麻烦了，所以最后我只用了两台机器做测试。Hadoop 需要一个 dfs 的中央服务器节点，一个 job tracker 的中央服务器节点，剩下的则是从节点，用于执行 map 和 reduce 的任务。我把 10.13.122.229 同时用作 dfs 和和 job tracker 的中央服务器节点（同时也作一个从节点），而 10.214.17.222 作为一个从节点。其中 Hadoop 的 site 配置文件 conf/hadoop-site.xml 如下：</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;"><span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;?xml</span> <span style="color: #000066;">version</span>=<span style="color: #ff0000;">&quot;1.0&quot;</span><span style="color: #000000; font-weight: bold;">?&gt;</span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;?xml-stylesheet</span> <span style="color: #000066;">type</span>=<span style="color: #ff0000;">&quot;text/xsl&quot;</span> <span style="color: #000066;">href</span>=<span style="color: #ff0000;">&quot;configuration.xsl&quot;</span><span style="color: #000000; font-weight: bold;">?&gt;</span></span>
&nbsp;
<span style="color: #808080; font-style: italic;">&lt;!-- Put site-specific property overrides in this file. --&gt;</span>
&nbsp;
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;configuration<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;property<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
                <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>fs.default.name<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
                <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;value<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>hdfs://10.13.122.229:9000/<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/value<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/property<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;property<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
                <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>mapred.job.tracker<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/name<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
                <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;value<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>10.13.122.229:9001<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/value<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/property<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/configuration<span style="color: #000000; font-weight: bold;">&gt;</span></span></span></pre></div></div>

<p>再在 conf/slaves 中列出从节点的 ip 地址或者是主机名。这样大致的一个测试系统就配置好了，把 hadoop 复制到从节点的相同路径下面，并配置好相互的无密码的 SSH 访问。接下来首先将 hdfs 文件系统格式化：</p>
<pre>
bin/hadoop namenode -format
</pre>
<p>HDFS 是 Hadoop 项目里实现的一个分布式文件系统，一个 MapReduce 实现有一个稳定高效的分布式文件系统支持是最好的了，Google 的实现有 GFS 支持，而其他的大部分实现也都可以建立在 <a href="http://aws.amazon.com/ec2/">Amazon Elastic Compute Cloud (Amazon EC2)</a> 之上。不过，让文件系统节点和 MapReduce 的任务节点是同样的集群当然是首选的，这样 MapReduce 的调度可以根据数据的分布来调度任务，尽量让数据可以从本地取到，避免大量数据阻塞网络造成瓶颈的情况。</p>
<p>不过 HDFS 有一个缺点就是并不是像普通的 Linux 文件系统那样直接 mount 上使用的，而是提供了命令行接口，需要显式地将数据存入文件系统或者从中取出来，例如：</p>
<pre>
bin/hadoop fs -put foo bar
</pre>
<p>就是把本地文件系统中的 foo 以“bar”这个名字存入 HDFS 中。在默认配置下，HDFS 会将数据文件存放在 /tmp 下，实际使用中应该将这个配置到合适的地方。</p>
<p>不过我总是在 <code>-put</code> 的时候出错，得到“could only be replicated to 0 nodes, instead of 1”的错误，按照<a href="http://wiki.apache.org/hadoop/HowToSetupYourDevelopmentEnvironment">这里</a>的办法，重新格式化文件系统，之后要先访问一下 DFS 的 web 页面查看一下健康状况以及统计信息，便能正常地使用了。 -.-bb</p>
<p>之后运行 Hadoop 提供的 grep 的例子，一切正常。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/happy.png' alt=':)' class='wp-smiley' /> 今天 MSTC 去爬山，玩得很开心，不过也还挺累的，没有精力再写下去了。至于如何在 Hadoop 的基础上做自己的 MapReduce 应用，就留到下次再说吧！ <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/biggrin.png' alt=':D' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=461</wfw:commentRss>
		</item>
		<item>
		<title>public interface RequestProcessorFactoryFactory</title>
		<link>http://lifegoo.pluskid.org/?p=459</link>
		<comments>http://lifegoo.pluskid.org/?p=459#comments</comments>
		<pubDate>Fri, 19 Sep 2008 17:10:59 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=459</guid>
		<description><![CDATA[曾经在 reddit 上看到这个 Apache 的 FactoryFactory ，觉得很好笑，只是想，大概这样的名字就是严格按照某些设计模式做出来的吧。不过笑过之后也并没有去细想。
最近自己开始用起 Java 来，因为以前学过这个语言，所以很容易就上手了，之后差不多很多东西都很自然，直到有一天我发现自己不小心做了一个叫做 FeatureCollectionFactory 的接口出来，才觉得似乎是该好好想一想了。
有一些东西，自己以前也时常听到或者看到，但是觉得太“企业级”了或者太“Java”了，并没有去关注。比如 Factory 这个东西，为什么会需要 Factory 呢？我觉得，主要就是因为 new 语句太“死板”了，比如：

Fruit fruit = new Fruit&#40;&#41;;

这里后面可以写 new Apple() 、new Pear() 或者是 new Grape() 什么的，但是无论是写任意一个，都必须要作为一个语句写死了，在编译的时候就完全确定了，但是有时候希望具体创建什么对象要等到运行时才能确定。于是就有了 Factory 。
除了这个，Factory 还可以做其他用处，比如，控制对象的创建等。然而，为何在 Ruby 或者 Python 这样的动态语言里面没有看到满天飞的 Factory 呢？原因也很简单：Factory 是为了在 Java 这样的相对静态的语言里将一些东西推迟到运行期，实现更加动态一点的特性，而这些在动态语言里是非常自然的。例如下面的 Ruby 代码：

fruit = fruit_class.new&#40;&#41;

其中我可以在运行时将变量 fruit_class 赋值为 Apple 或者 Pear 或者（得益于 Duck Typing）其他任何合适的东西。“类”对象本身就可以当作一个 Factory 来使用了。在 [...]]]></description>
			<content:encoded><![CDATA[<p><img src='/wp-content/uploads/2008/09/coffee_beans.png' alt='beans' style="float:right;" />曾经在 <a href="http://www.reddit.com/r/programming/comments/2b5zy/overengineering_in_action_apaches_xmlrpc_server/">reddit</a> 上看到这个 <a href="http://ws.apache.org/xmlrpc/apidocs/org/apache/xmlrpc/server/RequestProcessorFactoryFactory.html">Apache 的 FactoryFactory</a> ，觉得很好笑，只是想，大概这样的名字就是严格按照某些设计模式做出来的吧。不过笑过之后也并没有去细想。</p>
<p>最近自己开始用起 Java 来，因为以前学过这个语言，所以很容易就上手了，之后差不多很多东西都很自然，直到有一天我发现自己不小心做了一个叫做 <code>FeatureCollectionFactory</code> 的接口出来，才觉得似乎是该好好想一想了。</p>
<p>有一些东西，自己以前也时常听到或者看到，但是觉得太“企业级”了或者太“Java”了，并没有去关注。比如 Factory 这个东西，为什么会需要 Factory 呢？我觉得，主要就是因为 <code>new</code> 语句太“死板”了，比如：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;">Fruit fruit <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Fruit<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>这里后面可以写 <code>new Apple()</code> 、<code>new Pear()</code> 或者是 <code>new Grape()</code> 什么的，但是无论是写任意一个，都必须要作为一个语句写死了，在编译的时候就完全确定了，但是有时候希望具体创建什么对象要等到运行时才能确定。于是就有了 Factory 。</p>
<p><span id="more-459"></span>除了这个，Factory 还可以做其他用处，比如，控制对象的创建等。然而，为何在 Ruby 或者 Python 这样的动态语言里面没有看到满天飞的 Factory 呢？原因也很简单：Factory 是为了在 Java 这样的相对静态的语言里将一些东西推迟到运行期，实现更加动态一点的特性，而这些在动态语言里是非常自然的。例如下面的 Ruby 代码：</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">fruit = fruit_class.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#006600; font-weight:bold;">&#41;</span></pre></div></div>

<p>其中我可以在运行时将变量 <code>fruit_class</code> 赋值为 <code>Apple</code> 或者 <code>Pear</code> 或者（得益于 Duck Typing）其他任何合适的东西。“类”对象本身就可以当作一个 <i>Factory</i> 来使用了。在 Java 中虽然也有反射和 Class 对象，但是那些东西用起来实在是太笨重了，于是便有了一堆一堆的 Factory 。</p>
<p>再来说说最近在 Java 世界里了解到的一个叫做 Dependency Injection 的东西，很神秘的一个名字，一开始我还以为是如 <a href="http://blog.headius.com/2007/01/invokedynamic-actually-useful.html">InvokeDynamic</a> 之类的东西呢，查阅了一番之后才发现其实也是一个简单的概念。</p>
<p>我的理解大致就是这样：原本我们写程序都希望能尽量自动化，比如，我要 new 一个 <code>Computer</code> 出来，<code>Computer</code> 会自动去查找电源，并在需要的时候 new 一个电源，电源可能又会初始化大楼的总闸、发电厂……如果有幸中间某个步骤是已经初始化好的，直接用就可以了，否则大概要追溯到宇宙大爆炸。这是一种按需取用的方式，一切都在后台自动搞定。</p>
<p>然而 Dependency Injection 则把这些东西反过来，比如，电源不能让 <code>Computer</code> 自己去查找，而是要你先把电源初始化好之后再手工传递给 <code>Computer</code> ，当然，在这之前你可以需要先手工打开大楼的总闸，或者之前要先建立一个发电厂……或者你得从宇宙大爆炸开始做起。</p>
<p>看起来好像根本就是在自找麻烦啊，对比这两种方法，我就想起了小学时解应用题的两种方法：一种是设定一个未知数，把它当作已知的东西，列出一个等式来，问题自然就解决了；另一种是直接倒推，得出一个算式就可以算出来了。后者需要很高的技巧，而且很容易算错，看起来这里的 Dependency Injection 就是在走这样的一条路，为何要把运行得好好的高度自动化的装配线拆散了手工组装呢？</p>
<p>问题就出在“运行得好好的”上面。很多时候程序实际上并不能那么完美地运行，一般通过事前的 Unit Testing 和事后的 Debugging 。被 Debugging 折磨了这么多年的程序员们如今都渐渐开始喜欢 Unit Testing 了，然而整个流程太自动化了，牵一发而动全身：我只是要 new 一个 <code>Computer</code> 而已，却有可能会触发宇宙大爆炸，而且一切都在后台进行，根本没有插手的余地，完全没有可测试性。</p>
<p>有了 Dependency Injection ，就变得容易多了，诸如 <code>Computer</code> 或者“发电厂”之类的 Java 对象是几乎完全独立的存在，需要手工把他们装配在一起。但是手工装配太痛苦了，便通过 XML 配置文件来完成（于是 Java 世界里又遍地都是 XML 和“配置文件”了），在不同的场合把不同的对象组装在一起（比如，在单元测试的时候，可以使用一个 Mocking 的发电厂）便成了一个 Java 程序。这样让我颇有些不习惯：配置文件和源代码之间的关系变得非常紧密了，因为程序的很大一部分逻辑现在跑到了配置文件里面去，仅仅靠源代码根本无法构成一个完整的程序了。</p>
<p>看起来 Dependency Injection 在动态语言里也是可以推行的，只是相对于成堆的 XML 来说，大概直接用脚本或者加上一些 DSL 的元素更受欢迎一些。 <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/happy.png' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=459</wfw:commentRss>
		</item>
		<item>
		<title>很鹾的“电话面试”</title>
		<link>http://lifegoo.pluskid.org/?p=458</link>
		<comments>http://lifegoo.pluskid.org/?p=458#comments</comments>
		<pubDate>Fri, 19 Sep 2008 14:49:18 +0000</pubDate>
		<dc:creator>pluskid</dc:creator>
		
		<category><![CDATA[Life]]></category>

		<guid isPermaLink="false">http://pluskid.lifegoo.com/?p=458</guid>
		<description><![CDATA[因为朋友的推荐，阴差阳错地联系上了 Microsoft Ireland ，之前有两道题目，大约算一种海选的机制吧，很简单的，昨晚之后提交回去就可以了。后来说要约电话面试，最近也比较忙，约了两次在定在今晚十点钟，也就是爱尔兰时间的下午三点。
其实我是好好准备的，因为寝室的电话我都怀疑能不能用，还是只能用手机，准备了耳机线和话筒，纸、笔之类的，常见的我觉得会问到的问题都大致想了一下。可是有一个地方我确实是三番五次地忽略掉了——时间。其实最近几天 MSTC 在纳新，昨天我还在面试别人，一般如果遇到觉得合适的但是预感到有可能会很忙的那种人，我们都会问诸如“你能投入多少时间到社团里来”之类的问题。
所以就这样了，电话响了，信号不是特别好，不过基本上能听清楚，说了几句大致便开始进入正题了，唉，一来我的口语还是不行，二来我还是很紧张，说话就不太清楚了，不过我相信外国人肯定能听懂的 ^_^bb 。但是在问 technical 的问题之前，她先询问了一些 HR 相关的问题，比如我有没有在其他 Microsoft 子公司或者部门之类的面试过或者工作过之类的，其中一个就是“When will you be available for Microsoft”，我一听就寒了……我告诉她之前我还在犹豫，不过现在我决定要读研究生了，所以要工作的话，最早也要在两年半以后（其实应该再加上大四的一年）了。估计她听到之后也有些无语，然后说这样的话，今天先到这里了，后来说的一堆就没听太清楚了，大概说会发一些相关的材料给我，让我以后感兴趣的时候继续联系吧。
所以面试过程整个还不到十分钟啊，让我想起了刚进大学时那次丘成桐数学班的面试，那是我第一次参加面试了，完全不知道要准备什么，进去之后，看到一群面试官，就很紧张，让我做自我介绍的时候才知道原来该把这个准备一下的。结果也是很迅速就面完了，最后当然是没有进，不过幸好没有进啊，灭哈哈～  
如果可以把“不是我主动去联系的”当作这个面试变成这样的借口的话，那么下午微软亚洲工程院打过来的那个电话我就无话可说了：那边的人问我是不是投了 Intern ，我说是。然后是同样的问题：“你什么时候有时间？”我想了一下，告诉他说最近都没有时间…… -.-bb
如果再继续犹豫不决的话，我好像就是在把机会一个一个地浪费掉啊。真是彻底败给我自己了。今天去听了保研的信息发布会，再三检查了申请表格上没有勾上“直博”，才交了上去。不过几天后的保研面试应该不会这么惨吧？ ^_^bb 真是败给我自己了……
]]></description>
			<content:encoded><![CDATA[<p>因为朋友的推荐，阴差阳错地联系上了 Microsoft Ireland ，之前有两道题目，大约算一种海选的机制吧，很简单的，昨晚之后提交回去就可以了。后来说要约电话面试，最近也比较忙，约了两次在定在今晚十点钟，也就是爱尔兰时间的下午三点。</p>
<p>其实我是好好准备的，因为寝室的电话我都怀疑能不能用，还是只能用手机，准备了耳机线和话筒，纸、笔之类的，常见的我觉得会问到的问题都大致想了一下。可是有一个地方我确实是三番五次地忽略掉了——时间。其实最近几天 MSTC 在纳新，昨天我还在面试别人，一般如果遇到觉得合适的但是预感到有可能会很忙的那种人，我们都会问诸如“你能投入多少时间到社团里来”之类的问题。</p>
<p><span id="more-458"></span>所以就这样了，电话响了，信号不是特别好，不过基本上能听清楚，说了几句大致便开始进入正题了，唉，一来我的口语还是不行，二来我还是很紧张，说话就不太清楚了，不过我相信外国人肯定能听懂的 ^_^bb 。但是在问 technical 的问题之前，她先询问了一些 HR 相关的问题，比如我有没有在其他 Microsoft 子公司或者部门之类的面试过或者工作过之类的，其中一个就是“When will you be available for Microsoft”，我一听就寒了……我告诉她之前我还在犹豫，不过现在我决定要读研究生了，所以要工作的话，最早也要在两年半以后（其实应该再加上大四的一年）了。估计她听到之后也有些无语，然后说这样的话，今天先到这里了，后来说的一堆就没听太清楚了，大概说会发一些相关的材料给我，让我以后感兴趣的时候继续联系吧。</p>
<p>所以面试过程整个还不到十分钟啊，让我想起了刚进大学时那次丘成桐数学班的面试，那是我第一次参加面试了，完全不知道要准备什么，进去之后，看到一群面试官，就很紧张，让我做自我介绍的时候才知道原来该把这个准备一下的。结果也是很迅速就面完了，最后当然是没有进，不过幸好没有进啊，灭哈哈～ <img src='http://lifegoo.pluskid.org/wp-content/plugins/smilies-themer/adiumicons/biggrin.png' alt=':D' class='wp-smiley' /> </p>
<p>如果可以把“不是我主动去联系的”当作这个面试变成这样的借口的话，那么下午微软亚洲工程院打过来的那个电话我就无话可说了：那边的人问我是不是投了 Intern ，我说是。然后是同样的问题：“你什么时候有时间？”我想了一下，告诉他说最近都没有时间…… -.-bb</p>
<p>如果再继续犹豫不决的话，我好像就是在把机会一个一个地浪费掉啊。真是彻底败给我自己了。今天去听了保研的信息发布会，再三检查了申请表格上没有勾上“直博”，才交了上去。不过几天后的保研面试应该不会这么惨吧？ ^_^bb 真是败给我自己了……</p>
]]></content:encoded>
			<wfw:commentRss>http://lifegoo.pluskid.org/?feed=rss2&amp;p=458</wfw:commentRss>
		</item>
	</channel>
</rss><!-- Dynamic page generated in 2.197 seconds. --><!-- Cached page generated by WP-Super-Cache on 2009-07-15 23:43:09 -->
