我正在阅读关于.each迭代器的Ruby问题,有人说.each如果高阶迭代器更适合任务,使用可能是代码气味.Ruby中的高阶迭代器是什么?
编辑: JörgWMittag,我提到的StackOverflow答案的作者提到他打算编写更高级别的迭代器,但他也解释了它们在下面的内容.
Jör*_*tag 23
哎呀.我的意思是更高级别的迭代器,而不是更高阶的迭代器.每个迭代器当然都是高阶的.
基本上,迭代是一个非常低级的概念.编程的目的是将意图传达给团队中的其他利益相关者."初始化为空数组,然后再遍历另一个数组和添加该阵列到第一阵列的当前元素是否是被二整除没有余数"是不进行通信的意图."选择所有偶数" 是.
通常,您几乎不会为了迭代而迭代集合.你要么想要
mapRuby和Smalltalk,collect以及.NET和SQL Select),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中)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,所以你必须在那里打一个额外的行; 其他方法的行为略有不同,具体取决于您传入的参数类型和数量等等.我把它们排除在外是因为它们分散了我想要的注意力.
他们谈论的是更专业的方法,比如map,filter或inject.例如,而不是这样:
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一样更简洁.)
这些通常不被称为"高阶迭代器",但无论谁写的可能只是一个小的精神混淆.无论您使用什么术语,这都是一个很好的原则.