DOM元素查询函数getElementsByClassName

IE8以下的IE浏览器不支持getElementsByClassName。

参考http://www.cnblogs.com/rubylouvre/archive/2009/07/24/1529640.html的最最终实现方案,改写了一下,但这个“最最终”实现方案里有一个bug,result放在if的第一个分支里定义了。

余去掉了tag的查询,加入了安全检查
对DOM节点的检查是检测nodeType,当然刻意构造一个含有nodeType属性的非DOM节点对象,也能通过检测
这是一种鸭子测试,平衡安全性以及效率后的折衷选择
(→鸭子类型

另外className必须是字符串类型,否则返回空结果集
这和Firefox/Chrome的内置实现有点区别,className不是string的时候,Firefox/Chrome会使用toString()去取得字符串
可以打开控制台玩一下:

var obj = {};
obj.toString = function() {return 'x-grid-cell-inner';}; // 你的css类选择器
document.getElementsByClassName(obj);

最后附上源码

getElementsByClassName : function(node, className) {
    if (!node || !(node.nodeType == 1 || node.nodeType == 9)) {
        return undefined;
    }
    var result = [];
    if (typeof className !== 'string') {
        return result;
    }
    if (document.getElementsByClassName) {
        return node.getElementsByClassName(className);
    }

    var classes = className.split(' '),
        elements = (node.all) ? node.all : node.getElementsByTagName('*'),
        patterns = [],
        current,
        match;
    var i = classes.length;
    while(--i >= 0) {
        patterns.push(new RegExp('(^|\\s)' + classes[i] + '(\\s|$)'));
    }
    var j = elements.length;
    while (--j >= 0) {
        current = elements[j];
        match = false;
        for (var k=0, kl=patterns.length; k<kl; k++) {
            match = patterns[k].test(current.className);
            if (!match) {
                break;
            }
        }
        if (match) {
            result.push(current);
        }
    }
    return result;
}
3,993 次浏览 | 没有评论
2012年8月21日 | 归档于 技术, 程序

可以自由添加或删减过滤器的ExtJS4 Store

ExtJS4 Store的过滤器功能用起来巨蛋疼无比,如果写ExtJS4批判,绝对能写一篇
filter()只能增加过滤器不能减,clearFilter()又一次全部清空过滤器,filterBy()会无视过滤器列表中已存在的过滤器,没有一个令人满意的

一个gridpanel,页面上有很多ComboBox作为筛选条件,用户点击这些ComboBox,对数据进行筛选
问题在于ExtJS4的Store不能单个更换过滤器,必须先清空全部的过滤器,再一个个加回来,怎么会有这种反人类的设计啊

参考源码写了一个能自由增减和替换过滤器的派生Store

/**
 * @author kuyur
 */

Ext.define('myproject.store.BaseStore', {
	extend: 'Ext.data.Store',
	addFilter: function(key, filter, value) {
		if (Ext.isString(filter)) {
			filter = {
				property: filter,
				value: value
			};
		}
		var me = this;
		var decoded = me.decodeFilters(filter);
		if (decoded.length <= 0) {
			return;
		}
		var doLocalSort = me.sortOnFilter && !me.remoteSort;
		if (!Ext.isString(key)) {
			key = key.toString();
		}
		me.filters.add(key, decoded[0]);

		if (me.remoteFilter) {
			me.load();
		} else {
			if (me.filters.getCount()) {
				me.snapshot = me.snapshot || me.data.clone();
				me.data = me.snapshot.filter(me.filters.items);
				if (doLocalSort) {
					me.sort();
				}
				if (!doLocalSort || me.sorters.length < 1) {
					me.fireEvent('datachanged', me);
				}
			}
		}
	},
	removeFilter: function(key) {
		var me = this;
		if (!Ext.isString(key)) {
			key = key.toString();
		}
		var doLocalSort = me.sortOnFilter && !me.remoteSort;
		if (me.filters.removeAtKey(key)) {
			if (me.remoteFilter) {
				me.load();
				return;
			}
			if (me.filters.getCount()) {
				me.snapshot = me.snapshot || me.data.clone();
				me.data = me.snapshot.filter(me.filters.items);
			} else {
				me.data = me.snapshot.clone();
				delete me.snapshot;
			}
			if (doLocalSort) {
				me.sort();
			}
			if (!doLocalSort || me.sorters.length < 1) {
				me.fireEvent('datachanged', me);
			}
		}
	}
});

addFilter函数第一个参数是key,标识过滤器用
第二个参数可以是过滤器对象的一个实例,也可以是过滤器的定义{filterFn: function(record){}},还可以是数据模型的字段名,当作为字段名(字符串)使用时,需要传入第三个参数作为字段值
addFilter可以新添加一个过滤器,也可以覆盖旧的过滤器

removeFilter很简单,就是移除一个过滤器

4,346 次浏览 | 没有评论
2012年8月4日 | 归档于 技术, 程序

JavaScript对象当map使

一个转换函数:

getIconURL: function(content) {
    if (typeof content !== 'string') {
        return undefined;
    }
    switch (content) {
    case 'stopped':
        return 'resources/icons/circle_red_l.png';
    case 'running':
        return 'resources/icons/circle_green_l.png';
    case 'pending':
        return 'resources/icons/circle_orange_l.png';
    case 'shutting-down':
        return 'resources/icons/circle_orange_l.png';
    case 'stopping':
        return 'resources/icons/circle_orange_l.png';
    case 'available':
        return 'resources/icons/disconnect.png';
    case 'error':
        return 'resources/icons/delete.png';
    case 'us-east-1':
        return 'resources/icons/flag_us.png';
    case 'us-west-1':
        return 'resources/icons/flag_us.png';
    case 'us-west-2':
        return 'resources/icons/flag_us.png';
    case 'ap-northeast-1':
        return 'resources/icons/flag_jp.png';
    case 'ap-southeast-1':
        return 'resources/icons/flag_sg.png';
    case 'eu-west-1':
        return 'resources/icons/flag_eu.png';
    case 'sa-east-1':
        return 'resources/icons/flag_br.png';
    case 'valid':
        return 'resources/icons/accept.png';
    case 'invalid':
        return 'resources/icons/delete.png';
    default:
        return undefined;
    }
}

新的分支越加越多,就越觉得效率有问题,而且这个函数经常被调用

想使用map来代替switch,但又不想用ExtJS的HashMap
于是把JavaScript的对象当map使,还是非常好用的
速度没测过,但在分支特别多的情况下应该要快上很多

/**
 * @author kuyur
 */

Ext.define('myproject.util', {
	statics: {
		map: {
			'stopped': 'resources/icons/circle_red_l.png',
			'running': 'resources/icons/circle_green_l.png',
			'pending': 'resources/icons/circle_orange_l.png',
			'shutting-down': 'resources/icons/circle_orange_l.png',
			'stopping': 'resources/icons/circle_orange_l.png',
			'available': 'resources/icons/disconnect.png',
			'error': 'resources/icons/delete.png',
			'us-east-1': 'resources/icons/flag_us.png',
			'us-west-1': 'resources/icons/flag_us.png',
			'us-west-2': 'resources/icons/flag_us.png',
			'ap-northeast-1': 'resources/icons/flag_jp.png',
			'ap-southeast-1': 'resources/icons/flag_sg.png',
			'eu-west-1': 'resources/icons/flag_eu.png',
			'sa-east-1': 'resources/icons/flag_br.png',
			'valid': 'resources/icons/accept.png',
			'invalid': 'resources/icons/delete.png'
		},
		getIconURL: function(content) {
			if (typeof content !== 'string') {
				return undefined;
			}
			return myproject.util.map[content];
		},
		...
	}
});
3,861 次浏览 | 没有评论
2012年8月2日 | 归档于 技术, 程序

只提供RESTful API的Java EE工程的多语言化

一个Java EE工程如果只提供RESTful API,多语言化还真是非常简单,虽然Servlet的WEB页面多语言化可能也并不复杂

一开始就上多语言,比半中途才上,可以省掉很多不必要的功夫

提供多语言的类是一个典型的多例模式(http://en.wikipedia.org/wiki/Multiton_pattern),阎宏的《Java与模式》中有一个Demo,但那个Demo的代码写得真叫一个烂,一堆语法错误不说,逻辑都写得莫名其妙的,网上的博客一大抄,导致这种有问题的代码广为流传,真是误人子弟

某工程半中途要上多语言,语言资源文件决定使用xml,另外语言不使用region和locale细分,语言只包括下面四种
ja (日语,locale_ja.xml)
en (英语,locale_en.xml)
zh_CN (简体中文,locale_zh_CN.xml)
zh_TW (繁体中文,locale_zh_TW.xml)

因此从字符串到java.util.Locale对象,首先就要做一次转换。

如果语言文件选择properties文件,文件读取将非常简单
选择xml文件则需要自己写处理逻辑,这方面网上的文章非常多

语言资源需要按照下面的写法组织,推荐保存为无BOM的UTF-8文档

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 
<properties>
  <entry key="LoingFailed">登录失败,错误的用户名或密码。</entry>
</properties>

多语言类源代码,注意包的名字和资源文件路径
资源文件推荐放在工程的resources下,编译后会打包在一起

package info.kuyur.demo;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;

public class LocaleLoader {
	public static final String LOCALE_EN = "en";
	public static final String LOCALE_JA = "ja";
	public static final String LOCALE_ZH_CN = "zh_CN";
	public static final String LOCALE_ZH_TW = "zh_TW";
	private static final String LOCALE_DEFAULT = "ja";
	private static final String LOCALE_PREFIX = "info/kuyur/locales/locale";
	private static final String MESSAGE_NOT_FOUND = "Message not found.";
	private static final String LOCALE_FILE_NOT_EXISTED = "Locale file {0}{1}{2}{3} not existed.";

	private static Map<String, LocaleLoader> instances = new HashMap<String, LocaleLoader>(4);
	private static XMLResourceBundleControl xmlResourceBundleControl = new XMLResourceBundleControl();

	private String locale;
	private ResourceBundle resourceBundle = null;

	private static class XMLResourceBundle extends ResourceBundle {
		private Properties props;
		XMLResourceBundle(InputStream stream) throws IOException {
			props = new Properties();
			props.loadFromXML(stream);
		}

		@Override
		protected Object handleGetObject(String key) {
			return props.getProperty(key);
		}

		@Override
		public Enumeration<String> getKeys() {
			Set<String> handleKeys = props.stringPropertyNames();
			return Collections.enumeration(handleKeys);
		}
	}

	private static class XMLResourceBundleControl extends ResourceBundle.Control {
		@Override
		public List<String> getFormats(String baseName) {
			return Collections.singletonList("xml");
		}

		@Override
		public ResourceBundle newBundle(String baseName, Locale locale, String format,
				ClassLoader loader, boolean reload)
		throws IllegalAccessException, InstantiationException, IOException {
			if ((baseName == null) || (locale == null) || (format == null) || (loader == null)) {
				throw new NullPointerException();
			}
			ResourceBundle bundle = null;
			if (format.equalsIgnoreCase("xml")) {
				String bundleName = this.toBundleName(baseName, locale);
				String resourceName = this.toResourceName(bundleName, format);
				URL url = loader.getResource(resourceName);
				if (url != null) {
					URLConnection connection = url.openConnection();
					if (connection != null) {
						if (reload) {
							connection.setUseCaches(false);
						}
						InputStream stream = connection.getInputStream();
						if (stream != null) {
							BufferedInputStream bis = new BufferedInputStream(stream);
							bundle = new XMLResourceBundle(bis);
							bis.close();
						}
					}
				}
			}
			return bundle;
		}
	}

	// can not be constructed by external
	private LocaleLoader(String locale) {
		this.locale = locale;
		Locale lo = toLocale(locale);
		try {
			resourceBundle = ResourceBundle.getBundle(LOCALE_PREFIX, lo, xmlResourceBundleControl);
		} catch (MissingResourceException e) {
			resourceBundle = null;
		}
	}

	/**
	 * Return a locale object which contains messages.
	 * If parameter is null or emtpy, will return default locale.  
	 * @param locale
	 * @return
	 */
	public synchronized static LocaleLoader getInstance(String locale) {
		locale = toSupportedLocale(locale);
		LocaleLoader instance = instances.get(locale);
		if (instance != null) {
			return instance;
		} else {
			instance = new LocaleLoader(locale);
			instances.put(locale, instance);
			return instance;
		}
	}

	public String getLocale() {
		return locale;
	}

	public String getString(String key) {
		if (resourceBundle == null) {
			return MessageFormat.format(LOCALE_FILE_NOT_EXISTED, LOCALE_PREFIX, "_", locale, ".xml");
		}
		try {
			return resourceBundle.getString(key);
		} catch (Exception e) {
			return MESSAGE_NOT_FOUND;
		}
	}

	private static String toSupportedLocale(String locale) {
		if (LOCALE_JA.equalsIgnoreCase(locale)) {
			return LOCALE_JA;
		}
		if (LOCALE_EN.equalsIgnoreCase(locale)) {
			return LOCALE_EN;
		}
		if (LOCALE_ZH_CN.equalsIgnoreCase(locale)) {
			return LOCALE_ZH_CN;
		}
		if (LOCALE_ZH_TW.equalsIgnoreCase(locale)) {
			return LOCALE_ZH_TW;
		}
		return LOCALE_DEFAULT;
	}

	private Locale toLocale(String locale) {
		if (LOCALE_JA.equals(locale)) {
			return Locale.JAPANESE;
		} else if (LOCALE_EN.equals(locale)) {
			return Locale.ENGLISH;
		} else if (LOCALE_ZH_CN.equals(locale)) {
			return Locale.SIMPLIFIED_CHINESE;
		} else if (LOCALE_ZH_TW.equals(locale)) {
			return Locale.TRADITIONAL_CHINESE;
		}
		return Locale.JAPANESE;
	}
}

这个类被设计成不会出错,即使语言文件不存在或者词条不存在
在代码中写死了支持的语言种类,但考虑到几乎不可能频繁增加新语言,这种写法还是可以接受的
(java.util.Locale中的静态Locale常量也是写死的)

测试用例:

package info.kuyur.demo;

import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import org.junit.BeforeClass;
import org.junit.Test;

public class LocaleLoaderTest {
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
	}

	@Test
	public void testGetString() {
		LocaleLoader ja = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertTrue(LocaleLoader.LOCALE_JA.equals(ja.getLocale()));
		assertTrue("ja".equals(ja.getLocale()));
		System.out.println(ja.getString("LoingFailed"));
		System.out.println(ja.getString("hogehoge"));

		LocaleLoader en = LocaleLoader.getInstance("En");
		assertTrue(LocaleLoader.LOCALE_EN.equals(en.getLocale()));
		assertTrue("en".equals(en.getLocale()));
		System.out.println(en.getString("LoingFailed"));
		System.out.println(en.getString("hogehoge"));

		LocaleLoader zh_CN = LocaleLoader.getInstance("zH_cN");
		assertTrue(LocaleLoader.LOCALE_ZH_CN.equals(zh_CN.getLocale()));
		assertTrue("zh_CN".equals(zh_CN.getLocale()));
		System.out.println(zh_CN.getString("LoingFailed"));
		System.out.println(zh_CN.getString("hogehoge"));

		LocaleLoader zh_TW = LocaleLoader.getInstance("Zh_Tw");
		assertTrue(LocaleLoader.LOCALE_ZH_TW.equals(zh_TW.getLocale()));
		assertTrue("zh_TW".equals(zh_TW.getLocale()));
		System.out.println(zh_TW.getString("LoingFailed"));
		System.out.println(zh_TW.getString("hogehoge"));
	}

	@Test
	public void testIsSameObject() {
		LocaleLoader ja1 = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		LocaleLoader ja2 = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertSame(ja1, ja2);
	}

	@Test
	public void testDefault() {
		LocaleLoader ja = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertTrue(LocaleLoader.LOCALE_JA.equals(ja.getLocale()));

		LocaleLoader hogehoge = LocaleLoader.getInstance("hogehoge");
		assertTrue(LocaleLoader.LOCALE_JA.equals(hogehoge.getLocale()));

		assertSame(ja, hogehoge);
	}

	@Test
	public void testNullLocaleName() {
		LocaleLoader hogehoge = LocaleLoader.getInstance("");
		assertTrue(LocaleLoader.LOCALE_JA.equals(hogehoge.getLocale()));

		LocaleLoader gehogeho = LocaleLoader.getInstance(null);
		assertTrue(LocaleLoader.LOCALE_JA.equals(gehogeho.getLocale()));
	}
}

用法用例:
参考测试用例

4,495 次浏览 | 没有评论
2012年7月26日 | 归档于 技术, 程序

定制自己的ExtJS4的timefield控件

需求的变态是没有极限的
某RIA以前使用Silverlight开发,今年3月开始使用ExtJS4写JavaScript版本
两种完全不同的语言,写出来的界面任你再一致,很多特性还是很难移植的
比如Silverlight的timefield控件,支持用键盘的上下方向键调整时间,光标在小时数字的位置时,上下键的作用是增减一小时,光标在分钟数字的位置时,上下键的作用是增减一分钟
于是某测试人员说,既然Silverlight能做到,JS版本也希望实现这个功能
(余抽乃大爷100遍,这种要求何等无理啊,这是和控件自身相关的特性,JS要实现就非得hack ExtJS的代码不可,Silverlight能做到是控件它自身就已经实现,又不是代码写出来的,#$*%^&_+

花了一晚时间,还是把它给实现了,就是下面的定制timefield控件
由于光标位置没法判断,因此键盘操作只能增减一分钟

先是拦截timefield的keypress事件,不起作用,再试着拦截specialkey事件,UP键和DOWN键都成功捕捉到了
UP键的事件能停止,DOWN键的事件死活不能停止
仔细想一下,timefield控件的DOWN键是用来展开下拉列表的,看来默认是不给停止的了,没办法,读源代码

timefield是Ext.form.field.Time的别名
Ext.form.field.Time依次继承自Ext.form.field.Picker,Ext.form.field.Trigger,Ext.form.field.Text
在Ext.form.field.Picker中发现initEvents方法:

    initEvents: function() {
        var me = this;
        me.callParent();

        // Add handlers for keys to expand/collapse the picker
        me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
            down: function() {
                if (!me.isExpanded) {
                    // Don't call expand() directly as there may be additional processing involved before
                    // expanding, e.g. in the case of a ComboBox query.
                    me.onTriggerClick();
                }
            },
            esc: me.collapse,
            scope: me,
            forceKeyDown: true
        });

        // Non-editable allows opening the picker by clicking the field
        if (!me.editable) {
            me.mon(me.inputEl, 'click', me.onTriggerClick, me);
        }

        // Disable native browser autocomplete
        if (Ext.isGecko) {
            me.inputEl.dom.setAttribute('autocomplete', 'off');
        }
    },

