2008年12月19日星期五

GreaseMonkey脚本之“校内共同的好友”

之前看到吴天际同学写了一个"校内共同的好友"的GreaseMonkey脚本,可惜后来接口变了,于是我今天写了一个新的。
http://userscripts.org/scripts/show/38905
目前这个版本还很粗糙――
可以改进的地方有:
1、通信:不用page+1来抓取下一页好友列表,而是通过页面元素计算最后一页的页码,这要可以少抓一页
2、计算:两个集合求交集,预先对一个集合建索引,则复杂度O(n)
3、缓存:可以将部分已经计算过的内容压缩后放到cookie中,减少服务器计算开销(但是会增加通信开销因为cookie总是在HTTP Request Header中带着跑)
4、界面:显示共同好友的小图标

2008年12月11日星期四

从有序数组中找两个数求和等于某个给定的数

问题:
给一个正整数c,一个升序的正整数数组a=a1,...,an,找ai, aj使得ai+aj=c
算法:
i=1
j=n
while i<=j
{
case ai+aj==c, return true
case ai+aj<c, i++
case ai+aj>c, j--
}
return false
思路:
造一个方阵bij=ai+aj,则b对称,且bij<c蕴含bij左上区域都<c,只能向右或向下找,而我们从右上角开始,因此只能向下找。j++后,指针又在可能区域的右上角了。
正确性证明:
首先,算法显然至多n步后返回。
如果算法返回true,则显然ai+aj=c成立。
如果算法返回false,来证不存在bij满足bij=c。
由对称性,只需要证明bij!=c对于任意i<=j成立即可。
归纳法,显然当n=1时成立,若n-1时成立,来看n>=2时。
因为算法返回false,且n>=2,因此至少循环第一次b1n!=c,那么有两种情况
b1n<c,则b1j<c对任意j=1,...,n成立,n阶上三角去掉了第一行,剩下的是n-1阶的上三角,指针移到(2,n),是这个n-1阶上三角的右上角。
b1n>c,则bin>c对任意i=1,...,n成立,n阶上三角去掉了最后一列,剩下的还是n-1阶的上三角,指针移到(1,n-1),还是这个n-1阶上三角的右上角。
故由归纳假设,n时成立。

2008年12月1日星期一

topK

设U是一个全序集,A是U上的一个数组,长度为n。topK算法把最大的K个元素放在数组A的最前面。
思路类似快速排序,每次划分后只递归topK一边,而不是两边:
划分:在A中随便取一个pivot放中间,把大于等于pivot的数放左边,小于pivot的数放右边。设划分后pivot的下标为p。
如果p=K,太好了,左边的K个数刚好就是最大的。
如果p<K,我们还需要更多的比较大的数,从右侧的数中跑topK-p。
如果p>K,说明我们一下找多了,需要精益求精,在左侧的数中跑topK。
用C++写的,注意其中需要T重载一些比较运算符:
template<typename T>
void top(T *a, size_t n, size_t k) {
    if (n <= 0 || k <= 0 || n <= k) {
        printf("top need n>k! n=%d k=%d\n", n, k);
        throw "";
    }
    while (true) {
        int j = rand() % n;
        T pv = a[j];
        swap(a, 0, j);
        size_t p = 1;
        for (size_t i = 1; i < n; i++) {
            if (a[i] >= pv) {
                swap(a, i, p);
                p++;
            }
        }
        j = p - k;
        if (j < 0) {
            a = a + p;
            n = n - p;
            k = k - p;
        } else if (j > 0) {
            while (a[--p] == pv && p>k)
                ;
            swap(a, 0, p);
            if (p == k)
                break;
            else
                n = p;
        } else
            break;
    }
}
边界条件的处理略微有些复杂,我搞了好久才搞好。为了保证循环有限步完成,必须要让问题的规模确实减小。
为了加速处理所有数都相同的输入,p>K时,在不破坏正确性的、且不增加复杂度的情况下p尽量取小些。

2008年11月22日星期六

HTML中的UI何时更新

今天对比了一下
input type="text"的value

div的innerHTML
两个不同元素的字段,在赋值的时候更新UI的耗时。

测试用例如下
输入 element(div 或 input)
    var t1 = (new Date()).getTime();
    for(var i=0; i<1000; i++)
        element.innerHTML或value  = "" + i;
    var t2 = (new Date()).getTime();
输出 t2 - t1;

结果:
在FF(3.0.3)中
div 153
input 2495
在IE中
div 562
input 31
(毫秒)

值得注意的是,FF中,你可以看到input的更新过程,就是说,每次给input的value赋值都会时时更新到UI。而其他都是等线程从函数返回才更新,因此虽然赋值了很多次,你却只能看到UI更新一次。

初等的Bug(4)

所有人都知道二分查找正确性的前提是数组已经被排序,但是编程的时候,有时候就会忘记!
使用二分查找之前,一定要记得给数组排序!

2008年11月12日星期三

FreeWheel宣讲会笔记

今晚去FIT楼听了一场FreeWheel的宣讲会,主持人是做技术的,很不会活跃现场气氛,可是场面依然很火爆,Q&A时,北大清华的学子们轮番冲上讲台去抢话筒问问题,我站在后面狂汗不止。
在我看来,FreeWheel就是一个广告代理,专做互联网视频这块。以前,网友把电视节目上传到视频网站,大家都去看视频网站,没人看电视了,电视广告卖不出去,电视网不挣钱,没钱做不出好节目,没好节目视频网站没人看,恶性循环。电视网和视频网站谈判,电视网说我把电视节目版权一次性卖给你,视频网站说不行太贵风险太大,谈不拢。FreeWheel说这样好了,我在视频网站做精准的广告投放,广告商发现通过我投放广告回报高,就愿意给我钱,我把这笔钱分给电视网和视频网站,从而使得双方共同为电视节目的质量承担风险,双方谈拢了,在互联网上就能看到有版权的视频节目了,网站有内容就有用户,由用户就有人点广告,有人点广告广告商就给钱,有了钱视频网站和电视网就能发展,电视网发展得好就能做出更好的内容,良性循环。
我以为,互联网,已经成为继有线电缆、地面无线电、卫星之后,第四种广播电视的媒介了。FreeWheel跑通了一个在这个媒体上的商业模式,得到了电视网和视频网站的认可,更主要的是赶在经济危机前完成了融资,现在想跟风模仿FreeWheel的人发现找不到风投了。FreeWheel没有理由不牛X,宣讲会上听懂了这一点的同学,自然兴奋不已争着冲上去抢话筒。

FreeWheel的情况大约是这样的,全球共70人,美国东西海岸各有一个Office主要是Sales和DataCenter;北京朝外一个Office,有40人,研发中心。坐镇朝外的是Diane Yu,CTO,1/3 Co-founder of FreeWheel,北大毕业的,曾在DoubleClick十多年。
FreeWheel今年1月上线,现在已经有CBS、WB、Joost、viddler等知名客户,产业链上下游一传十十传百,一直没敢做Marketing可Customer还是纷至沓来。
FreeWheel的技术问题主要也还是Scalability的问题,海量数据处理,毕竟用户增长太快。
团队很精英,有优酷、MSRA、IBM过来的人。团队很年轻,只有一个刚刚抱上孩子。

我在想,FreeWheel研发在中国,市场在美国,未来发展如何?FreeWheel盈利模式以后能否迁移到中国?中国的广播电视行业和美国很不一样,其差异,恐怕比两国间电信行业的差异还要大。中国的广播电视行业挂靠在新闻宣传口下,有很多斯大林时期的痕迹。我更愿意相信:在不久的将来,中国广电行业有一场自上而下的变革,而不是像美国那样通过市场的自动配置,成就互联网广播电视这个新媒体。因此,FreeWheel在中国只能是一块出色的殖民地,除非公关非常牛,否则广电不会分它一杯羹。

Netflix Data的一些统计特性

电影总数17770,用户总数480189
在所有的约10000000次评分中,1分约占4.6%,2分9.5%,3分28.8%,4分33.8%,5分23.2%
115136个用户从来不打1分,占24%――近四分之一的用户从来不打1分
仅7部电影从来没有得到1分

评价过的电影数超过1777的用户有1956个――0.4%的用户评价过的电影数超过(电影总数的)10%
评价过的电影数超过540的用户有48033个――10%的用户评过的电影数超过3%

注意到0.4%和3%差1个数量级,而电影总数与用户总数也刚好差1个数量级。应该能用某个随机配对的模型解释这个现象。

2008年11月8日星期六

初等的Bug(3)

文件中是int, char[4]的结构数组,想把int取出来
fread(&i, 4, 1, f);
fseek(f, 4, SEEK_CUR);
直到feof(f),却发现程序永久性地被挂起了!
查手册才明白:
Using fseek() clears the EOF flag associated with that stream.
终止条件永远不会满足
正确的做法是fread之后立即判断feof

2008年11月7日星期五

初等的Bug(2)

在C中打开文件句柄写入文件
FILE *fopen
这个函数的第二个参数可以为w或wb。
用w打开文件,但是写入二进制数据会有什么后果?
今天遇到一个这个问题导致的Bug,困惑了我很久,后来在十六进制编辑器的帮助下,发现再写入二进制流的过程中每隔一段会多写入一个0D,也就是换行符,才明白。
给我的启示是:一定要搞清楚C函数库参数的含义,不要再犯这么初等的错误!

2008年9月30日星期二

深邃的初等Bug

这是一个初等的Bug,我却De了很久才De出来。暴露了我这个知识点没有很好地掌握:int和byte的类型转换。
我在重写inputstream的int
read()抽象方法。按照定义,它返回[-1,255]的整数,如果流到末端了,返回-1,否则返回流的下一个byte,向上转换成int,取值范围[0,255]。
有一个方法byte f(byte),我希望用它转换所有读到的字节,于是我这样写了:
int x=in.read();
if(x==-1)return -1;
else return f((byte)x);
结果,这个read方法给我找了很多麻烦,后来才发现,它返回值经常是比-1还小的负数,导致上层readUnsignedShort调用它的时候抛出EOF,因为它是这样判断的:
int ch1 = in.read();
int ch2 = in.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (ch1 << 8) + (ch2 << 0);
如果in按照inputstream定义实现,这没问题,因为在这个假设前提下(ch1 | ch2) <
0当且仅当ch1和ch2至少有一个是-1,当且仅当in到了流末端。

原来,f返回byte,但所在的read方法却要求返回int,所以它自动完成了类型转换,这里非常深邃了,在java中,byte类型值作为整数取值范围在[-128,127],所以自动类型转换成int后就出比-1还小的负数了!要把byte转换成[0,255]之间的整数,(不能做线性变换!),应该将前3个高字节位补成0。
学计算机的人不难想出解决办法:做&0xFF就行了。我起初对此很困惑,写java程序很少用位运算的。。。不过现在能理解了^_^
改成
int x=in.read();
if(x==-1)return -1;
else return f((byte)x) &0xFF;
就都好了:)

P.S.
把一个int强制转换成byte时肯定不是单射,事实上,它并非直接取最低字节,而是带符号位的转换。

2008年8月8日星期五

gtktunet

tunet二校门在Linux下只有命令行程序,我给包装成了gtk托盘图标的了。
StatusIcon很漂亮

#!/usr/bin/env python
import popen2, gtk

class MyTunetGTK():
    def __init__(self):
        self.tr = gtk.StatusIcon()
        self.tr.connect('popup-menu', self.on_popup_menu)
        self.tr.set_from_file('gif/tunet.gif')
        self.tr.set_visible(True)
        self.ui = gtk.UIManager()
        menu = """
        <ui><popup name="Popup">
                <menuitem action="Disconnect"/>
                <menuitem action="Limit"/>
                <menuitem action="Free"/>
                <separator/>
                <menuitem action="About"/>
                <menuitem action="Quit"/>
        </popup></ui>
        """
        ag = gtk.ActionGroup('Actions')
        ag.add_actions([
                ('About', gtk.STOCK_ABOUT, 'About', None, None, self.on_about),
                ('Quit', gtk.STOCK_QUIT, 'Quit', None, None, self.on_quit)
        ])
        ag.add_radio_actions([
            ('Disconnect', None, 'Disconnect', None, None, 0),
                ('Limit', None, 'Limit', None, None, 1),
                ('Free', None, 'Free', None, None, 2),
        ], 0, self.on_conn_changed)
        self.ui.insert_action_group(ag, 0)
        self.ui.add_ui_from_string(menu)
        self.menu = self.ui.get_widget('/Popup')

    def start(self):
        gtk.main()

    def on_popup_menu(self, status, button, time):
        self.menu.popup(None, None, None, button, time)
   
    def on_conn_changed(self, action, current):
        self.tr.set_from_file('gif/tunet.gif')
        self.local()
        self.status = action.get_current_value()
        if self.status == 1:
            self.conn_limit()
        elif self.status == 2:
            self.conn_free()

    def local(self):
        for i in range(3):
            p = popen2.Popen3("ps -C tunet o pid= | grep -m 1 . | xargs kill", True)
            s = p.fromchild.read()

    def conn_limit(self):
        f = popen2.Popen3("./inner", True).fromchild
        while True:
            line = f.readline()
            if line:
                if "DoConnect(503): ok!" in line:
                    self.tr.set_from_file('gif/green.gif')
                    break

    def conn_free(self):
        p = popen2.Popen3("./outter", True)
        f = p.fromchild
        while True:
            line = f.readline()
            if line:
                if "DoConnect(503): ok!" in line:
                    self.tr.set_from_file('gif/blue.gif')
                    break

    def on_about(self, data):
        dialog = gtk.AboutDialog()
        dialog.set_name('pygtk wrapper for tunet')
        dialog.set_version('0.1')
        dialog.set_authors(['wangyuantao@msn.com'])
        dialog.run()
        dialog.destroy()
           
    def on_quit(self, data):
        self.local()
        gtk.main_quit()

