过年前的时候,看到同事HQJ在手机上玩猜成语,前两天,我也下载了一个《疯狂看图猜成语2》,其实最开始看到这个游戏时候,就习惯性的想作弊了。
玩游戏我一直喜欢修改的,比如FPE啦,金山游侠啦,对我来说都是玩游戏必备的工具。
这个看图猜成语,就是给一幅图,下面是24个汉字,从里面选4个字,作为符合图内容的成语答案。
ok,现在让我们go。
系统:Windows XP sp2
ruby:1.9.2p180
编辑器:SciTE 1.73
工作环境,无法上网,所以系统旧,ruby旧,大家先承担些,后面没问题了,再转Ubuntu(Ubuntu在u盘上安装的系统,比windows下还慢)。
1.开始的时候,很简单的想法,就是从里面随便抽汉字,组成4个字的词,看里面是否有成语,这个是最简单的。
我喜欢使用ruby,纯属个人爱好,代码也许很烂,看的人多担待些。
在Windows下有个问题,操作系统的编码是gbk,但是ruby1.9底层是utf-8,所以总是需要转码。
#coding: UTF-8 require 'iconv' words_array = %w|无 黑 用 如 小 比 伦 材 脚 纸 四 表 两 得 一 字 天 大 朝 白 与 举 一 里| #成语游戏的备选字库 words = words_array.map {|x| Iconv.conv 'gbk', 'utf-8', x} #utf-8转gbk idioms = words.permutation(4).to_a #每4个字分组 idioms.each {|x| puts x.join} #分组合并,就是四字成语
功能是有了,但是这个排列组合的数量巨大,人眼很难找到成语,24个字4字排列总共255024个,眼神好能找到符合的成语,不好就很难了。
2.数量巨大的结果不实用,那么就减少。一个想法是从网上弄成语词库,从里面选就可以了。
去网上找成语词库,真的不怎么好找,都是带解释什么的乱七八糟的,最后找到一个叫“成语词典.txt”的文件,里面的内容是类似“【束教管闻】谓学识浅陋,见闻不广。”这样的内容内容,就他了,很容易通过正则取出【】中间的内容为成语词典。
#coding: UTF-8 require 'iconv' s1 = Iconv.conv 'gbk','utf-8',"【" #烦人的转码 s2 = Iconv.conv 'gbk','utf-8',"】" File.open('成语.txt','w') do |wfile| File.open('成语词典.txt','r') do |rfile| rfile.each do |line| line =~ /#{s1}(.*)#{s2}/ wfile << $1 << " " #没有 就会连一起了 end end end
输出为“成语.txt”,最后有几个错误的词,手工给删除了,最后是23594条成语。
3.有字库排列,有成语词典,第一个想法当然是排列对照,简单的就是两个循环。其实我最开始就是这么做的,比较丢人啊,就不写了,速度很慢,你想想255024*23594的循环。后来就问了HQJ,程序员就明白的多,告诉我既然成语词典少,就从成语词典过滤,先比较第一个字,这样一次就只有几十上百个词语选择,接着再去字库中选择。
按照这个思路,写了几版,最终的定型大概是这样的。
#coding: UTF-8 require 'iconv' words = %w|无 黑 用 如 小 比 伦 材 脚 纸 四 表 两 得 一 字 天 大 朝 白 与 举 一 里| idioms_array = Array.new idioms_regexp = Array.new File.open('成语.txt','r') do |file| file.each do |line| idioms_array << line.chomp #把成语词典折腾出来,作为array数组 end end words_g = words.map {|x| Iconv.conv 'gbk', 'utf-8',x}.uniq #转码,另外把重复的字去除,后面用不到 words_g.each {|word| idioms_array.map {|x| idioms_regexp << x if x =~ /^#{word}/} #循环,从成语词典中匹配第一个字相同的记录 idioms_regexp.map {|idiom| idioms_regexp = [] #这里坑了我很久,一直有重复的,后来发现因为里面有数据,清空就好了 puts idiom if words_g.include?(idiom[1]) && words_g.include?(idiom[2]) && words_g.include?(idiom[3]) #检查成语的第2、3、4个字,是否在词库中,都在那就ok了 } }
4.上面的想法都太直白了,还是按照ruby的习惯,尽可能的少的代码,把循环等去除一下。
重构code:
#coding: UTF-8 require 'iconv' require 'benchmark' words = %w|无 黑 用 如 小 比 伦 材 脚 纸 四 表 两 得 一 字 天 大 朝 白 与 举 一 里| idioms_array = Array.new File.open('成语.txt','r') do |file| file.each do |line| idioms_array << line.chomp #把成语词典折腾出来,作为array数组 end end words_g = words.map {|x| Iconv.conv 'gbk', 'utf-8',x}.uniq #转码,另外把重复的字去除,后面用不到 Benchmark.bm do |bm| bm.report { words_g.each do |word| idioms_array.map do |idiom| puts idiom if idiom =~ /^#{word}/ and words_g.include?(idiom[1]) and words_g.include?(idiom[2]) and words_g.include?(idiom[3]) end end } end
我的烂机器上,大概6.8秒。重构与否其实速度没有变化。本来还序列化的了,但是和直接读txt一样,所以就没有用。
5.Ubuntu下使用下面的代码。因为我的Ubuntu性能更烂,所以需要近10秒。而且Ubuntu下,需要把成语.txt转换为utf-8格式,所以我用了“成语u.txt”。
Ubuntu下代码:
#coding: UTF-8 require 'iconv' require 'benchmark' words = %w|无 黑 用 如 小 比 伦 材 脚 纸 四 表 两 得 一 字 天 大 朝 白 与 举 一 里| idioms_array = Array.new File.open('成语u.txt','r') do |file| file.each do |line| idioms_array << line.chomp #把成语词典折腾出来,作为array数组 end end Benchmark.bm do |bm| bm.report { words.uniq.each do |word| idioms_array.map do |idiom| puts idiom if idiom =~ /^#{word}/ and words.include?(idiom[1]) and words.include?(idiom[2]) and words.include?(idiom[3]) end end } end
6.看到这里,大家发现了什么没有,其实上面说的都是在扯淡,不过恐怕没有几个人有耐心看到这里的吧。我的处理思想是有问题的,其实完全想多了。最简单的就是,从成语文件中读取,判断是否在字库中存在即可,下面的代码是秒速的,不到0.2秒结果就出来了,快2个数量级的差别。
Windows版本:
#coding: UTF-8 require 'iconv' require 'benchmark' words = %w|无 黑 用 如 小 比 伦 材 脚 纸 四 表 两 得 一 字 天 大 朝 白 与 举 一 里| words_g = words.map {|x| Iconv.conv 'gbk', 'utf-8',x}.uniq #转码,另外把重复的字去除,后面用不到 Benchmark.bm do |bm| bm.report { File.open('成语.txt','r') do |file| file.each do |line| puts line if words_g.include?(line[0]) and words_g.include?(line[1]) and words_g.include?(line[2]) and words_g.include?(line[3]) end end } end
Ubuntu版本:
#coding: UTF-8 require 'benchmark' words = %w|无 黑 用 如 小 比 伦 材 脚 纸 四 表 两 得 一 字 天 大 朝 白 与 举 一 里| Benchmark.bm do |bm| bm.report { File.open('成语u.txt','r') do |file| file.each do |line| puts line if words.include?(line[0]) and words.include?(line[1]) and words.include?(line[2]) and words.include?(line[3]) end end } end
7.大家使用的时候,可以把benchmark的部分屏蔽,反正就是要个结果而已。还有,因为我用的成语词典库不大,所以有很多查不到的,比如286关“胜友如云”,还有“置之脑后”"不可造次""巧用天时""寸土如金"“自始至终”“串通一气”等,遇上这些,大家还是用金币吧,不过应该不是很多。
代码我放到下面的链接中了,只放最终的代码。
感谢能看到这里的朋友。