很显然,Down键的事件在这里就被写死了

试着从Ext.form.field.Time派生一个新类MyProject.ui.UpDownTimeField,覆盖initEvents,绕过基类的事件处理

	initEvents: function() {
		var me = this;
		me.callParent();
		me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
			down: function() {
				me.decreaseTime();
			},
			up: function(e) {
				me.increaseTime();
			},
			esc: me.collapse,
			scope: me,
			forceKeyDown: true
		});
		if (!me.editable) {
			me.mon(me.inputEl, 'click', me.onTriggerClick, me);
		}
		if (Ext.isGecko) {
			me.inputEl.dom.setAttribute('autocomplete', 'off');
		}
	},

还是失败了
细心一想,UpDownTimeField的initEvents方法中,me.callParent()调用基类方法,也即是Ext.form.field.Picker的initEvents,根本就还没绕过去嘛

于是用Ext.form.field.Text.prototype.initEvents.call(me)代替,成功用自己的事件处理取代了原来的事件处理

最后的UpDownTimeField:

Ext.define('MyProject.ui.UpDownTimeField', {
	extend: 'Ext.form.field.Time',
	alias: 'widget.updowntimefield',
	format: 'H:i',
	increment: 30,
	minValue: '00:00',
	maxValue: '23:59',
	initEvents: function() {
		var me = this;
		Ext.form.field.Text.prototype.initEvents.call(me);
		me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
			pageDown: function() {
				if (!me.isExpanded) {
					me.onTriggerClick();
				}
			},
			down: function() {
				me.decreaseTime();
			},
			up: function(e) {
				me.increaseTime();
			},
			esc: me.collapse,
			scope: me,
			forceKeyDown: true
		});
		if (!me.editable) {
			me.mon(me.inputEl, 'click', me.onTriggerClick, me);
		}
		if (Ext.isGecko) {
			me.inputEl.dom.setAttribute('autocomplete', 'off');
		}
	},
	increaseTime: function() {
		var me = this;
		if (me.isExpanded || !me.isValid()) {
			return;
		}
		var timeString = me.getRawValue();
		if (timeString) {
			var arr = timeString.split(':');
			var h = parseInt(arr[0], 10);
			var m = parseInt(arr[1], 10);
			m++;
			if (m > 59) {
				m = 0;
				h++;
				if (h > 23) {
					h = 0;
				}
			}
			h = h>=10 ? h.toString() : '0'+h;
			m = m>=10 ? m.toString() : '0'+m;
			me.setRawValue(h + ':' + m);
		}
	},
	decreaseTime: function() {
		var me = this;
		if (me.isExpanded || !me.isValid()) {
			return;
		}
		var timeString = me.getRawValue();
		if (timeString) {
			var arr = timeString.split(':');
			var h = parseInt(arr[0], 10);
			var m = parseInt(arr[1], 10);
			m--;
			if (m < 0) {
				m = 59;
				h--;
				if (h < 0) {
					h = 23;
				}
			}
			h = h>=10 ? h.toString() : '0'+h;
			m = m>=10 ? m.toString() : '0'+m;
			me.setRawValue(h + ':' + m);
		}
	}
});

