Ruby中的高阶迭代器?

jer*_*son 5 ruby iterator

我正在阅读关于.each迭代器的Ruby问题,有人说.each如果高阶迭代器更适合任务,使用可能是代码气味.Ruby中的高阶迭代器是什么?

编辑: JörgWMittag,我提到的StackOverflow答案的作者提到他打算编写更高级别的迭代器,但他也解释了它们在下面的内容.

Jör*_*tag 23

哎呀.我的意思是更高级别的迭代器,而不是更高阶的迭代器.每个迭代器当然都是高阶的.

基本上,迭代是一个非常低级的概念.编程的目的是将意图传达给团队中的其他利益相关者."初始化为空数组,然后再遍历另一个数组和添加该阵列到第一阵列的当前元素是否是被二整除没有余数"是进行通信的意图."选择所有偶数" .

通常,您几乎不会为了迭代而迭代集合.你要么想要

  • 以某种方式转换每个元素(通常称为mapRuby和Smalltalk,collect以及.NET和SQL Select),
  • 将整个集合减少到一个单一的值,例如计算足球得分列表的总和或平均值或标准差(在类别理论中,这被称为catamorphism,在函数式编程中,fold或者reduce在Smalltalk中inject:into:,在Ruby中)它是inject在.NET中Aggregate,
  • 过滤掉满足特定条件的所有元素(filter在大多数函数语言中,select在Smalltalk和Ruby中,也在find_allRuby中,Where在.NET和SQL中),
  • 过滤掉所有元素满足条件(reject在Smalltalk和Ruby)
  • 找到满足条件的第一个元素(find在Ruby中)
  • 计算满足条件的元素(count在Ruby中)
  • 检查所有elements(all?),至少一个element(any?)或没有elements(none?)是否满足条件
  • 基于某些鉴别器(group_by在Ruby,.NET和SQL中)将元素分组到存储桶中
  • 基于一些谓词(partition)将集合划分为两个集合
  • 对集合(sort,sort_by)进行排序
  • 将多个集合合并为一个(zip)
  • 等等等等 …

几乎永远不是你的目标,只是迭代一个集合.

特别是,reduce又名.inject又名.fold又名.inject:into:又名.Aggregate又名.catamorphism是你的朋友.这就是为什么它具有如此奇特的数学名称的原因:它非常强大.事实上,我上面提到的大部分内容都可以用来实现reduce.

基本上,reduce它使用某些功能将整个集合"减少"到单个值.你有一些累加器值,然后你取累加器值和第一个元素并将其输入函数.然后该函数的结果成为新的累加器,您将其与第二个元素配对并输入函数,依此类推.

最明显的例子是汇总一个数字列表:

[4, 8, 15, 16, 23, 42].reduce(0) {|acc, elem|
  acc + elem
}
Run Code Online (Sandbox Code Playgroud)

因此,累加器开始时0,我们将第一个元素传递4+函数.结果是4,它成为新的累加器.现在我们传递下一个元素8,结果是12.这一直持续到最后一个元素,结果是他们一直都死了.不,等等,结果是108.

红宝石实际上允许我们采取了几个快捷键:如果元素类型是一样的蓄电池类型,你可以离开了蓄压器和Ruby会简单地传递的第一个元素作为累加器的第一个值:

[4, 8, 15, 16, 23, 42].reduce {|acc, elem|
  acc + elem
}
Run Code Online (Sandbox Code Playgroud)

另外,我们可以Symbol#to_proc在这里使用:

[4, 8, 15, 16, 23, 42].reduce(&:+)
Run Code Online (Sandbox Code Playgroud)

实际上,如果你传递reduce一个Symbol参数,它将被视为用于缩减操作的函数的名称:

[4, 8, 15, 16, 23, 42].reduce(:+)
Run Code Online (Sandbox Code Playgroud)

然而,求和并非所有reduce能做到的.事实上,我发现这个例子有点危险.我向大家展示了这一点,立即明白,"Aah,所以这就是现实reduce",但不幸的是,有些人还认为总结数字就是这样 reduce,而且绝对不是这样.事实上,reduce是一个迭代的一般方法,我的意思是reduce可以做任何事情each可以做的.特别是,您可以在累加器中存储任意状态.

