作者:gcbeen

日期:2013年11月08日

一、用正则表达式进行模式匹配

  • =~操作符,一个操作数必须是正则表达式,另一个必须是字符串。
    
    pattern = /Ruby?/i
    pattern =~ "backrub"    # => 4
    "rub ruby" =~ pattern   # => 0
    pattern =~ "r"          # => nil
    # 模式匹配的其他一些信息
    "hello" =~ /e\w{2}/
    $~.string           # => "hello"
    $~.to_s             # => "ell"
    $~.pre_match        # => "h"
    $~.post_match       # => "o"

$~是一个特殊的线程局部和方法局部变量,它在两个并发运行的线程中的值是不同的。并且,使用了=~操作符的方法不会修改调用者方法中的$~变量值。可以使用面向对象方式引用(Regexp.last_match)。

  • MatchData对象
    pattern = /(Ruby|Perl)(\s+)(rocks|sucks)!/
    text = "Ruby\trocks!"
    pattern =~ text             # => 0
    data = Regexp.last_match    # => 匹配详细
    data.size    # => 4
    data[0]      # => "Ruby\trocks"
    data[1]      # => "Ruby"
    data[2]      # => "\t"
    data[3]      # => "rocks"
    data[1, 2]   # => ["Ruby", "\t"]
    data[1..3]   # => ["Ruby", "\t", "rocks"]
    data.values_at(1, 3)    # => ["Ruby", "rocks"]
    data.captures           # => ["Ruby", "\t", "rocks"]
    
    Regexp.last_match(3)    # => "rocks"
    
    # 开始和结束的匹配
    
    data.begin(0)     # => 0
    data.begin(2)     # => 4
    data.end(2)       # => 5
    data.offset(3)    # => [5, 10]
    # 在Ruby 1.9中的有名捕获。
    pattern = /(?<lang>Ruby|Perl) (?<ver>\d(\.\d)+) (?<review>rocks|sucks)!/
    if (pattern =~ "Ruby 1.9.1 rocks!")
      $~[:lang]         # => "Ruby"
      $~[:ver]          # => "1.9.1"
      $~["review"]      # => "rocks"
      $~.offset(:ver)   # => [5, 10]
    end
    
    pattern.names           # => ["lang", "ver", "review"]
    pattern.named_captures  # => { "lang" => [1], "ver" => [2], "review" => [3] }

有名匹配和局部变量

在Ruby 1.9中,如果一个正则表达式字面量包含有名捕获并且出现在=~操作符的左侧,那么捕获分组名将成为局部变量,匹配的文本放入这些变量中。如果匹配失败这些变量将被赋值为nil。

    if /(?\w+) (?\d+\.(\d+)+) (?\w+)/ =~ "Ruby 1.9 rules!"
      lang      # => "Ruby"
      ver       # => "1.9"
      review    # => "rules"
    end

这值针对正则表达式字面量有效。如果一个匹配模式被存储在变量或常量中,或是方法的返回值,或出现在操作符的右侧,那么=~操作符不会进行这种操作。

match方法:返回MatchData对象

    if data = pattern.match(text)
      handle_match(data)
    end

    # 在Ruby1.9中,对match的调用关联一个代码块。
    pattern.match(text) { |data| handle_match(data) }