if __name__ == "__main__":
    m = MyTunetGTK()
    m.start()

2008年8月1日星期五

乐道随机播放的GM脚本

乐道
http://www.likenote.com/
是一个音乐网站,功能上没有什么特别的,甚至很弱,但是我喜欢上面的内容,是我喜欢的音乐风格。
为了能够在这个网站上不间断地听随机收听音乐,我写了一个简单的GreaseMonkey脚本。
目前的问题是无法获得一首曲子的结束时间,因此总是5分钟后强制切换到下一首。

// ==UserScript==
// @name           likenote random walk
// @include        http://www.likenote.com/listener/article/*
// ==/UserScript==

(function(){
//setTimeout("document.documentElement.innerHTML=''",1000);

$ = function(id){
    return document.getElementById(id);
}


duration=5*60*1000;
setTimeout(function(){
    var player = $("SinglePlayer");
//    var tab = $("content").getElementsByClassName("right_bg")[0].getElementsByTagName("table")[1];
//    for(var i=0;i<tab.rows.length;i++){
//        list[i] = tab.rows[i].cells[1].firstChild.href;
//    }
    str = document.documentElement.innerHTML;


    list = str.match(/listener\/article\/\d+/mg);
    rnd = Math.floor(Math.random()*list.length);
    document.documentElement.innerHTML='';
    document.documentElement.appendChild(player);
//    document.documentElement.appendChild(tab);
    document.documentElement.innerHTML += "all:";
    for(i in list){
        href = list[i].split("\/")[2];
        document.documentElement.innerHTML += "<a href='"+href+"'>"+href+"</a>|"
    }
    document.documentElement.innerHTML += "<p>next:"+list[rnd];
},1);

setTimeout(function(){
try{
        location.href = "http://www.likenote.com/"+list[rnd];
}catch(e){alert(e)}
},duration);
//str.match(/listener\/article\/\d+/mg)

})();

2008年7月27日星期日

扎绵羊机器人

玩扎绵羊走火入魔,一怒之下写了个扎绵羊机器人。

原理超级简单,就是让计算机自动判断绵羊的出现并在按钮上方点击鼠标。
很粗糙的一个Java程序,根据不同显示器分辨率和浏览器需要手动调整代码。
我的是1024*768,Firefox3默认布局,启动前滚动条滚动到扎绵羊开始页面顶部。

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;

public class Cap {
    static final int SIZE = 10;
    Robot robot;
    Rectangle rect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
    Color[] detector = new Color[SIZE];;
    Point[] ps = new Point[SIZE];

    public Cap() throws AWTException {
        robot = new Robot();
        for (int i = 0; i < 10; i++) {
            ps[i] = new Point(255, 280 + i * 44);
        //    ps[10 + i] = new Point(355, 285 + i * 44);
        //    ps[20 + i] = new Point(410, 290 + i * 44);
        }
    }

    BufferedImage capture() {
        return robot.createScreenCapture(rect);
    }

    void shoot() {
        robot.mouseMove(550, 660);
        robot.mousePress(InputEvent.BUTTON1_MASK);
        robot.mouseRelease(InputEvent.BUTTON1_MASK);
    }
    void update(){
        for (int i=0; i < SIZE; i++){
            detector[i] = robot.getPixelColor(ps[i].x, ps[i].y);
        }
    }
    void showDetector() {// debug
        robot.delay(500);
        for (Point p : ps) {
            robot.mouseMove(p.x, p.y);
            robot.delay(200);
        }
    }
    static boolean closeTo(Color c1, Color c2){
        return c1.equals(c2);

    }
    public static void main(String args[]) throws AWTException, IOException {
        Cap c = new Cap();
        if (args.length > 0) {
            c.showDetector();
            return;
        }
        // wait to switch to firefox window
        c.robot.delay(1000);
        // press to start
        c.shoot();
        // wait the fog disappear
        c.robot.delay(500);
        // make detector
        c.update();
        // When different from detector, shoot
        int bullet = 5;
        while (bullet > 0) {
            c.robot.delay(20);
        //    long t = System.currentTimeMillis();
            for (int i = 0; i < SIZE; i++) {
                int x = c.ps[i].x;
                int y = c.ps[i].y;
                Color colorNow = c.robot.getPixelColor(x, y);
                if (!closeTo(colorNow, c.detector[i])) {
                    c.shoot();
                    bullet--;
                    System.out.println(i);
                //    c.detector[i] = colorNow;
                    c.robot.delay(500);
                    c.update();
                    break;
                }
            }
        //    System.out.println(System.currentTimeMillis() - t);
        }
    }

}
在 命令行启动程序后,赶快Alt+Tab切换到Firefox界面上来,然后你就看着绵羊一只只死在屏幕最左边吧XD(我现在作弊后的成绩是0.02秒多一 点,已经接近Flash Player的计时函数的误差了(在我机器上setTimeout的误差约0.01秒(测量误差不计)))
P.S.用了这么久Java,才知道有java.awt.Robot这么一个类,JDK里真是充满了宝藏啊~

2008年7月23日星期三

A simple picasa synchronizer in console

I wrote a simple utility to synchronize local picture directory with picasa web album. I'm pleased to share it :)
Python code, 41 lines.

This is the wiki page for it. Welcome to download and enjoy it :)

Install

Install python gdata client

Download http://gdata-python-client.googlecode.com/files/gdata.py-1.1.1.zip and install it according to the http://code.google.com/apis/gdata/articles/python_client_lib.html

Download this utility

