枚举器`Array#each`的{block}不能总是改变数组值?

时间:2012-10-23 01:15:33

标签: ruby arrays block each enumerator

好吧也许这很简单但...... 鉴于此:

arr = ("a".."z").to_a

arr

=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

..并且我正在尝试将所有“arr”值更改为“bad”

为什么 这不起作用?

arr.each { |v| v = "bad" }

arr

=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

答案提示“v”是块的局部变量(数组值的“副本”),我完全明白(以前从未困惑过我)但是

..如果数组元素是对象,它为什么会起作用?

class Person
  def initialize
    @age = 0
  end
  attr_accessor :age
end

kid = Person.new
man = Person.new
arr = [kid, man]


arr.each { |p| p.age = 50 }

arr[0]
=> #<Person:0xf98298 @age=50>

这里的“p”还不在这里? 但是它真的影响了对象,为什么会这样?

4 个答案:

答案 0 :(得分:11)

我将扩展@pst的评论:

  

为什么这不起作用?

arr.each { |v| v = "bad" }

因为each遍历数组并将每个项目放入您作为本地变量v提供的块中,因为v不是对数组arr的引用。

new_arr = arr.each { |v| v = "bad" }

each没有回馈数组,因为你会使用map(参见@ benjaminbenben的回答)。因此,分配它并不“有效”。

arr.each { |v| arr[arr.index v] = "bad" }

在这里,您将arr中的每个项目放入本地变量v,但您在块中引用了数组本身,因此您可以分配给数组并使用局部变量v来查找与v的内容相对应的索引(但您可能会发现当项目不是全部唯一时,这将无法正常工作)

arr.each { |p| p.age = 50 }

kid.age #-> 50

在这里,您再次使用p中的每个项目/对象填充了局部变量arr,但之后您通过方法访问了每个项目,因此您可以更改该项目 - 您没有更改数组。它是不同的,因为引用是局部变量的内容,你混淆了它是对数组的引用。它们是分开的东西。


回应以下评论:

arr[0]
# => #<Person:0xf98298 @age=50>

所有关于谁在何时指的是谁。

试试这个:

v = Person.new
# => #<Person:0x000001008de248 @age=0>
w = Person.new
# => #<Person:0x000001008d8050 @age=0>
x = v
# => #<Person:0x000001008de248 @age=0>
v = Person.new
# => #<Person:0x00000100877e80 @age=0>
arr = [v,w,x]
# => [#<Person:0x00000100877e80 @age=0>, #<Person:0x000001008d8050 @age=0>, #<Person:0x000001008de248 @age=0>]

v在那里提到了2个不同的对象。 v不是一个固定的东西,它是一个名字。首先它指的是#<Person:0x000001008de248 @age=0>,然后它指的是#<Person:0x00000100877e80 @age=0>

现在试试这个:

arr.each { |v| v = "bad" }
# => [#<Person:0x00000100877e80 @age=0>, #<Person:0x000001008d8050 @age=0>, #<Person:0x000001008de248 @age=0>]

它们都是对象,但没有更新或“工作”。为什么?因为首次输入块时,v指的是数组中的项目(给定)。因此,在第一次迭代时,v#<Person:0x00000100877e80 @age=0>

但是,我们会将"bad"分配给v。我们没有将"bad"分配给数组的第一个索引,因为我们根本没有引用该数组arr是对数组的引用。将arr放在块中,您可以更改它:

arr.each { |v| 
  arr[0] = "bad" # yes, a bad idea!
}

为什么arr.each { |p| p.age = 50 }更新数组中的项目?因为p指的是恰好在数组中的对象。在第一次迭代时,p引用的对象也称为kidkid具有age=方法,您可以在其中粘贴50kid也是数组中的第一项,但您所说的是kid而不是数组。你可以这样做:

arr.each { |p| p = "bad"; p.age }
NoMethodError: undefined method `age' for "bad":String

首先,p引用了恰好位于数组中的对象(就是它产生的对象),但后来p引用了"bad"

each遍历数组并在每次迭代时产生一个值。您只能获得值而不是数组。如果要更新阵列,请执行以下操作:

new_arr = arr.map{|v| v = "bad" }
new_arr = arr.map{|v| "bad" } # same thing

arr.map!{|v| v = "bad"}
arr.map!{|v| "bad"}  # same thing

as map返回一个数组,其中填充了块的返回值map!将使用填充了块的返回值的数组更新您调用它的引用。一般来说,无论如何迭代它时更新对象是个坏主意。我发现将它想象为创建一个新数组总是更好,然后你可以使用!方法作为捷径。

答案 1 :(得分:4)

在示例中

arr.each { |v| v = "bad" }

“v”只是对字符串的引用,当你执行v = "bad"时,你重新分配局部变量。为了使一切变得糟糕,你可以这样做:

arr.each { |v| v.replace "bad" }

下次您可以使用Object#object_id

puts arr[0].object_id #will be save as object_id in first iteration bellow
arr.each { |v| puts v.object_id }

答案 2 :(得分:3)

您可能正在寻找.map - 它返回一个新数组,其中包含每个元素的块的返回值。

arr.map { "bad" }

=> ["bad", "bad", "bad", "bad", …] 

使用.map!将改变原始数组的内容,而不是返回一个新数组。

答案 3 :(得分:1)

这个怎么样

arry = Array.new(arry.length,"bad")

这会将默认值“bad”设置为 arry.length