用于匹配数据的全局变量

    # 特殊的正则表达式变量
    $~      # 等价于 Regexp.last_match
    $&      # 等价于 Regexp.last_match[0]
    $`      # 等价于 Regexp.last_match.pre_match
    $'      #'等价于 Regexp.last_match.post_match
    $1      # 等价于 Regexp.last_match[1]
    $2, etc # 等价于 Regexp.last_match[2], etc
    $+      # 等价于 Regexp.last_match[-1]

$~和这些从它继承而来的变量都是线程局部且方法局部的,两个Ruby线程可以同时进行模式匹配,无需担心相互干扰。

  • 用字符串进行模式匹配
    "ruby123"[/\d+/]                 # "123"
    "ruby123"[/([a-z]+) (\d+)/, 1]   # "ruby"
    "ruby123"[/([a-z]+) (\d+)/, 2]   # "123"

slice方法是字符串索引操作符[]的同义词方法。slice!方法返回值与slice方法相同,并且将从字符串中删除返回的匹配字符串。

    r = "ruby123"
    r.slice!(/\d+/)  # => "123", r:"ruby"
    s = "one, two, three"
    s.split               # ["one,", "two,", "three,"]
    s.split(", ")         # ["one", "two", "three"]
    s.split(/\s*,\s*/)    # ["one", "two", "three"]

index方法可以搜索一个字符、子字符串或模式,并返回起始位置。如果给定一个Regexp对象作参数,则该方法的行为与=~操作符相似,不过它还可以用第二个参数指定搜索的起始位置,这样就可以在搜索完第一个之后,继续搜索下一个匹配值。

    text = "hello world"
    pattern = /l/
    first = text.index(pattern)
    n = Regexp.last_match.end(0)
    second = text.index(pattern, n)
    last = text.rindex(pattern)

搜索并替换(sub,gsub,sub!,gsub!) 最重要的使用正则表达式的String方法是sub(用于替换)和gsub(用于全局替换),以及它们的变体sub!和gsub!方法,如果发生替换,返回修改的字符,否则返回nil。

    phone = gets
    phone.sub!(/#.*$/, "")
    phone.gsub!(/\D/, "")
    
    text.gsub!("rails", "Rails")
    
    text.gsub!(/\brails\b/, "Rails")
    
    # 使用匹配文本(\0),使用匹配的第一个字串(\1)
    text.gsub(/\bruby\b/i, '<b>\0</b>')
    
    # 插入将在字符串字面量中被执行,这时字符串没有被传递给gsub方法,插入这个动作发生在模式匹配发生之前。$&变量要么没定义,要么是前次匹配所遗留的值。
    text.gsub(/\bruby\b/i, "<b>#{$&}</b>")
    
    re = /(?<quote>['"]) (?<body>[^"']*)\k<quote>/
    puts "These are 'quotes'".gsub(re, '\k<body>')

替换字符串也可以引用不在捕获分组中的文本,可以用&、`、\'和+来替换值为$&、$`、$'和$+的文本。

