`
Hooopo
  • 浏览: 335403 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

学习ruby之Enumerable模块

阅读更多
读入Enumerable模块的类,可以使用很多种迭代器与区块调用。在这里介绍其中的each、collect、sort和sort_by 4个方法。
20.4.1  each方法

each方法是使用Enumerable模块时一定要定义的方法。这个方法里必须定义一个迭代器,用来像前面的数组、杂凑、文件那样,将对象里可以访问的数据完整地逐项读出。
20.4.2  collect方法

collect方法是使用each方法定义出来的。
传入collect方法的区块虽然类似于each方法,但差异在于区块的判断结果最后会存放在数组中返回。
20.4.3  sort方法

sort方法用来排序元素。
所谓的“排序”方法其实有很多种:
—   依照数值大小排序;
—   依照数据长度排序;
—   依照字母顺序排序;
—   依照字母顺序反向排序。
若要结合不同的条件定义不同的排序方法,方法的数量会太多而很难记忆。所以,只定义一个用来排序的方法,而在调用方法的时候可以自己指定排序的方式,似乎是比较好的做法。
这个“指定排序方式”的动作,也是靠区块调用做到的。
例如,假设现在想要排序字符串数组array。若要以字母顺序排列时,则可以写:


sorted = array.sort{|a, b|
  a <=> b
}


如果要以字符串长度排序时,则可以写:

sorted = array.sort{|a, b|
  a.length <=> b.length
}

前面只是单纯比较a与b这两个字符串,而这里则使用length方法,来比较字符串的长度。
像这样,sort方法会在进行比较时使用区块里的代码。
20.4.4  sort_by方法

sort方法会在每次进行比较时判断元素。让我们来计算一下前面比较字符串长度的示例中,length方法到底被调用了几次。

ary = %w(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
num = 0                  # 调用的次数
ary.sort {|a, b|
  num += 2               # 累加调用的次数
  a.length <=> b.length
}
p num                      #=> 54

这个示例可以知道有20个元素的时候,length方法要调用54次。按理说对每个字符串调用一次length方法再排序就可以了,所以这里做了很多次多余的调用动作。当数组很长,或者判断元素的运算很耗时的时候,这些多余的调用动作会对整体执行时间造成很大的影响。

没有迭代器的区块调用

在Ruby中,存在本身不是循环,与循环也无关,内含被称为区块的方法。
代表的实例有诸如前面介绍的sort方法,在sort方法中,的确存在区块部分,它跟loop这样的循环结构完全没有任何关系。
至于区块,为什么要使用它呢?这是因为,区块传递处理作为方法的参数。
关于&ldquo;处理传递&rdquo;,这里有必要说明一下。
通常,方法中传递的东西为&ldquo;对象&rdquo;,比如字符串、数值、数组、杂凑,或者是自定义的类的对象,总之这些都是对象。
然而,对于方法来说,在有的场合下,我们希望能够传递&ldquo;处理&rdquo;(而不是对象)。例如&ldquo;排序&rdquo;这样的处理,基于进行什么样的比较这一前提,得出的排序的结果自然也不一样。因此,我们需要往排序里传递比较的方式。
在这样的问题背景下,对于一些程序设计语言来说,采用的方法是&ldquo;制作函数或是跟函数操作类似的东西,然后作为参数传递&rdquo;的做法,基于该种做法的步骤就写作:
&mdash;   定义函数(或是类似函数的东西)
&mdash;   将函数作为参数,进行方法调用
两个阶段。与上面相比,在
array.sort{|a,b| a &oacute;b}

的书写方式中,将函数中的操作部分作为方法参数进行传递。这样一来,方法的表述也就特别简单了。
含区块的方法的结构突显灵活的理由是:对于1个方法来讲,有多少个参数才算合适这样的问题。根据经验,我们认为在有区块的场合下,1个参数会比较好些。这样一来,区块和方法的关联关系看起来会比较容易理解些。
像这种要对所有元素使用相同的运算方式所运算出的结果进行排序时,使用sort_by方法可以节省不少时间。

ary = %w(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
ary.sort_by {|item|
  item.length
}

sort_by方法会对每个元素先执行一次区块指定的动作,再以这个结果进行排序
20.5   实现迭代器

前面介绍了很多Ruby所提供的标准迭代器,当然自己要定义迭代器也是可以的。
在这里以一个图书列表的类为例,来加以介绍。
首先定义一个代表书籍的Book类(见程序20.8)。在Book类的对象里,存放书名、作者名、领域等数据,分别存放在@title、@author、@genre这些实例变量里,并将这些实例变量设定为可通过访问方法从外部读取与更改。
程序20.8  book.rb
class Book
  attr_accessor :title, :author, :genre
  def initialize(title, author, genre=nil)
    @title  = title
    @author = author
    @genre = genre
  end
end

接着来定义BookList类作为图书列表(见程序20.9)。这个类提供了新增书籍、删除书籍、读取列表中的书籍数据等操作。
程序20.9  booklist.rb(尚未支持迭代器)
require 'book'
class BookList
  ## 初始化
  def initialize()
    @booklist = Array.new()
  end
  ## 新增书籍
  def add(book)
    @booklist.push(book)
  end
  ## 返回书籍数量
  def length()
    @booklist.length()
  end
  ## 将第n本书改成其他书籍
  def []=(n,book)
    @booklist[n] = book
  end
  ## 返回第n本书
  def [](n)
    @booklist[n]
  end
  ## 从列表中删除书籍
  def delete(book)
    @booklist.delete(book)
  end
end

程序20.10是一个使用Book类与BookList类的程序示例。
程序20.10  booklist_test.rb
require 'book'
require 'booklist'
# 建立新的图书列表(这时是空的)
booklist = BookList.new()
# 先插入几本书
b1 = Book.new("iPod 玩拆解","三浦一则")
b2 = Book.new("How Objects Work","平泽章")
# 新增书籍
booklist.add(b1)
booklist.add(b2)
# 输出列表里的书
print booklist[0].title, "\n"
print booklist[1].title, "\n"

接下来,依序获取存放在BookList类对象里的每一本书。做法虽然有很多,但这里想要使用&ldquo;以迭代器获取&rdquo;的做法。使用起来会是这样:

booklist.each{|book|
  print book.title, "\n"
}

就在booklist.rb里定义这个方法吧:

def each
  @booklist.each{|book|
    yield(book)
  }
end

在这里出现了&ldquo;yield(book)&rdquo;这行语句,这个yield就是定义迭代器时最重要的关键所在。写在方法定义里的yield,表示调用传递给这个方法的区块。这时,yield若有指定实参,就会分别存进区块变量里。
例如,下面这个print2方法的调用。

obj.print2{|x, y|
  print x,"\n"
  print y,"\n"
}

如果print2方法的定义如下:

def print2
  yield(1,2)
end

则这时区块变量的x与y的值就分别是1与2。
当然,这段定义中print2方法的区块只调用了一次。要定义成迭代器时,应该使用循环之类的方式不断调用yield语句。
回到前面each方法的定义。在这个定义中,调用实例变量@booklist的each方法,获取@booklist内的每个对象,再以这个对象作为实参,调用yield语句。这样一来,就可以对实例变量@booklist里的每个元素,执行一次迭代器的区块了。
接下来要思考的是只获取书名的迭代器。这个迭代器的名称使用each_title似乎不错。使用方法如下:

booklist.each_title{|title|
  print title,"\n"
}

前面的each方法是先建立Book对象,再以对象的title方法获取数据。这个each_title方法则可以直接从booklist对象里取出书名。
现在就试着在booklist.rb里定义each_title方法吧。

def each_title
  @booklist.each{|book|
    yield(book.title)
  }
end

与前面的each方法示例不同的地方只有yield的部分。使用each方法时yield的实参是book,传递整个book对象,而这里则是以book.title为实参,所以区块得到的数据就只有书名的字符串了。
下面再设计一个迭代器,不只可以处理书名,而且还传入作者。方法名称就叫做each_title_author吧。用法如下:

booklist.each_title_author{|title, author|
  print "「",title,"&ldquo;" ,author,"\n"
end

与前面示例不同的地方在于它传递给区块两个实参。现在就赶快在booklist.rb里定义这个each_title_author方法吧。

def each_title_author
  @booklist.each{|book|
    yield(book.title, book.author)
  }
end

差异也只在于yield的实参而已。前面只有book.title,这里则有book.title与book.author两个。
就像这个方法所定义的,当yield不只一个实参时,区块就会收到不只一个区块变量。
有实参的迭代器

这里再举一个除了区块以外,也有一般实参的迭代器示例。
例如,想要只获取某个特定作者的书籍列表时,使用each方法,可以这样写:

author_regexp = /高桥/
booklist.each{|book|
  if author_regexp =~ book.author
    print book.title, "\n"
  end
}

当然这样做也不是不行,但若能直接以作者姓名获取适当的迭代器,感觉似乎更方便。例如像这样:

booklist.find_by_author(/高桥/){|book|
  print book.title, "\n"
}

把这个find_by_author方法也定义在booklisk.rb里:

def find_by_author(author)
  @booklist.each{|book|
    if author =~ book.author
      yield(book)
    end
  }
end

以实例变量@booklist的each方法逐项获取book的地方是一样的,差异仅在于在使用yield将book传递给区块的部分是写在if条件式里的。这里会将传递给方法的参数author与书籍的作者姓名,也就是book对象的author互相匹配,只在匹配成功的时候调用yield。
接下来再把这个方法改写成没有指定区块的时候,就返回匹配成功的项构成的数组。让这个方法也可以不需要指定区块。

books = booklist.find_by_author(/高桥/)
books.each{|book|
  print book.title,"\n"
}

修改后的find_by_author方法如下所示:

def find_by_author(author)
  if block_given?
    @booklist.each{|book|
      if author =~ book.author
        yield(book)
      end
    }
  else ## 区块不存在时
    result = []
    @booklist.each{|book|
      if author =~ book.author
        result << book
      end
    }
    return result
  end
end

这个方法使用到了block_given?这个方法。这个方法在有传入区块时会返回true;没有传入区块时会返回false。
这一章所定义的BookList类的最后完整版如程序20.11所示。
程序20.11  booklist.rb(完整版)
require 'book'
class BookList
  ## 初始化
  def initialize()
    @booklist = Array.new()
  end
  ## 新增书籍
  def add(book)
    @booklist.push(book)
  end
  ## 返回书籍数量
  def length()
    @booklist.length()
  end
  ## 将第n本书改成其他书籍
  def []=(n,book)
    @booklist[n] = book
  end
  ## 返回第n本书
  def [](n)
    @booklist[n]
  end
  ## 从列表中删除书籍
  def delete(book)
    @booklist.delete(book)
  end
  def each
    @booklist.each{|book|
      yield(book)
    }
  end
  def each_title
    @booklist.each{|book|
      yield(book.title)
    }
  end
  def each_title_author
    @booklist.each{|book|
      yield(book.title, book.author)
    }
  end
  def find_by_author(author)
    if block_given?
      @booklist.each{|book|
        if author =~ book.author
          yield(book)
        end
      }
    else ## 区块不存在时
      result = []
      @booklist.each{|book|
        if author =~ book.author
          result << book
        end
      }
      return result
    end
  end
end


区块的传递方法

到目前为止的例子中,区块是以方法结尾地方用{~}括起来这样的形式进行传递的。除此之外,区块还可以以Proc对象的方式进行传递。
例如,在方法向方法传递区块的场合,需要通过命名变量来表示传递的区块,该变量前面必须使用&ldquo;&&rdquo;方能进行区块的传递。
def each_some(a, b, &block)
    #前面的处理
    each_some2(&block)
    #后面的处理
    end

  
each_some方法调用了each_some2方法,这时传入each_some中区块&block,再次传给了each_some2方法。
对于使用了&ldquo;&&rdquo;的参数代表传递的区块的场合,在运行该区块的内容时,需要用call方法进行调用。
def def foo(a, b, &block)
        block.call(a, b)
    end
这与
def foo(a, b)
   yield(a, b)
end

运行出来的效果是一样的。

分享到:
评论

相关推荐

    Ruby学习资料chm

    9. blocks和迭代器:Ruby的Enumerable模块提供了许多内置的迭代器,如each、map等,结合blocks可以方便地进行数据处理。 在"Ruby学习资料chm"中,可能涵盖以下内容: 1. Ruby基础语法:包括变量、常量、运算符、...

    关于ruby学习的资料

    4. 核心库和标准库:了解Ruby内置的库和模块,如`Enumerable`、`File`和`Net`。 5. 元编程:深入理解Ruby的元编程特性,如`send`、`class_eval`和`instance_variable_get/set`。 6. 测试驱动开发(TDD):学习使用...

    ruby实用函数和实例

    此外,Enumerable模块是一组用于遍历和操作集合的强大工具,它包含的`inject`和`reduce`方法可以执行聚合操作,`group_by`则可以按条件对元素分组。 Ruby的模块(Module)和类(Class)系统使得代码组织和复用变得...

    Enumerable.js:适用于所有类型集合的实用函数

    这个库很大程度上受到 Ruby 的 Enumerable 模块的启发,并借用了它的大量功能。 注意:这并不意味着是一个独立的库,而是要与具有.each方法的另一种数据类型结合使用。安装作为 NPM 模块npm install enumerable-js ...

    ruby trap 初学者使用

    "Ruby Trap"这个标题暗示了这是一本关于Ruby编程中常见问题和陷阱的电子书,旨在帮助初学者避免在学习过程中遇到的困扰。下面,我们将深入探讨一些可能涵盖在书中的Ruby编程知识点。 1. **变量和常量**: - Ruby有...

    j-enum:Enumerable 和 Enumerator 的实现

    在Ruby编程语言中,Enumerable模块和Enumerator类是两个非常核心且强大的工具,它们为处理集合数据提供了丰富的功能。本文将深入探讨这两个概念的实现、用途以及如何在实际编程中运用它们。 首先,Enumerable模块是...

    函数式-确定性-Ruby取笑___下载.zip

    9. **Enumerable模块**:Ruby的Enumerable模块提供了一整套函数式编程工具,如`each`, `all?`, `any?`, `none?`, `find`, `count`等,这些都是在处理集合时非常有用的函数。 10. **Proc和Lambda**:两者都是Ruby中...

    ruby文档方面的资料

    Enumerable模块包含了大量处理集合的便利方法,如`each`, `map`, `select`等。 异常处理在Ruby中使用`begin..rescue..end`结构,允许程序在出现错误时进行恢复。Ruby还支持自定义异常类,方便扩展。 Ruby on Rails...

    ruby 入门练习上手项目

    3. **Ruby标准库**:Ruby提供丰富的内置库,如`Enumerable`模块,可以用于处理集合数据。了解并熟练使用这些库能提高代码效率。 4. **异常处理**:学习`begin-rescue-end`块来捕获和处理程序运行时可能出现的错误。...

    ruyb1.9.3标准库帮组文档

    2. **Enumerable模块**:Ruby的Enumerable模块提供了一组方便的数据处理方法,如`each`, `map`, `select`, `inject`等,可以遍历并操作数组、哈希等可枚举对象。 3. **String类**:Ruby的字符串处理非常强大,...

    Ruby-fastrubyRuby编写快速收集常见Ruby惯用语

    `fast-ruby` 项目揭示了`times`、`upto` 和 `step` 之间的差异,以及如何有效地使用`Enumerable`模块。 6. **对象创建和内存管理** Ruby的垃圾回收机制对性能有影响。创建和销毁大量临时对象可能导致性能下降。`...

    enumerable-methods

    该项目展示了Ruby Enumerable模块中方法的重建列表。 重写的Enumerable方法是: 每个-&gt; my_each each_with_index-&gt;​​ my_each_with_index 选择-&gt; my_select 全部? -&gt; my_all? 任何? -&gt; my_any? 没有...

    ruby初学者教程(对初学者很有帮助)

    irb是一个命令行工具,可以让开发者直接在命令行中测试Ruby代码片段,非常适合学习和调试。 **2.4 使用ri(Ruby信息) ri是一个内置的帮助系统,可以帮助开发者查询Ruby文档和API。 **2.5 RubyGems** RubyGems是...

    Ruby for Rails

    - 枚举(Enumerable)模块提供了一系列操作集合的方法,如迭代、排序等。 4. **正则表达式与字符串操作** - Ruby中的正则表达式功能强大,可用于文本匹配和替换等操作。 - 字符串操作是Ruby中的一个重要方面,...

    Ruby-Ruby中的GoF设计模式实现

    Ruby的`Enumerable`模块提供了迭代器模式,如`each`方法。 17. **状态模式**:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。Ruby中,可以使用模块或类的继承来实现状态的变化。 18. ...

    enumerables:实现Ruby可枚举方法

    在Ruby编程语言中,Enumerable模块是一组非常强大的工具,它为集合对象提供了各种迭代方法,使得我们可以方便地处理数组、哈希等数据结构。本文将深入探讨如何实现Ruby的可枚举方法,以及这些方法在实际编程中的应用...

    Ruby编程语言算法集

    在机器学习领域,虽然不如Python的库那么丰富,但`WekaRuby` gem 可以将Java的Weka机器学习框架引入到Ruby中。 对于算法学习,Ruby提供了良好的环境。例如,可以使用`LeetCode`或`HackerRank`上的挑战来练习算法,...

    Ruby经典学习教程(口碑不错)

    ### Ruby经典学习教程知识点梳理 #### 一、Ruby语言概述 **1.1 Ruby的历史** - **起源**:Ruby语言最初由日本人松本行弘(Yukihiro Matsumoto)于1995年开始开发。 - **设计理念**:旨在提供一种既能满足功能需求...

    开源项目-linkosmos-mapop.zip

    Enumerable模块是Ruby中非常重要的一个部分,它提供了一套强大的集合操作方法,如map、select、reduce等,使得处理数组、范围等各种可枚举对象变得极其便利。然而,在处理更复杂的键值对数据结构,如map[string]...

    Enumerable-Methods:这是Microverse中Ruby模块的项目#2

    如果您对上述方法之一有任何疑问,请考虑检查文件。 现场演示 这是该项目中所有方法的。 建于 Ruby Visual Studio,Git和GitHub 入门 先决条件 要运行此项目,您需要一台装有Ruby的计​​算机。 如果您不确定如何...

Global site tag (gtag.js) - Google Analytics