使用:

{
    xtype: 'updowntimefield',
    itemId: 'form-xxx-pickTimeField',
    name: 'LaunchTime',
    fieldLabel: message.YourLable,
    labelWidth: 150,
    labelClsExtra: 'your-css',
    regex: /^\d\d:[0-5]\d$/,
    regexText: message.YourMessage   
}

事实上timefield的合法性验证不是特别严密,可以自己指定regex属性

5,736 次浏览 | 没有评论
2012年7月24日 | 归档于 技术, 程序

Canon EF28mm F1.8 USM试镜

最近入了两定焦镜头,一原厂广角用来拍景,一副厂85mm用来拍人。
原厂头即Canon EF28mm F1.8 USM。
11区这边的价格和国内行货差不多,比香港水货要贵上一大截。

当初不想入EF50mm F1.4,觉得50mm用在半幅机上不上不下,但在试过28mm和85mm后,这个观点可能需要修正
每个焦段都有它的适用范围,别的焦段是无论如何都替代不了这个焦段的
28mm即使有大光圈,拍稍远一点的小东西就非常勉强了
由于连入两颗头有点伤身,过段时间可能会入个40mm饼干头玩玩

出浴照(18-200mm变焦头在57mm焦距F4.5光圈拍摄,可以看出几乎可以忽略的背景虚化能力)