Download http://picasup.googlecode.com/svn/trunk/picUp/src/picup/picu.py , and copy it to wherever you like, such as /home/todwong/sandbox/python/picupy/picu.py

Register it into PATH

Create a shell script in $PATH, such as /home/todwong/bin/picupy, with the content:

python /home/todwong/sandbox/python/picupy/picu.py

and make this scrtip executable:

chmod +x /home/todwong/bin/picupy

Use

Go to your directory with your jpg files, such as

cd /home/todwong/pic/hangzhou/

execute this shell directly

picupy

which refers /home/todwong/bin/picupy

Input you picasa web account username and password.

If you have already got an album named hangzhou online, continue to use that album, else create a new album named hangzhou and use it.

Upload those JPG files in your local directory /home/todwong/pic/hangzhou/ and not in your online album hangzhou yet.

To some extent, this utility is a one-way synchronizer, synchronizing from local directory to online picasa web album, with the current directory name to be the album title, and with the name of each file in current directory to be the title of each photo in the album. It is quite easy to use, because except for the account info, nothing else you have to input. Synchronizing processes automatically.


Thanks to
http://code.google.com/apis/picasaweb

Welcome to subscribe with my blog
http://todwang.blogspot.com

2008年7月12日星期六

Ubuntu Opera Flash Player Plug-in

locate libflashplayer.so

如果找到,直接把这个so文件复制到

/usr/lib/opera/plugins

即可

否则去Adobe网站下载

http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash

安装,再locate libflashplayer.so

2008年7月5日星期六

解析法计算两圆交点

中学解析几何知道圆的方程,因此计算两圆交点只需要将两个方程联立,解这个二元二次方程组即可。很容易降幂,化成二元一次方程和圆方程,几何意义是计算直线和圆的交点。下面是用Java实现的代码,可以直观验证解的正确性。
package chap9;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;

import javax.swing.JFrame;

public class CrossPoint extends JFrame {
    private static final long serialVersionUID = 1L;

    public CrossPoint() {
        super("Cross Point");
        setSize(400, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
        play();
    }

    private double rnd(double k) {
        return Math.random() * k;
    }

    @Override
    public void paint(Graphics g) {
        g.clearRect(0, 0, getWidth(), getHeight());
        Circle c1 = new Circle(rnd(200), rnd(280), 100 + rnd(100));
        c1.drawOn(g);
        Circle c2 = new Circle(rnd(250), rnd(200), 100 + rnd(200));
        c2.drawOn(g);
        Point[] ps = c1.crossAt(c2);
        if (ps == null)
            System.out.println("not cross");
        else
            System.out.println(Arrays.toString(ps));
    }

    public static void main(String[] args) {
        new CrossPoint();
    }

    private void play() {
        addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                Point p = e.getPoint();
                setTitle(p.toString());
            }
        });
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                repaint();
            }
        });
    }

}

class Circle {
    double x, y, d;

    public Circle(double x, double y, double d) {
        super();
        this.x = x;
        this.y = y;
        this.d = d;
    }

    public void drawOn(Graphics g) {
        int r = (int) d / 2;
        g.setColor(Color.BLACK);
        g.drawOval((int) x - r, (int) y - r, (int) d, (int) d);
    }

    public double distanceTo(Circle c2) {
        return Point.distance(x, y, c2.x, c2.y);
    }

    public Point[] crossAt(Circle c2) {
        double r1 = d / 2;
        double r2 = c2.d / 2;
        double L = distanceTo(c2);
        if (L > r1 + r2)
            return null;
        double dx = x - c2.x;
        double dy = y - c2.y;
        double dr = (r2 * r2 - r1 * r1 - dx * dx - dy * dy) / 2;
        double a = dx * dx + dy * dy;
        double b, c, d, x01, x02, y01, y02;
        if (isZero(dx)) {
            b = -2 * dx * dr;
            c = dr * dr - r1 * r1 * dy * dy;
            d = Math.sqrt(b * b - 4 * a * c);
            x01 = (-b + d) / (2 * a);
            x02 = (-b - d) / (2 * a);
            y01 = (dr - x01 * dx) / dy;
            y02 = (dr - x02 * dx) / dy;
        } else {
            b = -2 * dy * dr;
            c = dr * dr - r1 * r1 * dx * dx;
            d = Math.sqrt(b * b - 4 * a * c);
            y01 = (-b + d) / (2 * a);
            y02 = (-b - d) / (2 * a);
            x01 = (dr - y01 * dy) / dx;
            x02 = (dr - y02 * dy) / dx;
        }
        return new Point[] { new Point((int) (x01 + x), (int) (y01 + y)),
                new Point((int) (x02 + x), (int) (y02 + y)) };
    }

    private boolean isZero(double dx) {
        return Math.abs(dx) < 0.001;
    }
}

2008年6月10日星期二

makefile latex

最近在写论文,用到tex排版,为了调试方便,我写了个makefile,和最简单的C程序的makefile差不多,对比一下:
.c-.tex
.o-.dvi
可执行程序-.pdf

下面就是我的latex makefile代码了
#Usage:
#make
#    make .tex to .pdf neatly
#make show
#    evince .pdf
#make clean
#    rm all but .tex

MAIN = bsong

all : pdf

pdf : ${MAIN}.pdf

${MAIN}.pdf : ${MAIN}.dvi
    dvipdf ${MAIN}.dvi ${MAIN}.pdf
    rm ${MAIN}.dvi
    rm ${MAIN}.log
    rm ${MAIN}.aux

${MAIN}.dvi : ${MAIN}.tex
    pslatex ${MAIN}.tex ${MAIN}.dvi

clean :
    rm *.log
    rm *.aux
    rm *.dvi
    rm *.pdf
show :
    evince ${MAIN}.pdf &

2008年6月3日星期二

Ubuntu下配置中文TeX

主要参考了这个文章
思路是这样的,先安装texlive的一些组件,然后安装cjk的latex支持,最后下载并注册中文字体。
前两步用apt-get解决,最后一步压缩包中有现成的安装脚本
./install
即可。

测试a.tex可以用
pdflatex a.tex
编译,直接得到pdf文件

摘抄如下:
1、
sudo apt-get install texlive texlive-math-extra texlive-latex-base texlive-latex-extra texlive-latex-recommended texlive-pictures texlive-science texlive-bibtex-extra texlive-common latex-beamer
2、
sudo apt-get install latex-cjk-chinese ttf-arphic-* hbf-*
3、
ftp://cle.linux.org.tw/pub2/tex/cjk/fonts/

2008年6月1日星期日

