为什么不调用这两个.map方法会产生相同的结果?第一个按预期工作,而第二个没有效果。
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap
答案 0 :(得分:1)
问题在于,在你的第一张地图中!已修改原始数组中的值,因此它现在包含反向字符串。
irb:001:0> array = ["batman","boobytrap"]
=> ["batman", "boobytrap"]
irb:002:0> puts array.map { |x| x.reverse! }
namtab
partyboob
=> nil
irb:003:0> array
=> ["namtab", "partyboob"]
所以第二次正在做你期望的事情,但是输入数据不是你想象的那样。 如果您在没有完成第一个案例的情况下单独试用第二个案例,您将看到它按预期工作。
答案 1 :(得分:1)
在array
之后打印puts array.map { |x| x.reverse! }
。你会看到 - 数组已经改变。
阅读reverse!
方法的documentation。
答案 2 :(得分:1)
你必须改变你对变量的看法。变量不是实际值,而只是对该值的引用。
array = ["batman"]
# We are freezing this string to prevent ruby from creating a
# new object for a string with the same value.
# This isnt really necessary, just to "make sure" the memory
# address stays the same.
string = "boobytrap".freeze
array.each do |val|
puts "before: ",
"val.object_id: #{val.object_id}",
"string.object_id: #{string.object_id}",
"array[0].object_id: #{array[0].object_id}",
""
val = string
puts "after: ",
"val.object_id: #{val.object_id}",
"string.object_id: #{string.object_id}",
"array[0].object_id: #{array[0].object_id}"
end
# before:
# val.object_id: 70244773377800,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800
#
# after:
# val.object_id: 70244761504360,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800
显然,如果在您的机器上运行此代码,则值会有所不同,但关键是,val
的内存地址会发生变化,而array[0]
(val来自的地方)保持不变在我们将字符串分配给val之后。基本上我们对重新分配的处理方式是,我们告诉ruby,在70244773377800中找不到val的值,但是在70244761504360中。尽管数组仍然引用它的第一个值70244773377800!
另一方面,您在x上的示例中使用的#reverse!
方法调用会更改内存中70244773377800处找到的任何值,这就是它按预期工作的原因。
TLDR; 您的第一个示例更改了内存中的值,而第二个示例为本地变量分配了一个新的内存地址。
答案 3 :(得分:1)
执行array.map { |x| x.reverse! }
时,它更改了数组值。
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
如果对同一个数组执行第二次操作,它将产生与问题中所述相同的结果。但是,它不会更改原始数组的值。
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap
array
=> ["namtab", "partyboob"]
要更改原始数组的值,请在第二次操作中使用map!
。
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
puts array.map! { |x| x.reverse }
=> batman
=> boobytrap
array
=> ["batman", "boobytrap"]
答案 4 :(得分:0)
您的方法存在一些问题。
第一个问题是您没有正确隔离测试用例:在第一个测试用例中,您可以反转数组中的字符串。在您的第二个测试用例中,您再次将它们反转 。如果你反转两次会怎么样?那是对的:没有!所以,你认为它不起作用的原因实际上正是 工作的事实!如果没有工作(即没有反转字符串),那么它会打印以前反转的字符串,你会认为它 工作。< / p>
所以,第1课:始终隔离您的测试用例!
问题#2是第二段代码没有做你(可能)认为它做的事情。 x
是一个局部变量(因为它以小写字母开头)。局部变量是它们定义的范围的本地变量,在本例中是块。因此,x
仅存在于块内。你分配给它,但在分配之后你永远不会对它做任何事情。因此,作业实际上是无关紧要的。
相关的是块的返回值。现在,在Ruby中,赋值会计算分配的值,因此考虑到x
的赋值是多余的,我们可以摆脱它,而你的代码实际上与此完全等价:
array.map { |x| x.reverse }
你的第三个问题是,第一件作品也没有做你(可能)认为它做的事情。 Array#map
会返回 new 数组并保持原始数组不变,但String#reverse!
会突变字符串!换句话说:它的主要操作模式是副作用。除了副作用之外,还会返回反向字符串,这是让你困惑的另一件事。它也可以返回nil
来表示它执行副作用,在这种情况下,您将看到以下内容:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> [nil, nil]
array
#=> ['namtab', 'partyboob']
如您所见,如果 String#reverse!
返回nil
,您会看到以下内容:
array.map
返回 new 数组,其元素是块的返回值,仅为nil
array
现在仍然包含与以前相同的String
个对象,但它们已经发生变异现在,由于String#reverse!
实际上确实返回了反向String
,您实际观察到的是:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> ['namtab', 'partyboob']
array
#=> ['namtab', 'partyboob']
array.map(&:reverse)
#=> ['batman', 'boobytrap']
array
#=> ['namtab', 'partyboob']
这让我第二课:副作用和共享的可变状态是邪恶的!
你应该尽量避免副作用和共享可变状态(理想情况下,一般是可变状态,但不同部分之间共享的可变状态尤其是邪恶)。
他们为什么邪恶?好吧,看看他们在这个极其简单的小例子中有多困惑?你能想象在一个更大,更大的应用程序中发生同样的事情吗?
副作用的问题在于它们发生在旁边&#34;。它们不是参数,它们不是返回值。您无法将它们打印出来,检查它们,将它们存储在变量中,在单元测试中对它们进行断言,等等。
共享可变状态的问题(突变只是副作用的一种形式,顺便说一句)是能够实现远距离的幽灵行为&#34;:你在一个地方有一个数据您的代码,但这些数据与代码的不同位置共享。现在,如果一个地方改变了数据,另一个地方似乎神奇地将他们的数据改变了。在这里的示例中,共享状态是数组中的字符串,并在一行代码中对它们进行变换,这使得它们在另一行代码中也会发生变化。