换上28mm头,1.8光圈全开

随随便便的晚餐,F1.8,1/30s,无闪光灯,ISO 100

再来一次钥匙扣,F1.8,1/30s,无闪光灯,ISO 400

周日拉同事去试镜,同事说还没去过皇居,于是就去了皇居。
家附近的紫阳花,F1.8

皇居外苑的松树,F1.8

这棵树下有个卖乌冬面的,午餐速战速决。F1.8

皇居外苑草坪上的小花,F2

二重桥后面的某桥,F9

二重桥,F7.1

看完无聊的二重桥,同事提议绕一圈走到北之丸
F7.1

立入禁止,F1.8

警察叔叔不知在干啥,F9

梧桐叶子,F2.2

小野花,F2

北之丸公园入口路上的指示牌,F9

某展览馆附近,F8

某展览馆附近,F1.8

青苔,F1.8

青草上的泡泡,F2.2

武道馆,F11

回家晚饭后想骑自行车去拍一下东京天空树,拿上三角架
手持一张,F1.8,1/40s

正寻找着角度,天空树居然关灯了,于是算了
隅田川上随便一张夜景后回家,F11,11s,ISO 640

回家路上正骑着车打着电话呢,遇到了警察蜀黍查车,第一次被查

4,684 次浏览 | 没有评论
2012年7月22日 | 归档于 私语

