`
cloudtech
  • 浏览: 4605786 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

把金庸小说数据化——关于语言的思一点考

 
阅读更多
  先看几道有关金庸小说的问题:
  第一题:
  1、一男性角色叫她姑姑,但二者没有血缘关系;
  2、她的师父是女性,师父的师父也是女性;
  3、她于人情世故所知甚少,更习惯生活在原来的的环境中。
  4、曾经有一位武功高强的女性故人来其生活的地方见面,连同主角一同离开了生活的地方。
  5、喜欢男主角,与主角多次分离。
  6、出场时武功比主角高。
  7、曾经易过容。
  根据以上提示猜这个金庸书中的人物。
  
  第二题:
  1、此人偷过皇宫厨房的菜;
  2、此人断了一根手指;
  3、此人教过主角武功;
  4、此人撮合了主角的姻缘;
  5、此人和宿敌恶战同归于尽;
  6、此人某个有名有姓的下属是独臂残疾人
  根据以上提示猜这个金庸书中的人物。
  
  第三题:
  1、A是金书人物,B是历史人物,C是一件物品;
  2、AB有一项爱好相同;
  3、AC两字相同;
  4、A是某组合人物,与某主角长辈交好。曾有人对主角提过B;
  5、主角曾把C送给别人;
  6、B与C有着一定的关系;
  请问AB的名字?
  
  再来一道变态的:
  1、A与B两字相同,A是一个地名,某人跟主角说话的时候提到。B是一招武功,某主角的同事曾经使用过。
  2、C是一招武功与B两字相同,某CEO曾经使用过。
  3、C与D两字相同,D是个残疾见过主角。
  4、E是地名与D两字相同,某CEO曾经提到过这个地方。
  5、F与A两字同音,F见过主角。
  6、G是一招武功与B两字相同,某人在对阵主角的时候曾经使用过。
  7、H是一招武功与C两字相同。
  8、I是一个地名与H两字相同。
  9、主角曾经到过I。
  10、J与I一字相同一字同音,J是某CEO的物什。
  11、J与K两字相同,K也是一样物什,K的主人不是汉人。
  12、L与H两字相同,L是某主角长辈的绰号。
  13、A与M两字同音,M是一个组合名,主角曾经见过这个组合的成员。
  14、M与N两字相同,N见过主角。
  15、O是一招武功,O与N两字相同,某CEO曾经使用过,某人这招武功是看别人使用学会的。
  问:ABCDEFGHIJKLMNO分别是啥。
  
  自从在腾讯做了金庸问答应用,就接触了很多金庸武侠爱好者,也第一次接触到这种条件题目。
  对于前两道题,金庸帝们解决起来比较容易,几乎都是秒杀。第三题涉及到名字字数相同的条件,算是比较烦,但由于整道题其它的提示相对比较清晰,对高手来说也不算难。最后一道题看起来就让人比较闹心,不但到处都是字数相同,还有同音的条件。
  当我第一次见到这种题的时候,一是觉得挺有意思,再有就是立刻想到如果用计算机来解决这样的问题不知会如何。毕竟是一个编程爱好者,遇到很多问题都首先想到是否可以编程解决。当时觉得只要数据录入够全,那么编个程序解决这样的问题不是难事,但随即就想到了两个问题,一个是数据怎么录入,以什么格式存在数据库中,第二就是总不能遇到一道题就编一次程序解决吧,还得搞一个通用的条件录入解释器,然后根据录入的条件去搜索符合条件的数据。当然,再仔细想想,其实两个问题是统一的,需要根据数据来设计条件解释器,或者说根据条件解释器的搜索方式来选择合适的存储方式。
  像这种求唯一解的问题基本上不用考虑,穷举应该是最佳选择,确保不漏掉答案,这样将来作为出题工具的时候才不会出有两个答案的问题。实际上在真实的金庸爱好者出的题目中,出现一题多解的现象也是时有发生的,这种问题称之为bug题,一般都是作废的。
  最开始的时候是这样想的,在数据库中建立很多表,人物表,包括人物的姓名、性别、出场与否、姓氏、职务等等,物品表,地点表,动物表,门派表、武功表、招式表……想了想就觉得这么做是不正确的,因为小说中的元素太多,各种元素之间的关系也是错综复杂,首先人物之间的关系,师徒、父子、母子、师兄弟、师叔侄等等,还有谁杀了谁,谁伤了谁,谁用什么兵器伤了谁,谁到过什么地方,谁死在什么地方等等,也许这种方式录入数据相对清晰,但即便真的能把数据录入,真要根据外部录入的条件来进行查询,简直是要了亲命了,做查询起来几乎也是不可能完成的任务,你需要判断到哪个表中去搜索相应数据,当有数十个表存在的时候,这项工作想想就头疼。
  换个思路吧,不如从录入条件及解释条件这里入手。首先给自己出了一道非常简单的题目:
  此人的师父杀了B,B曾经和此人交过手,B的老大用刀。
  如果将这个题目录入计算机让计算机去搜索呢?这时候我非常怀念当初用Delphi时有一个PascalScript的控件,可以运行后输入delphi的代码,可以调用你自己写在delphi程序中的函数等等,当初还用这个做了一个网页游戏通用外挂,根据不同的网页游戏写出不同的脚本代码,输入到命令框中就可以自动玩网页游戏。不过现在本人已经深深喜欢上了C#的方便,做这种需要大量进行字符串比较,字符串列表的程序,还是C#更的心应手一些,更容易尽快的实现自己的想法,而不会浪费太多时间在写具体的代码上。再说了,用已经会的方式来解决岂不还少了一些挑战性。再再说了,能不能找到新的PascalScript控件也是很难说的。其实我最开始思考解决办法的时候,最先想到的就是函数式语言,当初很粗略的看过函数式语言的介绍,感觉解决这种问题好像LISP、F#更合适。不过F#不支持winform,还得靠C#做界面,而且对F#也不懂,花费时间会比较多,而我比较着急实现功能,还是看看用C#做吧,F#等啥时候要解决科学计算问题的时候再尝试。
  那么我是不是等于需要用C#写一个脚本解释器呢?感觉自己还不具备这个实力。那么先把上面的题目翻译成人比较容易理解计算机也相对更容易处理的话吧:
  X 属于 人物;
  A 属于 人物;
  X 的师父是 A;
  B 属于 人物;
  A 杀了 B;
  X 交手 B;
  C 属于 人物;
  B 的老大是 C;
  C 用 刀;
  
  只要把金庸小说中的所有人物分别代入X、A、B、C进行逐条判断,只要满足要求就可以得出答案了。
  仔细看看上面这段代码看起来还真有点像编程语言:
  转换成VB就是:
  dim X as 人物
  dim A as 人物
  dim B as 人物
  dim C as 人物
  X.师父=A
  X.kill B
  X.fight B
  B.boss=C
  C.weapon=刀
  
  那么用C#写一个伪求解程序吧:


  暂时先不考虑这么穷举花费的时间,首先想想怎么实现上面的功能。第一步是先创建变量,如果用户录入了“A 属于 人物”,那么就创建一个列表A,将数据库中所有的人物填入。同理,如果出现“B 属于 武器”这样的命令的话,就创建一个B的列表变量,将数据库中所有武器填入,以此类推。那么就需要在C#创建一个变量的列表,根据用户定义的变量来增删或者使用变量。为了简化起见,只允许用户输入A-Z的字母作为变量,然后直接先创建listA,listB……listZ一共26个List<string>,根据用户输入的ABCD直接使用对应的列表,这样省去了维护用户使用字母与动态创建的列表之间关系的操作,如果程序其它功能都好用了再来解决这块也不晚。

  这好像有点像Delphi、C#等强数据类型的编程语言,好处么就是根据用户的变量定义我可以很容易就知道这个变量到底是什么类型,可以根据变量的类型直接使用该变量的一些属性和方法,比如如果定义了A是人物,那么我就可以去取人物的性别、是否出场、是否残疾等各种信息,也可以调用杀人的方法。那么在数据库中怎么存放呢,如果分别放在不同的表中,那么还是涉及到要判断是什么类型然后去相应的数据表中取数据,并非不可实现,但是编写代码会增加很多条件判断的语句,不如直接都放在一个表中,只要用“select 名称 from datatable where 类型 like '人物'” 这样的SQL语句就可以直接取出全部数据了。做了一个简单的测试,还是可以完成搜索功能的,当然,数据类型比较少,数据量也比较少。而且实际上金庸小说中的人物有将近1400人,真要这么穷举肯定不行,再有就是当条件多的话,这么循环嵌套也是不行的,还是改成递归比较理想,然后再加上事先剪枝,基本上就可以了。
  人物的数据比较简单,直接从网上找金庸小说人物列表,直接导入就行了。不过后来在录入其它数据的时候发现网上这些人物数据并不十分准确,有很多人名都是错的。为了保证录入的数据比较规矩,不出现错误,定义了好多基础类型,比如人物、动物、武器、地点、书籍、组织、门派、身份等等,同时,为了便于以后查找,定义了一些录入规则(类似于操作符),比如人物的师傅必须是人物,人物到过的必须是地点,人物杀的必须是人物和动物,人物属于门派或组织等等。也就是说每一个数据都必须明确属于某个类型。不过在录入的过程中发现,其实没必要非要明确规定所有操作的数据必须属于某一类,因为根据我规定的录入规则其实可以大体判断数据类型,比如某人的父亲是谁,那么父亲所涉及到的两个人必然都是属于人物,在比如某人杀了谁,被杀的必然是人物或者动物,同理,阅读的必然是书,到过的必然是地点……咦,现在有点像弱类型的编程语言了,根据操作符的不同来推测数据类型。思想从C#变成了PHP了,呵呵。通过这段的经过,想了想编程语言,强类型和弱类型的语言只是对于使用者本身不同,内部实现其实也还是有类型的,不过就是弱类型的用的时候才动态确定是什么类型,甚至用的时候也不能完全确定,那么就只能先用暂时用一种符合要求的,当后面发现不是这种类型的时候再转换过去。对编译原理不懂,瞎猜的。拿我这里的例子,某人杀死A,那么A可能是人物也可能是动物,但是后面如果遇到了A的姓氏,那么A必然就是人物了,如果遇到A是鸟类之类的条件那么自然就是动物。咦,如果是强类型,那这算不算多态?
  在录入数据的时候对录入程序进行了不断地修改,能够自动完成很多录入,比如录入A是B的师傅,那么就会自动录入B是A的徒弟。再后来,为了录入更多地信息,比如张昭重用的武器是凝碧剑,那么张召重必然是用剑,这就需要录入两条数据,骆冰用的是鸳鸯刀,鸳鸯刀属于双刀,双刀属于刀,所以需要录入三条数据,骆冰使用鸳鸯刀,骆冰使用双刀,骆冰使用刀。于是,刀继承于武器,双刀继承于刀,鸳鸯刀继承于双刀,只要录入了骆冰使用鸳鸯刀,就自动录入上面那些数据。你看,继承的思想也被用了。这下就可以极大地方便了录入,比如雷锋塔位于杭州,杭州位于浙江,浙江属于江南,一个人到过雷锋塔就等于到了父级的那些地点,但到了浙江不一定到过杭州……
  挑了四本比较短的书开始录入,花了半个月,算是录完了四本,进行了一番测试,感觉还可以,想要的功能都实现了。不过问题还是数据录入不够完全,类似于第一题第二题这种很难用数据精确定义并查询的条件还是支持不好,但是对于第四道题这种纯数据流的问题应该还是可以比较容易解决的。虽然不完美但也算是解决了我的需求,对于那种很难精确描述的条件我就直接略过,根据其它条件推出的结果由人来判断略过的条件,由于已经被计算机筛选过了,所以剩下需要人来判的几乎没几个数据。金庸的书一共370多回,现在才录完30多回,路漫漫其修远兮……
  也许有人说,已经知道怎么解决这个问题了,那么就不用再花那么多时间录数据了吧,浪费时间,也没有意义。我总觉得,全部录完,那么这是一个完整的工程,不录完,这个东西啥也不是,没有任何意义。而且在不断地使用过程中肯定还会发现各种各样的需要改进的地方,不亲自做完是无法发现的,实际上我就是在不断地做的过程中慢慢完善自己的这个系统的。很多人学习东西也是这样,看了一眼,觉得我懂了,没必要亲自做一遍,其实当真正做的时候才发现会有很多问题。
  目前如果仅是为了输入条件解决问题,那么完全可以不用定义录入的数据类型(指的是人物、武器、地点等),但为了收集数据方便,比如我将来可能需要金庸书中所有的兵器列表,那么强制类型就会比较方便获得,一个SQL语句就搞定的,而且强类型也确实有助于防止输入错误数据。这一点其实与C#和PHP也很类似,PHP中很自由,但如果不小心把一个变量的名字输错了,程序照常运行,不会提示错误,但结果却不是预期的。
  
  唉,有个梦想,要是能做一个程序,自动把小说读出来,然后自动搜集有用的信息,按要求录入数据库该多好……那才是真正的人工智能。这么一边看书一边录入数据真挺累啊。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics