设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
楼主: DeathKing
打印 上一主题 下一主题

[讨论] Ruby/RGSS Tips 每日一更 [技术区的版聊帖?]

  [复制链接]

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
82
发表于 2011-2-8 05:47:52 | 只看该作者
本帖最后由 苏小脉 于 2011-4-2 19:40 编辑

要注意区分传统的捕获组引用和 79、80 楼所提到的引用方式的区别。79、80 楼使用的 \g<name> 语法,是将捕获组当作是一个子程序(subroutine)在用,就好比定义了一个函数,每次去调用它都会进行新的匹配。

传统的引用是这样的:
  1. str = 'a1:13:9f:dd:32:72'
  2. regex = /(\h)\1/
  3. p str[regex] # => "dd"
复制代码
`(\h)' 是一个捕获组,而后面跟着一个后向引用,这不会再次进行 `\h' 的匹配,而是拿捕获组匹配后的结果来进行匹配。所以,这个表达式实际上匹配的是两个相同的十六进制数位,那么在 str 中,只有 `dd' 是匹配的。

由于 1.9 支持命名捕获组,那么想要依然使用以上方式的后向引用自然也需要扩展语法。

  1. str = 'a1:13:9f:dd:32:72'
  2. regex = /(?<hex>\h)\k<hex>/
  3. p str[regex] # => "dd"
复制代码
而如果把 \k 替换为 \g,那就和 79、80 楼一样,重新对“子程序”进行了求值,所以可以匹配任意两个十六进制数位。
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
81
发表于 2011-2-7 06:17:43 | 只看该作者
80 楼提到了递归性质的正则表达式。Oniguruma 的递归仍然是通过命名捕获组完成的,这里就做一个演示。

我们有一段很简单的代码字符串:
  1. str = <<STRING_ENDS_HERE
  2. void main() {
  3.     int i;
  4.     if (1) {
  5.         int a;
  6.         if (10) {
  7.             int var;
  8.             if (1) {
  9.                 int i;
  10.             }
  11.         }
  12.         int b;
  13.     }
  14. }
  15. STRING_ENDS_HERE
复制代码
而我们希望通过正则表达式来取出语法正确的 if 语句代码块。
  1. regex = /

  2. (?<if>
  3.     if\s*\(\d*?\)\s*{\s*
  4.         (?:int\s*[a-zA-Z]\w*?;\s*|\g<if>)*
  5.     \s*}\s*
  6. )

  7. /xm
复制代码
这里我们限制了一个语法正确的 if 语句为:
if,跟着可能的空白,跟着 (,跟着一个数字,跟着 ),跟着 {,跟着可能的空白,跟着另一个语法正确的 if 语句,或者跟着一个 int 变量的声明,最后跟着 } 和可能的空白。那么我们:

  1. p str[regex]
复制代码
就能捕获到最外围的 if 语句。当 if 后面少了 ( 或 ),或是少了 { 或 },抑或是 int 变量声明少了 ;,就是一个语法错误,那么就不匹配我们上面所写的 regex。比如,我们故意去掉 int var 后面的分号:

  1. str = <<STRING_ENDS_HERE
  2. void main() {
  3.     int i;
  4.     if (1) {
  5.         int a;
  6.         if (10) {
  7.             int var
  8.             if (1) {
  9.                 int i;
  10.             }
  11.         }
  12.         int b;
  13.     }
  14. }
  15. STRING_ENDS_HERE
复制代码
p str[regex] 则是:
"if (1) {\n                int i;\n            }\n        "

因为外围的 if 内部有语法错误,所以只有最里层的 if 被匹配了。

这只是一个很简陋的语法匹配,但演示了如何使用递归匹配。
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
80
发表于 2011-2-6 13:48:59 | 只看该作者
Ruby 1.9 使用了一个效率更高、功能更强大的正则表达式引擎——Oniguruma(鬼車)。相比于 Ruby 1.8 的正则表达式引擎来说,Oniguruma 所能描述的再也不仅仅是正则语言了。这其中的一个重要因素是由于递归表达式的出现——正则语言的一大特点就是没有递归。和大多数现代化的引擎一样,Oniguruma 甚至能用来描述上下文无关语言这样的形式语言。可是,“正则表达式”这个术语仍然被各种语言持续使用了下来。可能人们对正则表达式太熟悉了,熟悉到会觉得其名称改为“形式表达式”很别扭。

本帖主要介绍一个新功能:命名捕获组

对于能熟练使用正则表达式的朋友来说,捕获组并不陌生。未经转义的圆括号中的表达式就是一个捕获组,我们之后可以在表达式中引用这个捕获组。传统的引用方式是通过所谓的后向引用,即 \n 的形式,来引用在表达式中从左到右第 n 个出现的捕获组。

这种方式有很多弊端。首先,用户必须维护 1, 2, ..., n 这些数字。一旦发生两个捕获组换位的情况,就要把所有的它们的后向引用都改掉,这对于较长的表达式来说无疑是力气活。如果我们能给捕获组命名,那么即便它的位置变了,名字却不会变,所以无须修改其引用处。

其次,对于代码阅读者来说(把整个表达式背下来的除外),一个数字只不过是一个密码,它本身并不能让读者直接回想起任何已知事物,读者只能从头开始阅读表达式,并按出现顺序找到对应的捕获组,这属于一个简单的破译过程。如果我们给捕获组命名,那么就可以通过一两个单词来简短地描述该捕获组所接受的语言。比如:将一个捕获组命名为“alphanum”,那么读者大概就能猜到这个捕获组匹配的是“alphanumeric”的值,也就是字母或数字。

Oniguruma 命名捕获组是通过 (?<name>regex) 的语法来完成的。name 是捕获组的名称,regex 是捕获组内部的正则表达式。

  1. #coding: GBK

  2. regex = /(?<hex>[0-9a-fA-F])\+\g<hex>/
  3. str = '我们需要取出4+f这个子串!'
  4. p str[regex] # "4+f"
复制代码
如果我们在命名捕获组时配合正则表达式的“忽略空白符”选项(字面值后添加 `x' 后缀),以及在捕获组子表达式后添加 {0} 表示匹配 0 次,就可以大大地提高表达式的可读性。给一个比较复杂的例子,是我之前写的一个匹配电子邮件地址的雏形:
  1. #coding: GBK

  2. # local@domain

  3. $pattern = %r<

  4. # Define subpatterns.
  5. (?<local>       \g<local_t>|\g<quoted>              ) {0}
  6. (?<local_t>     \g<local_a>+(?:\.\g<local_a>+)*     ) {0}
  7. (?<local_a>     \g<alphanum>|[!\#$\%&'*/=?^_`{|}~-] ) {0}
  8. (?<alphanum>    [a-zA-Z0-9]                         ) {0}
  9. (?<quoted>      ".*"                                ) {0}
  10. (?<domain>      \g<hostname>|\[\g<ip_addr>\]        ) {0}
  11. (?<hostname>    \g<label>(?:\.\g<label>)*           ) {0}
  12. (?<label>       \g<alphanum>+(?:-+\g<alphanum>+)*   ) {0}
  13. (?<ip_addr>     \g<zero_255>(?:\.\g<zero_255>){3}   ) {0}
  14. (?<zero_255>    \d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]   ) {0}

  15. # The root pattern is ...
  16. \g<local>@\g<domain>

  17. >x
复制代码
这里,由于使用的是 %r 来构建正则表达式,所以其内部可以直接写普通的注释。每个子表达式后面跟上 {0},表示这里仅仅是定义并命名一个捕获组,并不参与实际匹配。再加上最后的 `x' 后缀,表达式中的空白都会被忽略,所以可以对表达式进行缩进。这个例子就大量使用了命名捕获组,将匹配不同字符串片段的表达式分划到了不同的子表达式。这在便于维护的同时也提高了可读性,和代码模块化是一个道理。

http://szsu.wordpress.com/2010/11/13/regex_email_addr/
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv2.观梦者

傻♂逼

梦石
0
星屑
374
在线时间
1606 小时
注册时间
2007-3-13
帖子
6562

烫烫烫开拓者

79
发表于 2011-1-27 00:13:07 | 只看该作者
哎呀,蛋疼什么的最有爱了
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1135
在线时间
1564 小时
注册时间
2008-7-30
帖子
4418

贵宾

78
 楼主| 发表于 2011-1-24 20:30:04 | 只看该作者
偷个小懒:
Ruby排序算法收集:http://www.javaeye.com/topic/746562

不过个人觉得,个个类的sort方法不然各具特色,也是最方便最好用的了。

点评

其实用 Ruby 写排序的意义只停留在学术研究和演示层面上,实用性几乎为零 o.o  发表于 2011-1-25 01:26

See FScript Here:https://github.com/DeathKing/fscript
潜心编写URG3中。
所有对URG3的疑问和勘误或者建议,请移步至发布页面。
欢迎萌妹纸催更
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
77
发表于 2011-1-22 14:33:33 | 只看该作者
(承接 76 楼)

instance_eval 和 class_eval 的区别,也是在于对 default definee 的处理上。由于 class_eval 是 Module 类的实例方法,所以下面进行测试使用的接收者是一个兼容 Module 类型的实例(其运行时类型是 Class 类型),否则 class_eval 这个方法压根儿没法用。另:module_eval 只是 class_eval 的一个别名,但如果按照 Ruby 编程的习俗,我们通常会对一个可能是 Module 类型的对象则使用 module_eval,而其余场合则使用 class_eval。

其实你需要记住的就一句话:在 instance_eval 的块内部,self 会指向 instance_eval 的接收者,而 default definee 会成为接收者的 eigenclass(关于 eigenclass 可以参考 73 楼);在 class_eval 的块内部,self 和 default definee 都会成为 class_eval 的接收者。
  1. class Klass
  2. end

  3. Klass.instance_eval do
  4.     def class_method
  5.         p :class_method
  6.     end
  7.     def self.another_class_method
  8.         p :another_class_method
  9.     end
  10. end

  11. Klass.class_eval do
  12.     def instance_method
  13.         p :instance_method
  14.     end
  15.     def self.yet_another_class_method
  16.         p :yet_another_class_method
  17.     end
  18. end

  19. k = Klass.new
  20. k.instance_method               # => :instance_method
  21. Klass.class_method              # => :class_method
  22. Klass.another_class_method      # => :another_class_method
  23. Klass.yet_another_class_method  # => :yet_another_class_method
复制代码
我们一步一步地来看。

首先,我们在 instance_eval 的块内部定义了一个普通方法 class_method,这是实例方法的定义方式。什么?你问我为什么把它命名为“类方法”(class_method)?别忘了之前的那句加粗的话。在 instance_eval 的块内部,default definee 会成为 instance_eval 的接收者的 eigenclass。此处,instance_eval 的接收者是 Klass,所以我们定义的 class_method 方法实际上就成为了 Klass 的 eigenclass 的实例方法,这用以前的术语来说,就是定义了 Klass 这个对象(Klass 是 Class 类型)的单例方法(singleton method)。而我们知道,Class 对象的单例方法,实际上就是传统意义上的“类方法”,也就类似于 Java、C# 中的类静态方法。所以此处的 class_method 就成为了 Klass 类的类方法,在最后的输出语句中,可以直接通过 Klass 这个 Class 对象来调用 class_method 方法,而不需要 Klass 的实例。

之后我们又定义了一个 another_class_method,这次是用定义单例方法的方式定义的,因为在方法名前多了一个接收者 self。之前说了 instance_eval 的块内部的 self 会指向 instance_eval 的接收者,也就是这里的 Klass,所以 self 实际上就是 Klass 这个 Class 对象。我们对 Klass 这个对象定义单例方法,那就相当于定义了 Klass 的 eigenclass 的实例方法,所以和上面定义 class_method 的上下文没有区别,都是 Klass 的 eigenclass,故而他们都是 Klass 的类方法。

接着我们进入了 class_eval,然后定义了一个 instance_method。再次回顾之前被加粗的那句话,我们就知道,class_eval 的块的内部的 default definee 会成为 class_eval 的接收者,在这里就是 Klass 本身,所以我们定义的 instance_method 就成为了 Klass 的实例方法。在最后的输出语句中,可以看到通过 Klass 的实例 k 调用 instance_method。

最后我们又通过单例方法定义的形式定义了一个 yet_another_class_method 方法。class_eval 的块内部的 self 仍然是指向 class_eval 的接收者,也就还是 Klass 本身,所以 self 仍然是指向 Klass。如此一来,这个方法定义又成了给 Klass 这个对象定义单例方法,所以和 class_method、another_class_method 一样都是 Klass 的类方法,故而命名为 yet_another_class_method。

所以,千万不要被 instance_eval 和 class_eval 的名字迷惑。在 Module#instance_eval 中定义的普通方法,反而会成为该 Module 的类方法;在 Module#class_eval 中定义的普通方法,反而会成为该 Module 的实例方法。
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
76
发表于 2011-1-21 12:27:23 | 只看该作者
本帖最后由 苏小脉 于 2011-1-21 12:28 编辑

这里有一篇 Yuki Sonoda (园田裕贵)的博文:http://yugui.jp/articles/846
其中介绍了三种隐式的上下文:self,klass,常量定义点(这个是我从英语直接翻译过来的)。

下面的概念是直接从该博文中摘抄下来的,如果你能很自在地读英语,那推荐看原文,其中包含了例子代码。

所谓 "klass",是指定义方法(不包括单例方法的定义)的默认(隐式)类,Yuki 称之为 "default definee"(默认被定义者)。比如,在任意处写下一个方法的定义(def foo; end),实际上是定义了某个类的实例方法,这个类就是这里说的 "klass"。Ruby 总是会保存这个类的一个引用,但官方的实现中并没有直接的方法获取这个类。

在 Ruby 顶层,定义一个方法,其 default definee 是 Object 类型。这个可以参考:http://szsu.wordpress.com/2009/11/07/top_level_object_kernel/

当你使用 class 的语法时,在 class ... end 内部会产生一个新的上下文,这时 self 和 default definee 都会指向这个类本身。所以在内部定义的方法就是该类的实例方法,这个大家都知道。

如果你已经在一个方法主体中,那么 self 是这个方法的接收者,而 default definee 则是语法上的方法定义外部的最近的那个类。
  1. class Klass
  2.     def foo
  3.         def bar
  4.         end
  5.     end
  6. end

  7. Klass.new.foo

  8. p Klass.instance_method :bar # => #<UnboundMethod: Klass#bar>
复制代码
这里 foo 也是 Klass 的实例方法。

当然,方法的定义还可以出现在其它地方,但无论在哪儿,都会有一个 default definee。比如 Yuki 给出的这个例子就是在处理方法参数默认值的时候定义的方法,其 default definee 仍然是语法上的外部的 Bar 类:
  1. class Bar; end  
  2. $bar = Bar.new  
  3. class Baz  
  4.   def $bar.hoge(a = (def fuga; end))  
  5.     def piyo; end  
  6.   end  
  7. end  
  8. $bar.hoge  
  9.   
  10. Baz.instance_method(:fuga)      #=> #<UnboundMethod: Baz#fuga>  
  11. Baz.instance_method(:piyo)      #=> #<UnboundMethod: Baz#piyo>  
  12.   
  13. $bar.method(:fuga)              # raises a NameError "undefined method `fuga' for class `Bar'"  
  14. $bar.method(:piyo)              # raises a NameError "undefined method `piyo' for class `Bar'"