GM_xmlhttpRequest跨域带Cookie

在A上的GM脚本可以向B发送GM_xmlhttpRequest,很神奇的一点是,向B发送请求时如果不指定HTTP HEAD,则竟然可以使用firefox的B网站的cookie。
这使得在浏览器端整合多个封闭社区成为可能。至少Merge NewsFeed一点都不困难。

2008年5月17日星期六

《Perl语言入门》是本好书

出于调试一个C实现的一个简单遗传算法的参数的需要,写了一个简单的perl脚本,后来发现perl很好用,于是索性把另外一个算法的预处理从java程序里拿出来,交给perl来做了。

同寝室的一个哥们在诺基亚实习,他说他们也要用perl来处理日常工作,并向我推荐了《Perl语言入门》。我正在边用边读,发现确实是一本好书。

下面列一些我用到的函数,作为备忘吧。

  3 sub stat{
  4
  5         $sum = 0;
  6         $min = @_[0];
  7         $max = @_[0];
  8         $n = scalar(@_);
  9         $var = 0;
 10         foreach(@_){
 11                 $sum += $_;
 12
 13                 if($_ < $min){
 14                         $min = $_;
 15                 }
 16                 elsif($_ > $max){
 17                         $max = $_;
 18                 }
 19         }
 20         $avg = $sum / $n;
 21         foreach(@_){
 22                 $var += abs($_ - $avg);
 23         }
 24         return ($min, $avg, $max, $var / $n);
 25 }

这个函数用来计算一个非空数组的一些统计量。要算方差的话需要用到乘幂运算符
2**10 == 1024

--
$mutation = 0.1 + (int rand(9))/10;
随机数生成

--
use Time::HiRes qw(usleep ualarm gettimeofday tv_interval);
$toc = gettimeofday();
计时,浮点数做差

--
 -e $G_TWReaderExe or die
确认文件存在

--
 opendir DH, $G_datapath or die "data path not fount:$!";
 foreach $f (readdir DH)
next if $f =~ /^\./;
打开并遍历目录,过滤..路径,别忘了 close DH;

--
open F, "$G_TWReaderExe $G_datapath/$f/page.dat|" or die
打开进程的标准输出作为文件句柄
 while(<F>){
                 chomp;
                 $count_line++;
                 &deal($converter->convert($_));
         }
然后逐行遍历输出流

--
其中用到字符集转换
use Text::Iconv;
 $converter = Text::Iconv->new("gb2312","utf8");


调用自定义函数(perl的古老概念子过程sub)需要加&才好。


2008年4月4日星期五

MinGW让我欣喜若狂

优化计算方法 这门课的作业要求中有一条,要提交Windows下的可执行程序。这对于以前的我来说,很难,因为我只会Java,编译后不生成可执行文件,虽然也会用C#,C#能生成.exe文件,但是没有.NET运行时环境,exe也"不是合法的win32应用程序"。对于现在的我来说,也很难,因为已经很久不用Windows做桌面了(从去年改用Ubuntu了),开发更是依赖Linux环境。传说中的VC++6.0之类的流行IDE,我从来没弄过,想起VC来我就害怕。好在,最近为了研究算法,开始在Linux下的Eclpise中写纯C的代码,C的语法逐渐回忆起来了。
要交作业了,怎么办呢?
我觉得Windows下也应该有GCC这种命令行编译C代码的东西,一搜,果然有,以前用过的Cygwin,印象中过于庞大,要用他的命令行终端,我不喜欢。还搜到了MinGW,久仰大名,装上后发现相当清爽,C:\MinGW\bin\
下有一些.exe,比如gcc.exe等等。手动把它添加到path中。跑了一个hello.c输出hello
world,很干脆,直接给我了一个hello.exe,运行,没问题。
现在要把我在Linux下的Eclipse中写的的C代码迁移过来了,怎么弄?makefile?MinGW没有带。再搜,有GUN make
for windows,拿来,就一个make.exe,利索,我喜欢,丢到C:\MinGW\bin\ 中。运行make
clean,告诉我不知道rm是什么命令。好吧,我要把Unix常用命令都端给DOS,去
http://unxutils.sourceforge.net/
下载一包,解压缩,添加C:\UnxUtils\usr\local\wbin到path。
爽了,亲耐的他妈妈的DOS再也不会为我输入的ls感到困惑了。
稍微调整一下原来程序的配置文件路径、数据文件路径,然后make clean再make all,一切OK,和在Linux下没什么两样!
Oh yeah!

2008年4月3日星期四

gcc不认sqrt?

