Ruby中的each方法和collect方法有什么不同?

68

从这段代码中,我不知道这两种方法 collecteach 的区别。

a = ["L","Z","J"].collect{|x| puts x.succ} #=> M AA K 
print  a.class  #=> Array

b = ["L","Z","J"].each{|x| puts x.succ} #=> M AA K
print  b.class #=> Array

为什么你要打印 a.classb.class?打印 ab 就可以了,这样会更清晰。参考 Refactor 的回答。我认为你混淆了你的两个一行代码的效果,它们是相同的,但它们的返回值是不同的。 - sawa
1
当您想查看对象的内容时,请使用 p 而不是 print。我忘记提到了。在上面的代码中尝试 p ap b - sawa
7个回答

117

Array#each 方法接受一个数组并对所有项目应用给定的块。它不会影响数组或创建新对象,仅仅是遍历数组的一种方式。同时它返回原数组本身。

  arr=[1,2,3,4]
  arr.each {|x| puts x*2}

打印2、4、6、8并返回[1,2,3,4],无论如何都会执行此操作。

Array#collectArray#map相同,它在所有项上应用给定的代码块并返回新数组。简单来说,它将序列中的每个元素投影到一个新形式中

  arr.collect {|x| x*2}

返回 [2,4,6,8]

而在你的代码中

 a = ["L","Z","J"].collect{|x| puts x.succ} #=> M AA K 

a是一个数组,但实际上它是由Nil的数组[nil,nil,nil]组成的,因为puts x.succ返回nil(即使它打印了"M AA K")。

而且

 b = ["L","Z","J"].each{|x| puts x.succ} #=> M AA K

also是一个数组。但它的值为["L","Z","J"],因为它返回self。


41

Array#each 只是将每个元素放入块中,然后返回原始数组。Array#collect 将每个元素放入新的数组中并返回:

[1, 2, 3].each { |x| x + 1 }    #=> [1, 2, 3]
[1, 2, 3].collect { |x| x + 1 } #=> [2, 3, 4]

6
each 用于迭代数组并在每次迭代中做任何你想做的事情。在大多数(命令式)语言中,这是程序员需要处理列表时会使用的“一刀切”的方法。
对于更多函数式语言而言,只有当你无法通过其他方式进行操作时才使用这种通用迭代方法。大部分时间,使用 mapreduce 更为合适(在 Ruby 中分别为 collect 和 inject)。 collect 用于将一个数组转换为另一个数组。 inject 用于将一个数组转换为一个单一的值。

2

以下是两个源代码片段,根据文档...

VALUE
rb_ary_each(VALUE ary)
{
    long i;

    RETURN_ENUMERATOR(ary, 0, 0);
    for (i=0; i<RARRAY_LEN(ary); i++) {
        rb_yield(RARRAY_PTR(ary)[i]);
    }
    return ary;
}

# .... .... .... .... .... .... .... .... .... .... .... ....

static VALUE
rb_ary_collect(VALUE ary)
{
    long i;
    VALUE collect;

    RETURN_ENUMERATOR(ary, 0, 0);
    collect = rb_ary_new2(RARRAY_LEN(ary));
    for (i = 0; i < RARRAY_LEN(ary); i++) {
        rb_ary_push(collect, rb_yield(RARRAY_PTR(ary)[i]));
    }
    return collect;
}

rb_yield() 返回块返回的值 (关于元编程的博客文章请参见此处)。

因此,each 只是执行 yields 并返回原始数组,而 collect 创建一个新数组,并将块的结果推入其中;然后它返回这个新数组。

源代码片段:eachcollect


1
差别在于它返回的内容。 在上面的示例中a == [nil,nil,nil](puts x.succ的值),而b == ["L", "Z", "J"](原始数组)。
从ruby-doc中可以看到,collect的作用如下:

为数组中的每个元素调用一次块。 创建一个新的包含块返回值的数组。

each总是返回原始数组。 合理吧?

0

我认为更容易理解的方法如下:

nums = [1, 1, 2, 3, 5]
square = nums.each { |num| num ** 2 } # => [1, 1, 2, 3, 5]

相反,如果您使用collect:

square = nums.collect { |num| num ** 2 } # => [1, 1, 4, 9, 25]

而且,您可以使用.collect!来改变原始数组。

0

每个类都包含 Enumerable 模块定义的方法。 Object.each 返回一个 Enumerable::Enumerator 对象,其他 Enumerable 方法使用它来迭代对象。每个类的 each 方法的行为不同。

在 Array 类中,当传递一个块给 each 时,它会对每个元素执行块语句,但最终返回 self。当你不需要一个数组,但可能只想从数组中选择元素并将其用作其他方法的参数时,这非常有用。inspectmap 返回一个新数组,其中包含对每个元素执行块的返回值。您可以使用 map!collect! 在原始数组上执行操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接