复制代码
一句话:class 的定义改变了 default definee,而方法定义不会改变。

博文后半部分是关于 instance_eval 和 class_eval 在操纵 self 和 default definee 上的不同点,这个留待下一贴补完。

-----------------------------------------------------------

一些关于 Yuki Sonoda 的资料:
Yuki Sonoda,“园田裕贵”,是 CRuby 核心开发团队的成员之一,Ruby 社员大多称她为 “Yugui”。她目前负责管理 Ruby 1.9 的发布。
她的个人主页:http://yugui.jp/about/author
Linkedin 主页:http://jp.linkedin.com/in/yugui
Facebook 主页:http://www.facebook.com/yugui
Twitter 主页:http://twitter.com/#!/yugui
一张照片,左:Matz(松本行弘),中:Yuki,右:笹田耕一(Sasada Koichi),此人便是 Ruby 虚拟机 YARV 的作者。
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1135
在线时间
1564 小时
注册时间
2008-7-30
帖子
4418

贵宾

75
 楼主| 发表于 2011-1-20 09:56:05 | 只看该作者
和其他的语言不一样,Ruby的常量(Constant)可以在初始化后随意改变其置。考虑下面一段代码。


将其保存为 test.rb 然后运行它。




在默认情况下运行它,程序对你说:嗨,老兄,你早已经初始化好了Apple这个常量,当然,他只是警告你,而程序继续运行。我们试试将安全等级设置低一点。ruby.exe 支持一个参数叫做安全等级,表示为 -W[0|1|2]。由于默认是第二等级,verbose,因此我们只需要测试0和1的情况。

当安全等级为0时,他就不发牢骚了。

RM的RGSS/RGSS2系统也不会发牢骚,这就是FSL脚本可以利用常量随时更改配置的原因了。


点评

改常量不是好习惯。  发表于 2011-1-21 11:26
补充:更改常量只能在顶层或类/模块定义中,不能在方法中  发表于 2011-1-20 12:59

See FScript Here:https://github.com/DeathKing/fscript
潜心编写URG3中。
所有对URG3的疑问和勘误或者建议,请移步至发布页面。
欢迎萌妹纸催更
回复 支持 反对

使用道具 举报

Lv2.观梦者 (管理员)

八云紫的式神