动态计算替换字符串

    text = "RUBY Java perl PyThOn"
    lang = /ruby|java|perl|python/i
    text.gsub!(lang) {|l| l.capitalize}
    pattern = /(['"])([^\1]*)\1/  #'
    text.gsub!(pattern) do
      if ($1 == '"')
        "'#{$2}'"
      else
        "\"#{$2}\""
      end
    end

正则表达式编码

在Ruby 1.9中,Regexp对象有一个与字符串类似的encoding方法。可以用修饰符显示指定编码:

    u表示UTF-8编码
    s表示SJIS编码
    e表示EUC-JP编码
    n表示无编码

还可以在正则表达式中用\u转义字符显示指定UTF-8编码。如果不显示指定编码,则使用源程序编码,如果正则表达式中所有字符都是ASCII编码,那么即使源程序编码是ASCII编码的超集,也使用ASCII编码。

如果试图匹配的模式与文本编码不兼容,在Ruby 1.9中会抛出一个异常。如果Regexp对象的编码不是ASCII编码,fixed_encoding?方法会返回true。如果fixed_encoding?返回false,那么可以安全地使用这个模式来匹配编码为ASCII或ASCII超集的文本。

二、数字和数学运算

  • 数字相关的方法
    # 通用方法
    0.zero?      # => true
    1.0.zero?    # => false
    0.0.nonzero? # => nil(像false一样使用)
    1.nonzero?   # => 1(像true一样工作)
    1.integer?   # => true
    1.0.integer? # => false
    1.scalar?    # => false:不是复数
    1.0.scalar?  # => false:不是复数
    Complex(1, 2).scalar?  # => true:复数
    # 整数方法
    0.even?  # => true
    0.odd?   # => false
    # 浮点数方法
    ZERO, INF, NAN = 1.0/0.0, 0.0/0.0
    
    # 有限的数字
    ZERO.finite?  # => true
    INF.finite?   # => false
    NAN.finite?   # => false
    
    # 无限的数字
    ZERO.infinite?  # => nil
    INF.infinite?   # => 1
    -INF.infinite?  # => -1
    NAN.infinite?   # => nil
    
    # 非数字
    ZERO.nan? # => false
    INF.nan?  # => false
    NAN.nan?  # => true
    # 取整数方法
    1.1.ceil   # => 2
    -1.1.ceil  # => -1
    
    1.9.floor  # => 1
    -1.9.floor # => -2
    
    1.1.round   # => 1
    0.5round    # => 1
    -0.5.round  # => -1
    
    1.1.truncate # => 1
    -1.1.to_i    # => -1 truncate同义词
    # 浮点数的方法
    
    -2.0.abs      # => 2.0
    -2.0 <=> 0.0  # => -1
    
    # 常量
    Float::MAX     # => 1.79769313486232e+308
    Float::MIN     # => 2.2250738585072e-308
    Float::EPSILON # => 2.22044604925031e-16

三、Math模块

    # 常量
    Math::PI
    Math::E
    
    # 方根
    Math.sqrt(25.0)    # => 5.0
    27.0 ** (1.0/3.0)  # => 3.0
    
    # 对数
    Math.log10(100.0)        # => 2.0
    Math.log(Math::E ** 3)   # => 3.0
    Math.log2(8)     # => 3.0
    Math.log(16, 4)  # => 2.0
    Math.exp(2)      # => 7.38905609893065  和Math::E ** 2一样
    # 三角函数
    include Math
    sin(PI/2)     # => 1.0
    cos(0)        # => 1.0
    tan(PI/4)     # => 1.0
    asin(1.0)/PI  # => 0.5
    sinh(0)       # => 0.0
    asinh(1.0)    # => 0.0
    theta = atan2(y, x)   # 
    r = hypot(x, y)       # 

    f, e = frexp(1024.0)  # => [0.5, 11]
    x = ldexp(f, e)       # => 1024: x = f * 2 ** e

    erf(0.0)   # => 0.0
    erfc(0.0)  # => 1.0

四、数字运算

BigDecimal替代Float。BigDecimal对象的有效数字可以任意长,并且大小不受限制。在进行计算时,它们可以提供比舍入方式更好的精度控制。

    require "bigdecimal"
    dime = BigDecimal("0.1")
    4 * dime - 3 * dime == dime  # => true
    4 * 0.1 - 3 * 0.1 == 0.1     # => false
    
    BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_EVEN)
    BigDecimal.limit(20)
    principal = BigDecimal("200000")
    apr = BigDecimal("6.5")
    years = 30
    payments = years * 12
    interest = apr/100/12
    x = (interest + 1) ** payments
    monthly = (principal * interest * x) / (x -1)
    monthly = monthly.round(2)
    monthly = monthly.to_s("f")

五、复数

    require 'complex'
    c = Complex(0.5, -0.2)     # => 0.5 - 0.2i
    z = Complex.new(0.0, 0.0) 
    10.times { z = z * z + c}
    magnitude = z.abs
    x = Math.sin(z)
    Math.sqrt(-1.0).to_s           # => "1.0i"
    Math.sqrt(-1.0) == Complex::I  # => true

六、实数

标准库中的Rational类表示实数(两个整数之商),mathn库重定义了整数除法,用于创建实数。mathn还做了许多工作用于统一Ruby的算术运算,并使Integer、Rational和Complex类协同工作。

    require 'rational'
    penny = Rational(1, 100)  # => "1/100"
    require 'mathn'
    nickel = 5 / 100
    dime = 10 / 100
    quarter = 1 / 4
    change = 2 * quarter + 3 * pnny  # Rational result:53/100
    (1/2 * 1/3).to_s                 # "1/6"

七、向量和矩阵

matrix库定义了Matrix和Vector类,分别代表数字矩阵和向量,以及对它们进行算术运算的操作符。

    require 'matrix'
    
    unit = Vector[1, 1]
    
    identity = Matrix.identity(2)  # 2x2 matrix
    identity * unit == unit        # true
    
    sx, sy = 2.0, 3.0
    scale = Matrix[[sx, 0], [0, sy]]
    scale * unit                     # [2.0, 3.0]
    
    theta = Math::PI/2
    rotate = Matrix[[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]
    
    rotate * unit        # [-1.0, 1.0]
    scale * (rotate * unit)  # [-2.0, 3.0]

八、随机数

在Ruby中,用全局函数Kernel.rand产生随机数。如果不带参数,它返回一个大于等于0.0而小于1.0的伪随机浮点数。

    rand      # => 0.4682060936325654
    rand      # => 0.6254414462555852
    rand(100) # => 81
    rand(100) # => 32

可重复的伪随机数序列

    srand(0)
    [rand(100), rand(100)]  # => [44, 47]
    srand(0)
    [rand(100), rand(100)]  # => [44, 47]

九、日期和时间

Time类用于表示日期和时间。它是操作系统提供的日期和时间的一个简单包装,因此,在某些系统上,这个类不能用于表示1790年之前或2038年之后的日期。在date库中的Date和DateTime类则没有这个限制。

    Time.now  # => 返回现在的时间
    Time.new  # => Time.now同义词
    Time.local(2007, 7, 8)         # July 8, 2007
    Time.local(2007, 7, 8, 9, 10)  # July 8, 2007, 09:10am, local time
    Time.utc(2007, 7, 8, 9, 10)    # July 8, 2007, 09:10 UTC
    Time.gm(2007, 7, 8, 9, 10, 11) # July 8, 2007, 09:10:11 GMT (same as UTC)
    t = Time.utc(2000, 12, 31, 23, 59, 59, 999999)
    t.day   # => 31
    t.wday  # => 0(0代表星期天)
    t.yday  # => 366
    t.usec  # => 999999 微妙
    t.zone  # => "UTC"
    
    values = t.to_a  # => [59, 59, 23, 31, 12, 2000, 0, 366, false, "UTC"]
    
    values[5] += 1   
    Time.utc(*values) # => Mon Dec 31 23:59:59 UTC 2001
    
    # 时区
    t.zone        # => "UTC"
    t.utc?        # => true
    t.utc_offset  # => 0
    t.localtime   # 转换为本地时间
    t.zone        # "PST"
    t.utc?        # false
    t.utc_offset  # -28800
    t.gmtime      # 转换为utc
    t.getlocal    # 返回一个新的本地时间
    t.getutc      # 返回一个新的utc时间
    t.isdst       # => false  utc没有夏时制
    t.getlocal.isdst  # => false 没有夏时制
    
    # 星期函数
    t.sunday?  # => true
    t.monday?  # => false
    t.tuesday? # => false
    
    t.to_s   # => "Sun Dec 31 23:59:59 UTC 2000" Ruby 1.8
    t.to_s   # => "2000-12-31 23:59:59 UTC"      Ruby 1.9
    t.ctime  # => "Sun Dec 31 23:59:59 2000"
    
    # 格式化时间
    t.strftime("%Y-%m-%d %H:%M:%S") # => "2000-12-31 23:59:59"
    t.strftime("%H:%M")             # => "23:59"
    t.strftime("%I:%M %P")          # => "11:59 PM" 12小时制
    
    t.strftime("%A, %B %d")    # => "Sunday, December 31"
    t.strftime("%a, %b %d %y") # => "Sun, Dec 31 00"
    t.strftime("%x")   # => "12/31/00"
    t.strftime("%X")   # => "23:59:59"
    t.strftime("%c")   # "Sun Dec 31 23:59:59 2000"
    require 'parsedate'
    include ParseDate
    
    datestring = "2001-01-01"
    values = parsedate(datestring)  # => [2001, 1, 1, nil, nil, nil, nil, nil]
    t = Time.local(*values)         # => Mon Jan 01 00:00:00 -0800 2001
    s = t.ctime                     # => "Mon Jan 1 00:00:00 2001"
    Time.local(*parsedate(s)) == t  # => true
    
    s = "2001-01-01 00:00:00-0500"  
    v = parsedate(s)       # => [2001, 1, 1, 0, 0, 0, "-500", nil]
    t = Time.local(*v)     # 
    
    now = Time.now
    past = now - 10     # 10秒之前
    future = now + 10   # 10秒之后
    future - now        # => 10
    
    past <=> future
    past < future
    now >= future
    now == now

时间工具方法

    class Numeric
      def milliseconds; self/1000.0; end
      def seconds; self; end
      def minutes; self*60; end
      def hours; self*60*60; end
      def days; self*60*60*24; end
      def weeks; self*60*60*24*7; end
    
      def to_milliseconds; self*1000; end
      def to_seconds; self; end
      def to_minutes; self/60.0; end
      def to_hours; self/(60*60.0); end
      def to_days; self/(60*60*24.0); end
      def to_weeks; self/(60*60*24*7.0); end
    end
    
    expires = now + 10.days   # => 10小时后
    expires - now             # => 864000.0 秒
    (expires - now).to_hours  # => 240.0 小时
    
    t = Time.now.to_i  # => 1384160886
    Time.at(t)         # => 2013-11-11 17:08:06 +0800
    t = Time.now.to_f  # => 1384160954.451236
    Time.at(0)         # => 1970-01-01 08:00:00 +0800

十、集合

Enumerable模块是一种混入模块,它在each迭代器的基础上实现了一组有用的方法。Array、Hash和Set类都包含了Enumerable模块。

一些可枚举的类有自然的枚举顺序,它们的each方法遵循这个顺序。数组按照索引的升序枚举各个元素,Range对象按照升序枚举元素,IO对象按照对应的文件和socket读入的文本顺序枚举每一行,在Ruby 1.9中,Hash和Set按照元素插入的顺序进行枚举,在Ruby 1.9前,这些类是随意的顺序进行枚举的。

许多Enumerable的方法返回一个处理后的可枚举集合或一个子集。通常情况下,如果一个Enumerable方法返回一个集合,这个集合是一个Array对象。Hash类覆盖了reject方法,返回一个Hash对象。

对集合进行迭代和转换

    (5..7).each { |x| print x }
    (5..7).each_with_index{ |x, i| print x, i }

在Ruby 1.9中,Enumerable定义了cycle迭代器,它重复迭代集合中的每个元素,无限循环一直到给定的代码块用break、return或者抛出异常来明确中止这个迭代。在对Enumerable对象进行第一次迭代时,cycle把所有元素存储在一个数组中,以后的迭代将在数组中进行。

each_slice和each_cons迭代器

    (1..10).each_slice(4) { |x| print x }  # => prints "[1, 2, 3, 4][5, 6, 7, 8][9, 10]"
    (1..5).each_cons(3) {|x| print x}      # => prints "[1, 2, 3][2, 3, 4][3, 4, 5]"
    data = [1, 2, 3, 4]
    roots = data.collect {|x| Math.sqrt(x) }
    words = %w[hello world]
    upper = words.map {|x| x.upcase}  # collect同义词
    (1..3).zip([4, 5, 6]) { |x| print x.inspect }  # => "[1, 4][2, 5][3, 6]"
    (1..3).zip([4, 5, 6], [7, 8]) { |x| print x }  # => "14725836"
    (1..3).zip('a'..'c') { |x, y| print x, y}      # => "1a2b3c"
    # 将可枚举集合转换为一个数组
    (1..3).to_a     # => [1, 2, 3]
    (1..3).entries  # => [1, 2, 3] to_a同义词
    
    # 
    require 'set'
    (1..3).to_set

枚举器和外部迭代器

在Ruby 1.9中枚举器是用Enumerable::Enumerator类来实现。在Ruby 1.8中可以通过包含enumerator库来得到。

    # 通过to_enum来创建一个Enumerator对象
    e = [1..10].to_enum
    
    # 通过enum_for来创建一个Enumerator对象
    e = "test".enum_for(:each_byte)
    
    # 直接用不带代码块的方式调用iterator方法
    e = "test".each_byte

枚举器作为外部迭代器被使用

    "Ruby".each_char.max     # => "y"
    iter = "Ruby".each_char  # => 
    loop do print iter.next; end 
    print iter.next   # prints "R"
    iter.rewind       # 
    print iter.next   # prints "R"
    "Ruby".each_char.with_index.each do |c, i| puts "#{i}: #{c}"; end


blog comments powered by Disqus