Eclipse GCC默认的连接选项不包含-lm,因此sqrt等数学函数导致c程序连接失败――
undefined reference to `sqrt'
collect2: ld returned 1 exit status

解决方案是,在Eclipse的工程属性中,设置GCC Linker的Libraries添加m。在build的时候,连接器就会包含-lm选项。
命令行
gcc -lm test.c

2008年3月21日星期五

Ubuntu穿墙

和Windows相比,在Ubuntu下穿墙太简单了。

首先安装众所周知的两个服务:
sudo apt-get install tor privoxy

然后打开9050端口,供firefox使用
在配置文件 /etc/privoxy/config 中,取消注释掉
forward-socks4a / 127.0.0.1:9050 .
一行即可打开
修改完配置文件重启privoxy:
sudo /etc/init.d/privoxy restart

最后安装firefox插件torbutton
需要穿墙的时候点一下即可
click to enable/disable tor

根据tor穿墙原理,用tor是安全的,也就是不用担心含密码的数据包在转发途中被看到明文,因为所有数据包在tor系统中都加密了。也正因为如此,tor比较慢。

2008年3月10日星期一

Bezout Equation

今晚做了一个小练习,用欧几里得算法计算两个正整数的最大公约数。同时计算出Bezout等式的系数。递归描述更紧凑,突出欧几里得算法的核心内容,迭代描述算系数比较方便。
#include<stdio.h>
int gcd(a, b)
{
    if (a>=b)
        if (b>0)
            return gcd(b, a%b);
        else
            return a;
    else
        return gcd(b, a);
}

int gcdp(int x, int y, int* a, int* b) {
    int r=0, k=0, i=1;
    int ai=0, aii=0, bi=0, bii=0;
    int t=0, reverted=0;
    if (y>x) {
        t=x;
        x=y;
        y=t;
        reverted=1;
    }
    while (1) {
        r=x%y;
        k=x/y;
        if (r==0) {
            if (i==1)
                *b=1;
            if (reverted) {
                t=*a;
                *a=*b;
                *b=t;
            }
            return y;
        }
        x=y;
        y=r;
        switch (i) {
        case 1:
            *a=1;
            *b=-k;
            break;
        case 2:
            ai=*a;
            bi=*b;
            *a=-k;
            *b=1-bi*k;
            break;
        default:
            aii=ai;
            bii=bi;
            ai=*a;
            bi=*b;
            *a=aii-k*ai;
            *b=bii-k*bi;
            break;
        }
        i++;
    }
}

void test(x, y)
{
    int a=0, b=0;
    int r=gcdp(x, y, &a, &b);
    printf("%d * %d + %d * %d = %d\n", a, x, b, y, r);
    if (a*x+b*y!= r)
        printf("Error %d = %d\n", a*x+b*y, r);
}
int main() {
    test(12, 34);
    test(1, 2);
    test(4, 6);
    test(32465, 18078);
    test(36465, 18278);
    return 0;
}

没有考虑负数和零的情况。

2008年3月6日星期四

No Generic in C++?

一直以为Java的泛型是从C++的模板学来的,所以C++的模板也应该能够很好的支持最简单的泛型编程。
今天尝试写一个小Demo的时候发现,C++竟然不支持把模板类的声明和实现分到两个文件!
主程序中
Faint::Heart *h=new Faint::Heart();
h->test<int>(-1);
声明Heart.h
#ifndef HEART_H_
#define HEART_H_

namespace Faint {
class Heart {
public:
Heart();
virtual ~Heart();
template<class T> T test(T t);

};

}

2008年3月5日星期三

Hello MPI

并行计算这门课有一个实验平台,用MPI搭建的。课程还没有讲到,我已经迫不及待地想尝试一下了。虽然我既不会C也不会MPI,但是找本书来弄个Hello
World还是可以的――

首先,登录到实验平台的Linux操作系统上。例如
ssh wangyuantao@166.111.XXX.XXX
按照提示输入密码

然后到自己的目录中创建一个hi.c文件,内容如下:

#include "mpi.h"
#include <stdio.h>
void main(argc, argv)
int argc;
char *argv[];
{
int myid, numprocs;
int namelen;
char p_name[MPI_MAX_PROCESSOR_NAME];

MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
MPI_Get_processor_name(p_name,&namelen);
fprintf(stderr, "Hello MPI, Process %d of %d on %s\n", myid,
numprocs, p_name);

MPI_Finalize();
}

用途是打印进程号、组大小和节点名称。这个hello小程序没有进程间交互。

这个平台上有两个计算节点,node1和node2。在运行MPI时需要指定节点配置文件hosts,这个文件只有两行:
node1-ib
node2-ib
由于这个网络用infiniBand连接,所以多了-ib这个后缀。

这时就可以登录到计算节点上去编译了。
ssh node1
然后找到对应目录(这个实验平台中,计算节点的home路径被mount到了166.111.XXX.XX的home路径)
编译:
mpicc hi.c -o hi
得到可执行文件hi

最后启动运行就可以了
mpirun -machinefile hosts -np 6 hi
其中-machinefile指定了计算节点列表,任务被按照默认的方式负载均衡到各个计算节点。
-np指定了开启几个进程,也就是并行的度。

运行结果都在命令行上打印出来了
Hello MPI, Process 0 of 6 on node1-ib.test
Hello MPI, Process 2 of 6 on node1-ib.test
Hello MPI, Process 4 of 6 on node1-ib.test
Hello MPI, Process 1 of 6 on node2-ib.test
Hello MPI, Process 3 of 6 on node2-ib.test
Hello MPI, Process 5 of 6 on node2-ib.test


P.S.
如果在本机上编辑源代码,然后上传到服务器上,可以用scp命令
scp hi.c wangyuantao@166.111.XXX.XXX:2007211XXX/helloworld/
然后按照提示提供密码,文件就传上去了。

如果不相信程序真的是在两个计算节点执行的,可以打印时间戳,一般计算节点的物理时钟很难精确同步的,也没必要,逻辑时钟正确就行了。
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
fprintf(stdout, "Hello MPI, Process %d of %d on %s @ %s\n",
myid, numprocs, p_name, asctime(timeinfo));
输出结果:
Hello MPI, Process 0 of 3 on node1-ib.test @ Wed Mar 5 16:33:02 2008
Hello MPI, Process 1 of 3 on node2-ib.test @ Wed Mar 5 16:34:38 2008
Hello MPI, Process 2 of 3 on node1-ib.test @ Wed Mar 5 16:33:02 2008

发现node2的时钟比node1的时钟快了一分多钟,呵呵~

2008年2月29日星期五

区间的随机划分

描述数学问题的时候,自然语言很容易有二义性。在叙述随机问题的时候,尤其是这样。比较著名的例子是Bertrand paradox。在一个空间上的某种分布,映射到另一个空间上以后就成了另一种分布。

昨天上课走神的时候,我想到了一个区间的随机划分问题。给定[0,1]区间,给定正整数n,现在要把[0,1]区间划分成n段,使得划分具有某种随机的特性。这不是随机划分的准确描述,我的问题恰恰就是,如何准确描述随机划分,以避免发生类似的Bertrand paradox?

根据直觉,我和同学给出了两种候选的随机划分方案,都是构造性的。
方案一:生成n-1个独立的,服从U[0,1]分布的随机变量x_1,...,x_(n-1),令他们的顺序统计量为y_1,...,y_(n-1),补充定义y_0=0,y_n=1,则{[y_i,y_(i+1)],i=0,...,n-1}是一个随机划分。
方案二:生成n个独立的,服从U[0,1]分布的随机变量x_1,...,x_n,记s为他们的和,令y_i=(x_1+...+x_i)/s,i=1,...,n,补充定义y_0=0,则{[y_i,y_(i+1)],i=0,...,n-1}是一个随机划分。

接下来,我们就要回答,这两种方案是否得到相同的随机划分?
要回答这个问题,我们首先要定义什么叫做两个随机划分方案等价。最直观的,可以定义为他们产生的分点的联合密度函数几乎处处相同。我们算了一下,对于n=2的情形,上述两种方案不等价。事实上,n=2时只有一个分点,也就是要比较两个一维随机变量的密度函数。方案一的结果显然是均匀分布,方案二粗略看来也应该是均匀的,很对称呀,但其实不然,老老实实计算x1/(x1+x2)的分布函数,发现结果真的不是均匀分布。

我们发现,虽然两种方案的得到的分点联合密度不同,但是可以算出来小区间长的期望都是1/n,而且每一种方案的小区间长都服从相同的分布,只不过两种方案的分布不同。受到这一点启发,我们给出了随机划分的一个准确定义(类似方案二):
如果非负数x1+...+xn=1,且x1,...,xn服从相同的分布F,则称x1,...,xn是[0,1]的F随机划分。
可以证明上述两个方案都是随机划分,划分的分布函数F也可以求出来
方案一的是多项式函数,方案二的是有理函数。计算过程和结果的具体形式用TeX写更好看些,以后贴图吧。

这样定义两个随机划分等价就水到渠成了:当且进当二者的分布函数相同。

2008年2月28日星期四

海内 记事本 快捷键保存的GreaseMonkey脚本

以前习惯用Gmail草稿当作记事本,缺点是加载那么多脚本比较笨重,本来要记或者要查的事情很简单,打开却要打开半天,打开后搞的浏览器很疲惫。
自从海内记事本上线后,我一直用它替代Gmail的这个功能。唯一不爽的就是Ctrl+S不是保存记事本的快捷键,每次一按,就弹出一个窗口要保存网页的HTM了,很囧。昨天晚上我终于不能忍了,加了一个GreaseMonky脚本
YUI里面没有找到相关的组合键事件的API,索性手动判断了,实现起来也不太复杂,处理好冒泡终止就好了。
P.S.做的过程中发现openjs有现成的shortcut工具挺不错的。

2008年2月24日星期日

PPC+GPS bluetooth+Google Map实践

去爬山很重要的一点是知道自己在什么地方,gps定位不可少。去年年底买了gps蓝牙模块,软件一直没搞好,今天终于搞定了。
实验环境~
PPC:Qtek S200,大陆叫多普达830
GPS bluetooth:Holux m-1000
GPS software:
gpsgate,用来让多个gps客户端程序共享一个gps信号输入设备,相当于网关或交换机
google map,可以看卫星图,很直观,需要能上互联网,因为卫星图片是从Google网站上动态加载的
这两个软件都可以从网上下载到cab安装文件
实验步骤~
1、打开holux开关,等到橙色的灯开始闪烁时,表明已经找到gps卫星了。
2、打开PPC的蓝牙,搜索设备,找到holux。只需添加一次,以后就不用每次添加了。
3、启动gpsgate,在输入的tab里,选择GPS Bluetooth,找到holux,然后选择串行端口服务,确定。这时gpsgate图标变成绿色,表明gpsgate正确收到holux的信号输入。
3、在gpsgate输出tab中,添加Virture Com/nmea,高级选项,删除com3端口(如果有的话),然后添加虚拟端口com3,确定。这时提示com3 running ok。
4、启动Google Map,选项,gps,手动设置为com3/4800,启动gps,显示我的位置。
实验结论~
能够看到自己所在的位置的卫星图,是不是很科幻?不,很现实,而且很爽!
ozi是绿野论坛上的人比较推荐的一款软件,可以自己定制地图,画路标,显示经纬海拔,比较专业。以后慢慢研究~

2008年2月21日星期四

erlex : play erlang with flex

昨天完成了一个范例小程序,可以用来群聊。客户端是浏览器里的Flash,服务器端是erlang写的脚本。
基本框架是服务器监听两个端口,8765替代80用来让浏览器通过http协议装载html和swf文件,2345端口是原生Socket通信,负责聊天数据的推拉。
先来看看http的实现吧,在erlang脚本中只需要一句话:
inets:start(httpd, [{server_name,"erlex"},{bind_address, "59.66.121.94"},{port, 8765},{server_root,"www"}).
一个httpd就跑起来了(不用找Apache),然后把它放到后台去不用管了。这里server_root给的是相对路径,所有html,swf文件都在里面。
主要来看原生Socket的通讯。
1、客户端,熟悉Java的同志一定对ActionScript有一见如故的感觉。全部代码可以去
http://code.google.com/p/erlex/
看,我已经check in了,这里简单点一句
private function send(content:String):void{
    var buf:ByteArray = new ByteArray();
    buf.writeByte(0x02);
    buf.writeUTF(content);
    socket.writeBytes(buf,0,buf.length);
}
开了一段字节数组,先向里面丢了一个0x02,表示这个数据包是客户端说的话,后面是一个字符串,头两个字节表示跟着的字符数组的长度,跟着的字符数组是字符串的UTF8表示。socket在write之后应该flush一下,目前问题不大。
2、服务器端,大多数同志可能对erlang不是很熟悉,我多讲几句吧。
因为要支持多个客户端同时连接,因此用到了erlang的拿手好戏:并发编程。我们为每一个Socket连接准备一个erlang的process(可以理解为Java的线程,但是相当轻量级,官方测试数据证明了erlang这方面的性能),同时准备一个总线process,做传递消息枢纽。
par_connect(Listen)->
    case gen_tcp:accept(Listen) of
    {ok, Socket} ->
        spawn(fun() -> par_connect(Listen) end),
        loop(Socket);
    end.
上面的函数用来捕获一个TCP连接,在loop中处理,并启动一个新process,执行同样的par_connect函数,这是一种并行递归。
loop(Socket)->
    receive
        {tcp, Socket, Bin} ->
            [H1,_,_|T1] = binary_to_list(Bin),
            case H1 of
            2 ->
                bus ! {broadcast, self(), T1},
                loop(Socket);
    end.
这个函数所在的process可能接收到各种消息,其中{tcp, Socket, Bin}是收到一个TCP数据包(由原子tcp确定),Bin中存放的是数据包的TCP正文二进制流。我们先把它转换成字节数组binary_to_list,再模式匹配[H1,_,_|T1],得到H1是第一个字节,表示数据包的用途,2的意思就是上面0x02,然后用,_,_忽略后面两个字节(如果在ActionScript中用writeUTFBytes就没有这个麻烦),用T1装字节数组后面的内容。然后通过
bus ! {broadcast, self(), T1},
发给总线。其中bus在程序开始注册为总线process,self()表示当前process id,可以用来确定说话人的身份。
值得注意的是最后又调用了loop方法似乎是递归,很快要堆栈溢出的,不过erlang编译器把他当作迭代调用,这是erlang风格,erlang有很多很特别的风格。我认为最酷的还是process间通信的风格,发送消息用!加term,收到消息用recieve加模式匹配,和分布式算法的伪代码描述一模一样!

总线查出说话人的名字,并把他说的话发给所有process
[{_,SpeakerName}|_] = ets:lookup(TableID,SpeakerPid),
lists:foreach(fun({Pid,Name}) -> Pid ! {send, SpeakerName, Content} end , ets:tab2list(TableID))
这里用到了list的遍历、闭包以及erlang数据表ets的操作。
loop函数中再加一个recieve子句
{send, SpeakerName, Content}->
    ok = gen_tcp:send(Socket, list_to_binary(SpeakerName++" says: "++Content)),
这就发回给客户端了,ActionScript中这样接收:
socket.addEventListener( ProgressEvent.SOCKET_DATA, onSocketData );
其中
private function onSocketData( event:ProgressEvent ):void {
    var data:String = socket.readUTFBytes(socket.bytesAvailable);
    history.text=history.text+"\n"+data.toString();
}
history是多行文本框。

事实上,利用ets可以避免使用总线,只是考虑多个process间不共享存储的分布式场景,才用的。erlang为分布式计算而生。

2008年1月22日星期二

海内 电影 优酷看 的GreaseMonkey脚本

我这边上优酷网速超快,虽然画质略差,但是比ftp、BT等都方便,所以――
模仿longwosion的海内电影豆瓣评论脚本,我写了一个海内电影优酷看的脚本
这是我写的第一个GreaseMonkey脚本,还请大家多多指教。

用起来很容易,打开一个海内电影页面,看到如图在"上传海报"旁边多出来一个"优酷手气不错"的链接。你点一下,这个链接就消失了。稍等片刻,下边多出来一个列表,是优酷的搜索结果。
Note:
优酷上重名视频很多,所以很不准。
只抓取搜索结果第一页,查看全部的话请点"查看全部"链接。
Todo:
增加"数据获取中"的tip
像豆瓣评论那样sect展开/收起
P.S.
之所以叫"手气不错",是希望用这个脚本的人每次都能找到想要看的视频^_^
P.S.(2)
有一种不祥的预感,冥冥之中感到优酷等要被河蟹了……但愿不要如我所料……

2008年1月13日星期日

Event之于SNS――我看豆瓣同城

前天豆瓣同城上线,类似boubo.com,进一步向SNS倾斜。
我同意去年9月聊天时王兴的一个观点:在SNS中,location的是一个非常重要的人际要素。在车东转帖的一张facebook的UML类图上,我们也能发现,location的地位竟然仅次于profile。和很多社区一样,豆瓣的用户群集中在中等以上发达的城市,"地域"是将这些人组织起来、互相认识的关键要素之一。当然对于豆瓣来说,第一要素还是共同兴趣,而不是真实关系。
一个良好环境的SNS一定是基于真实关系的,但是如果真实关系总是现实关系的子集,SNS不可能成长。真实关系的延拓,可以通过朋友介绍,不过我认为这种渠道的信度至少随跳数指数衰减,别说六度,三度就和陌生人没什么两样了。真实关系的延拓,还可以通过共同的人类活动。原始社会,我们在一个部落打猎,中世纪,我们是同一个城邦的士兵,俗称战友,新文化运动来了,我们都信仰马克思主义,俗称同志,义务教育普及可以安心读书了,我们是同学,毕业了我们进了同一家公司,于是是同事。除了血缘关系与生俱来,其他现实关系都是建立在共同的人类活动基础上的,而且信度和从事的活动性质相关密切,比如一个人的QQ网友的平均信度绝对不如他的初中同学的平均信度。
我认为:帮助人们组织有价值的人类活动,是SNS拓展真实关系的有效渠道。这里的价值,指的是有利于人们形成可信的人际关系。
豆瓣上的人际网,是靠共同的文化消费品组织起来的网络。书籍、影音、Feed(对应九点频道)是文化消费品的三种不同形式,前两者传统、具体,后者略现代、抽象些。作为一种人类活动,文化消费是否具有上述的价值呢?有,但不够。这是豆瓣的价值观决定的,豆瓣旨在帮助人们发现他感兴趣的文化消费品,而不是人。现在的同城活动是文化消费另一种形式,看看豆瓣给定的活动类型枚举定义即可知:{音乐/演出,电影/戏剧,展览,讲座沙龙,生活/聚会,体育,旅行}。
在我看来,如果豆瓣同城能够做好,其中的商业价值一目了然,比如像"去当当购买此书"一样简单添加一个按钮"去ticket.cn订票",即可。
但是用户真会喜欢吗?我认为控制发布活动质量至关重要。
boubo.com充斥着各种商场打折信息,xiaonei.com的Event尽是换火车票和找兼职的信息。Event没有产生用户价值,因为
Event几乎都是毫无理性地丢给用户的,成了spam。更谈不上商业价值,商户自己在boubo.com上免费发小广告贴。xiaonei.com的
Event更少可能产生消费(找人出去K歌的少,找人练Toefl口语的多,穷学生没钱)。要提升豆瓣同城活动的价值,有两方面挑战,一方面在于控制活动质量,编辑把好关,另一方面在于智能推荐算法,有些作为文化消费品推荐的共性,有些恐怕还有它自己的特性,比如对城市文化的理解、市民出行习惯的理解和城市交通信息等等――和前三个文化消费品不同之处在于,同城活动,一要同城,二要出门、见面、并且同时同地消费。这里面大有文章可作!

还没有看到hainei.com推出Event相关的产品,要加油哦~其实hainei.com也有自己的产品优势,比如多消息窗口及时跟踪Event更新,比如关联Event和相册等等,最主要的是,真人网络的Event的成功执行比例应该比豆瓣高!

总而言之,Event成为SNS的一个明确实体,对网民是有益的。我认为,各家应该根据自己的价值观、根据自己既有的产品结构来谨慎规划Event。

2008年1月8日星期二

Ubuntu桥接到VBox

[keywords : bridge, VirtualBox]
因为作业需要,必须在Windows里连接到局域网,而且和局域网里的其他站�对等通讯,因此NAT不再适用(应用程序不管NAT穿透),必须使用Host
Interface Networking。
按照VBox帮助文件做桥接,我配置了/etc/network/interfaces,按说可以一劳永逸,但是每次休眠回来或重启后,本机就上不了网了。于是改用手动脚本。
启用桥接:
sudo brctl addbr br0
sudo brctl addif br0 eth0
sudo dhclient br0
sudo VBoxAddIF vbox0 root br0
然后可以ip addr看一下有了没有,其实看命令行输出也行了。
ok的话就可以开VBox了,看到windows拿到了独立IP。
关机之前建议执行这个脚本,不改系统配置文件,不留痕迹,省得休眠回来或重启之后又断网!
sudo ifconfig br0 down
sudo brctl delbr br0