梦石
0
星屑
589
在线时间
1243 小时
注册时间
2008-1-1
帖子
4282

烫烫烫

74
发表于 2011-1-19 08:22:33 | 只看该作者

单例类

以下引用紫苏在73楼的发言
我个人认为使用最广泛的“单例类”这个名称并不好,因为这很容易和著名的单例类设计模式混淆。单例类设计模式是在设计类的时候保证其实例只有一个


所谓著名的单例类设计模式是指实例只有一个,例如内建的TrueClass FalseClass NilClass是单例类
在Ruby中单例类可以这么实现
  1. class Singleton
  2.   class << self
  3.     alias old_new new
  4.     def new(*args)
  5.       if @instance #可以用?:运算符或者逻辑或运算符||写成一句
  6.         @instance
  7.       else
  8.         @instance = old_new(*args)
  9.       end
  10.     end
  11.   end
  12. end
复制代码
而有另一些时候,我们希望的不是这个类只能有唯一实例,而是希望这个类的实例不会重复
例如有一个Game_Actor类(可能跟RM的不完全相同),有名字和其他属性,我们希望不会出现重名的两个角色
  1. class << Game_Actor #在RM默认脚本基础上,已经有了这个类
  2.   @all = {}
  3.   alias old_new new
  4.   def new(name, *args)
  5.     @all[name] || old_new(name, *args) #用逻辑或运算符的示例
  6.   end
  7. end
复制代码
这样第一次Game_Actor.new("拉尔夫")会生成一个新角色,第二次时直接返回第一次的
如果要用id标识唯一性,那么@all用数组即可
rm for linux(wine)制作中,期待夏娜SAMA能实现到webrm上
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
73
发表于 2011-1-18 10:02:31 | 只看该作者
本帖最后由 紫苏 于 2011-1-18 10:11 编辑
  1. obj = Object.new
  2. class << obj
  3. end
复制代码
你知道这段代码中的 class 是什么吗?这段代码中的 class 定义的实际上就是 obj 这个对象的“单例类”。本帖主要是针对对 Ruby 单例类已经有了正确概念的朋友,否则可能不知所云。

Ruby 社区就 Ruby 的“单例类”这个术语的命名曾有过许多争议。至今,Ruby 的创造者 Matz 也没有宣布一个正式的、官方的的名称。在大部分书籍,包括《The Ruby Programming Language》中,都使用了“单例类”(Singleton class)这个术语,故而“单例类”也就成了 Ruby 的“单例类”概念的 de facto(约定俗成的)命名。除了“单例类”这个命名以外,主要还有另外两种命名:“元类”(Metaclass)和“特征类”(Eigenclass)。

“元类”实际上在更早的时候就出现了。Smalltalk、Java、C#、Python 中都有被称为“元类”的概念。然而,这些语言中的元类和 Ruby 的“单例类”颇有不同。传统意义上的元类,说白了,就是类的类,是用来描述一种类的类型,其实例就是一个类。这相当于 Ruby 中的 Class 类——所有对象的类的类型都是这个 Class 类的实例。这当然和“单例类”风马牛不相及,所以以“元类”来命名 Ruby “单例类”的概念并不合适。

“特征类”是一个很好的命名。这个“特征”和线性代数中“特征值”、“特征向量”的“特征”是一个意思。“特征值”、“特征向量”的名称是由德国数学家 David Hilbert (大卫·希尔伯特)在 1904 年使用并得以流传的,德语单词“Eigen”本意为“自己的”。Eigenclass,也就意味着“自己的类”,用于 Ruby 的“单例类”概念之上十分贴切,因为“单例类”实际上就是一个对象独有的类。

我个人认为使用最广泛的“单例类”这个名称并不好,因为这很容易和著名的单例类设计模式混淆。单例类设计模式是在设计类的时候保证其实例只有一个,而 Ruby 的“单例类”却是先有一个可有多个实例的类型,比如本帖开头的 Object 类型,而后对其某个实例进行“单例的类”(及其方法)的创建。这么一想,是不是觉得“特征类”更加贴切了(特别是对于专精于线性代数的你)?

Eigenclass 这个术语应该算是 Ruby 社员的发明创造。上文提到的“特征类”是我直接从线性代数中 eigenvalue、eigenvector 取来的,也并非是正式的、官方的翻译。就目前来说,使用 eigenclass 的英语原型是最不会引起歧义的。

关于 eigenclass 的命名,更详细的讨论参见:http://www.ruby-forum.com/topic/571597
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-9-21 01:39

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表