只用天平称3次,找出13个球中重量与其他球不同的唯一球

题目:13个有编号的球,其中12个重量相同,剩下的一个与其他球重量不同,但不知是轻了还是重了,只用天平称3次,找出这个与众不同的球
twitter上无意从别人的转推看到,开始以为很简单,深入思考后发现细分的情形还是不少的。其实它是一条扫雷题。或许某些招聘也会拿来当面试题

如果知道与众不同的球(下面简称特殊球)是轻了还是重了,就变成了非常简单的题目,即使球数量是27个,都能只称3次就找出特殊球

闲得蛋疼推导出了解法

解法:
将重量都相同的球称之为标准球。
不妨假定球的编号是1,2,3,…,13

将之分成三堆,1,2,3,4为堆A,5,6,7,8为堆B,9,10,11,12,13为堆C

第一次称:
将堆A和堆B放在天平上,如果平衡(分支一),则特殊球在堆C,如果不平衡(分支二),则特殊球在堆A或堆B

分支一:
堆A和堆B的球全部都是标准球。这时候要利用上标准球的一个,不妨加入球1
将球1和球9放在天平一边,球10和球11放在天平另一边,称第二次,如果平衡(分支三),则特殊球在12,13之中,如果不平衡(分支四),则特殊球在9,10,11之中,我们要记下1,9 VS 10,11那边轻那边重