例如,我在上面写道,reduce将集合减少到单个值.但当然,"单一价值"可能是任意复杂的.例如,它本身就是一个集合.或者一个字符串:

class Array
  def mystery_method(foo)
    drop(1).reduce("#{first}") {|s, el| s << foo.to_str << el.to_s }
  end
end
Run Code Online (Sandbox Code Playgroud)

这是一个例子,你可以用累加器玩弄技巧.如果你尝试一下,你当然会认为它是Array#join:

class Array
  def join(sep=$,)
    drop(1).reduce("#{first}") {|s, el| s << sep.to_str << el.to_s }
  end
end
Run Code Online (Sandbox Code Playgroud)

请注意,在这个"循环"中没有任何地方我必须跟踪我是在最后一个元素还是倒数第二个元素.代码中也没有任何条件.这里没有fencepost错误的可能性.如果您想了解如何与实现这个each,你必须以某种方式跟踪指数,并检查你是否在最后一个元素,然后有一个if在里面,防止在年底发射的分隔符.

由于我上面写的所有迭代都可以完成reduce,我也可以证明它.这是Ruby的Enumerable方法,reduce而不是each像往常那样实现.(请注意,我刚开始并且只到了g.)

module Enumerable
  def all?
    reduce(true) {|res, el| res && yield(el) }
  end

  def any?
    reduce(false) {|res, el| res || yield(el) }
  end

  def collect
    reduce([]) {|res, el| res << yield(el) }
  end
  alias_method :map, :collect

  def count
    reduce(0) {|res, el| res + 1 if yield el }
  end

  def detect
    reduce(nil) {|res, el| if yield el then el end unless res }
  end
  alias_method :find, :detect

  def drop(n=1)
    reduce([]) {|res, el| res.tap {|res| res << el unless n -= 1 >= 0 }}
  end

  def drop_while
    reduce([]) {|res, el| res.tap {|res| res << el unless yield el }}
  end

  def each
    reduce(nil) {|_, el| yield el }
  end

  def each_with_index
    tap { reduce(-1) {|i, el| (i+1).tap {|i| yield el, i }}}
  end

  def find_all
    reduce([]) {|res, el| res.tap {|res| res << el if yield el }}
  end
  alias_method :select, :find_all

  def grep(pattern)
    reduce([]) {|res, el| res.tap {|res| res << yield(el) if pattern === el }}
  end

  def group_by
    reduce(Hash.new {|hsh, key| hsh[key] = [] }) {|res, el| res.tap {|res|
        res[yield el] = el
    }}
  end

  def include?(obj)
    reduce(false) {|res, el| break true if res || el == obj }
  end

  def reject
    reduce([]) {|res, el| res.tap {|res| res << el unless yield el }}
  end
end
Run Code Online (Sandbox Code Playgroud)

[注意:为了这篇文章,我做了一些简化.例如,根据标准的Ruby Enumerable协议,each应该返回self,所以你必须在那里打一个额外的行; 其他方法的行为略有不同,具体取决于您传入的参数类型和数量等等.我把它们排除在外是因为它们分散了我想要的注意力.


Chu*_*uck 5

他们谈论的是更专业的方法,比如map,filterinject.例如,而不是这样:

even_numbers = []
numbers.each {|num| even_numbers << num if num.even?}
Run Code Online (Sandbox Code Playgroud)

你应该做这个:

even_numbers = numbers.select {|num| num.even?}
Run Code Online (Sandbox Code Playgroud)

它说明你想做什么,但是将所有不相关的技术细节都封装在select方法中.(顺便说一下,在Ruby 1.8.7或更高版本中,你可以写even_numbers = numbers.select(&:even?),所以如果稍微像Perl一样更简洁.)

这些通常不被称为"高阶迭代器",但无论谁写的可能只是一个小的精神混淆.无论您使用什么术语,这都是一个很好的原则.