@功能的猜测
2012-08-17 00:19 ggzwtj 阅读(203) 评论(0) 编辑 收藏 举报闲来无事,八卦一下@功能(下面的规则是用新浪微博做的实验)。
先举个例子:如果有一个昵称是【明天hui更好】,由于支持拼音,需要匹配下面的两个串:
- 【明,天,h,u,i,更,好】;
- 【ming,tian,h,u,i,geng,hao】;
通过测试发现匹配的规则还是比较苛刻的:
- 如果是汉字匹配汉字,必须完全匹配,比如【名】和【明】无法匹配;
- 如果是拼音匹配汉字,必须和汉字的读音完全匹配【min】和【ming】无法匹配(除非是【min天】这种后面是汉字的);
- 必须从头匹配到尾,【明天更好】和【明天hui更好】是无法匹配的;
还有一个放宽了的地方是:
- 【更好】是可以和【明天hui更好】匹配的;
如果按照上的功能做@的话就很简单了:
- 把用户关注的人的昵称取回来:别如【明天hui更好】等;
- 依次判断用户的输入是否和关注人的昵称匹配:
- 根据关注人的昵称生成对应的拼音数组【ming,tian,h,u,i,geng,hao】;
- 根据用户输入的第一个字符找到开始匹配的下标:
- 判断从该位置能否完全匹配,如果能就加入结果列表;
- X:正在匹配的输入的下标,Y:昵称的下标;
- 如果X处是汉字,且匹配,X++,Y++;否则,找下一个第一个匹配的Y的位置;
- 如果X处是汉字,且匹配,则继续匹配整个的拼音(或者是到下个汉字前的拼音),比如X为【m】,Y为【明】,那么就要匹配到【ming】,然后X+=4,Y++;
- X为输入的长度时匹配完成;
- 根据距离判断能否再次匹配;
- 判断从该位置能否完全匹配,如果能就加入结果列表;
最后要想一下如何将这些结果排序:
- 首字匹配的一定比从中间才开始匹配的准确;
- 经常联系的同一种经常联系的放在前面?
最后再看效率:
在一般的情况下,昵称中相同的字符数量不是会很多,在最极端的情况用户输入的是输入的是长度为N的【qqq..p】,而关注的人的昵称是长度为M的【qqqqq...】,这种情况下要比较的次数为NxM,在关注少于2000的时候也毫无压力。
如果一定认为昵称中重复的字符的数目非常非常多,上KMP和自动机都是没有问题的,但是,可能大部分的时间消耗在了这些上面,有时候复杂的东西不一定是高效的,尤其是为了一些小概率事件。实际的数据不会像程序比赛的时候那样拿变态的数据故意卡你。
最后可以放开【连续字符匹配】和【拼音完全匹配】,这个时候就有点意思的,不过没有什么实际意义。。。
如果想@的时候在所有的人里面找,那估计PD的脑袋是被驴踢了。
public class AntiUtil { /** * [input]->[word] [word] [word] [word] * * [nick]-->[word] [word] [word] [word] * [pin1] [pin1] [pin1] [pin1] * [pin2] [pin2] [pin2] * [pin3] [pin3] */ public static boolean nickMatch(String nick, String input){ if(input == null || input.length() <= 0){ return false; } int nickLength = nick.length(), x = 0, y = 0, inputLength = input.length(); String[][] pinyinArray = new String[nickLength][]; for(int i = 0; i < nickLength; i++){ pinyinArray[i] = PinyinHelper.toHanyuPinyinStringArray(nick.charAt(i)); } nextStartChar: for(int i = 0; i < nickLength; i++){ //System.out.println("start:" + nick.charAt(i+x)); nextInputChar: while(y < inputLength){ if(i+x >= nickLength){ x = 0; y = 0; continue nextStartChar; } // [x+i] vs [y] // input is chiness || nick is english.(3 situation). if(!isEnglish(input.charAt(y)) || isEnglish(nick.charAt(x+i))){ if(nick.charAt(i+x) != input.charAt(y)){ x = y = 0; continue nextStartChar; }else{ x++; y++; } continue; } // input is english || nick is chiness.(1 situation). int oldY = y; nextPinYin: for(int j = 0; pinyinArray[i+x] != null && j < pinyinArray[i+x].length; j++){ y = oldY; for(int k = 0; k < pinyinArray[i+x][j].length() && y < inputLength; k++){ if(k == pinyinArray[i+x][j].length()-1){ x++; continue nextInputChar; } if(pinyinArray[i+x][j].charAt(k) != input.charAt(y)){ // success. if(y > 0){ x++; continue nextInputChar; }else{// fail. continue nextPinYin; } }else{ y++; } } } if(y == inputLength){ return true; } // not find pinyin. x = 0; y = 0; continue nextStartChar; } // success if(y == inputLength){ return true; } } return false; } public static List<String> getNicksByInput(List<String> nickList, String input, int maxCount){ input = input.toLowerCase(); LinkedList<String> result = new LinkedList<String>(); for(String nick : nickList){ try{ if(nickMatch(nick, input)){ result.addLast(nick); if(maxCount-- <= 0){ return result; } } }catch(Exception e){ e.printStackTrace(); } } return result; } public static boolean isEnglish(char ch){ return (ch >='A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); } /***************************TEST***************************/ public static void main(String[] args){ System.out.println(nickMatch("单干一支花", "dgyzh")); System.out.println(nickMatch("单干一支花", "sg")); System.out.println(nickMatch("单干一支花", "一支花")); System.out.println(nickMatch("单干一支花", "花")); System.out.println(nickMatch("单干一支花", "gan一支")); System.out.println(nickMatch("单干一支花", "shgb")); } }
------------------------------------------
欢迎拍砖。