分支三:
很简单,将12和标准球放在天平两边称第三次,平衡则特殊球是13(已经找出但仍然不知道轻了还是重了),不平衡则特殊球是12,并且还能知道轻了还是重了

分支四:
将10和11放在天平两边称第三次,
如果平衡,则特殊球是9,因此10,11都是标准球,依据第二次的结果,还能知道9重了还是轻了
如果不平衡,则特殊球在10,11当中。在第二次的结果中,如果10,11这边重了,说明特殊球是重了,则特殊球是第三次的较重者;如果第二次10,11这边轻了,说明特殊球是轻了,则特殊球是第三次的较轻者

回到分支二
分支二:
回顾一下:特殊球要么在堆A中,要么在堆B中
如果堆A在第一次测量中较轻,分支五
如果堆B在第一次测量中较轻,分支六
分支五和分支六的后续解法是对称的,我们只需要解决分支五即可

分支五:堆A(1,2,3,4)较轻,堆B(5,6,7,8)较重,这个结果很重要,后面的推理要利用到
将1,5,6放在天平一边,2,7,8放在天平另一边,称第二次
如果天平平衡,则1,2,5,6,7,8都是标准球,特殊球在3,4当中,拿标准球的任一个和3称第三次,如果平衡则特殊球是4,如果不平衡,则特殊球是3
如果1,5,6较轻,2,7,8较重,分支七
如果1,5,6较重,2,7,8较轻,分支八

分支七:
我们要筛选出特殊球的备选球,第二次测量的结果也要利用,1,5,6较轻,2,7,8较重
1,7,8都是特殊球的备选,2,5,6都不可能是备选。因为2或5或6如果是特殊球,代入去都能导致矛盾
这种推理过程跟扫雷是一样的
然后将7,8放在天平两边称第三次,如果平衡,则特殊球是1,如果不平衡,则特殊球是较重的那个

分支八(和分支七的推理逻辑是一样的):
第二次测量结果已知是1,5,6较重,2,7,8较轻
可以筛选出2,5,6是特殊球的备选。1,7,8都不可能是。
将5,6放在天平两边称第三次,如果平衡,则特殊球是2,如果不平衡,则特殊球是较重的那个

OVER。

5,840 次浏览 | 2 条评论
2012年7月5日 | 归档于 私语

使用VNC远程连接Amazon EC2的Ubuntu实例

使用AmazonEC2的Ubuntu实例编译Python-Webkit(http://www.gnu.org/software/pythonwebkit/)
需要启动浏览器证实编译成果,唯有安装VNC

使用putty SSH登录Ubuntu

首先安装VNC Server

cd /home/ubuntu
sudo apt-get update
sudo apt-get install vnc4server

启动VNC Server一次,生成配置文件,同时也设置密码

sudo vnc4server

再关掉

sudo vnc4server -kill :1

修改配置文件

cd ./.vnc
sudo vim xstartup

将#去掉

# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc

安装(或更新)桌面

sudo apt-cache search ubuntu-desktop
sudo apt-get install ubuntu-desktop

如果ubuntu开启了防火墙,关掉它

sudo ufw status
sudo ufw disable

给予X11初始化脚本执行权限

sudo chmod 755 /etc/X11/xinit/xinitrc

启动VNC Server,同时可以设置分辨率和色深(24位色深比较耗带宽,自己看网速情况设定)

sudo vnc4server -geometry 1280x1024 -depth 24

到这里ubuntu上的设置完毕

Amazon EC2的Security Groups里,允许5900,5901端口通信

本地:
下载VNC Viewer
(注意仅需要Viewer,不需要服务器端的东东)

启动VNC Viewer,注意地址是 ip:1域名:1
连接,接着会提示输入密码,就能成功进入远程主机的桌面

远程主机桌面的D键被设定为显示桌面的快捷键,需要在keybord shortcuts里禁用这个快捷键
退出远程桌面,重启VNC Server才能生效

sudo vnc4server -kill :1
sudo vnc4server -geometry 1280x1024 -depth 24

17,656 次浏览 | 6 条评论
2012年6月21日 | 归档于 技术

用Google新图形化编程语言Blockly写一类迷宫的通用解

在CB看到新闻:Blockly: Google新推出的图形化编程语言,手一贱点了链接,接着玩起了Demo中的迷宫游戏Maze。

http://blockly-demo.appspot.com/blockly/demos/maze/index.html

Maze是一个自定义类,提供向前向后移动,向左转向右转以及判断前后左右是否有墙等函数。
这里的迷宫是一个很规整的迷宫,墙都是上下左右的直线段,而且是单位长度的整倍,小人每次只能移动单位长度。
(大概从拓扑学上迷宫都可以约化成这样的规整迷宫?)

Demo中的迷宫很简单,顺序结构可以到达终点,但余想写一个通用的解法,可以解所有迷宫。
一个封闭迷宫,沿着墙的一边走,最终可以回到原点,这构成了一个环。如果目标地在这个环上,无论起始方向如何,都能到达目标地。
方向1:

方向2:

方向1中,即使途中有环,都可以避过捕捉。(1和2无本质区别)

余很快发现这个解法还是不够通用,比如下面的简单迷宫,这种解法永远无法找到目标地

因此这个程序只能对付目标和原点在同一个环的情况,或者说,程序需要依赖原点位置才能找到目标,还没足够通用

7,431 次浏览 | 1 条评论
2012年6月1日 | 归档于 程序

Dewplayer播放列表生成器

实在忍受不了用记事本编辑Dewplayer的播放列表,于是用JavaScript写了一个生成器,顺便练一下JavaScript

使用地址:
https://go.kuyur.net/playlist-creator/

将几个文件(index.html/make.js/style.css)下载回本地也可以运行
支持IE6~IE9,Firefox,Chrome,应该也支持Opera
话说,IE8以下真的和别人格格不入啊,IE9在标准JavaScript支持上改善了不少,但还不够

用法简单说明:

必填:
Track titles:歌曲标题列表
Track URLs:
在选中Pattern模式时,请在单行文本框输入URL模式字符串,形如http://example.com/{##}.mp3。{##}会被替换,从1开始,单位递增1,#的位数是生成数字的位数。
{#}.mp3会产生1.mp3,2.mp3,…,9.mp3,10.mp3,…
{##}.mp3会产生01.mp3,02.mp3,…,09.mp3,10.mp3,…
在选中List模式时,请在多行文本框输入歌曲的URL列表,行数必须和曲目标题列表的行数相同

选填:
(Optional) Album Title:专辑名
(Optional) Album Cover URL:专辑封面图片URL

点击Generate按钮就可以生成支持Dewplayer的xml播放列表。
Save按钮现在无效

有时间大概会做成一个wordpress插件

参考资料:
播放列表xml化的命名空间:http://xspf.org/ns/0/
xml文本的格式化: http://www.cnblogs.com/evlon/archive/2009/01/09/1372283.html

8,194 次浏览 | 5 条评论
2012年3月8日 | 归档于 技术, 程序