为什么当我传递一个数组的引用调用函数时,在Ruby中数组会改变?

huangapple go评论74阅读模式
英文:

why when I call a function passing a reference to an array, the array is changing in ruby?

问题

I was following a tutorial on creating a little game in Ruby when I fell into a bug I couldn't understand. When I looked closely, I realized that the error was happening in line 51 of the fogefoge.rb on the call of nova_posicao = calcula_nova_posicao heroi, direcao.

At this point of the tutorial, the hero "H" should be able to move around with the keys "ASDW". I noticed that the array heroi at line 49 heroi = encontra_jogador mapa has been changed and that was introducing the bug in the application. After that I created another variable called heroi2 and the code worked.

Why when I call line 51 of fogefoge.rb, the array is changing?

From what I've been reading in Rubydocs, I just can't understand why this is happening.

File "main.rb":
require_relative "fogefoge"

inicia_fogefoge
File "mapa1.txt":
XXXXX
X H X
X X X
X X X
X   X
  X  
 XXX 
  X  
X F X
XXXXX
File "ui.rb":
def da_boas_vindas
  puts "bem vindo ao Foge-foge"
  puts "Qual é o seu nome?"
  nome = gets.strip
  puts "\n\n\n\n\n"
  puts "Começaremos o jogo para você, #{nome}"
  nome
end

def desenha(mapa)
  puts mapa
end

def pede_movimento
  puts "Para onde deseja ir?"
  movimento = gets.strip
end
#File "fogefoge.rb":
require_relative "ui"

def le_mapa(numero)
  arquivo = "mapa#{numero}.txt"
  texto = File.read(arquivo)
  mapa = texto.split "\n"
  #  p mapa
end

def encontra_jogador(mapa)
  caractere_do_heroi = "H"
  mapa.each_with_index do |linha_atual, linha|
    coluna_do_heroi   = linha_atual.index caractere_do_heroi
    if coluna_do_heroi
      return [linha, coluna_do_heroi]
    end
  end
end

def calcula_nova_posicao(heroi, direcao)
  case direcao
  when "W"
    heroi[0] -= 1
  when "S"
    heroi[0] += 1
  when "A"
    heroi[1] -= 1
  when "D"
    heroi[1] += 1
  end
  heroi
end

def posicao_valida?(mapa, posicao)
  linhas = mapa.size
  colunas = mapa[0].size
  estourou_linhas = posicao[0] < 0 || posicao[0] >= linhas
  estourou_colunas = posicao[1] < 0 || posicao[1] >= colunas
  if estourou_linhas || estourou_colunas
    return false
  end
  if mapa[posicao[0]][posicao[1]] == "X"
    return false
  end
  true
end

def joga(nome)
  mapa = le_mapa 1
  while true
    desenha mapa
    direcao = pede_movimento
    heroi = encontra_jogador mapa
    #heroi2 = encontra_jogador mapa
    nova_posicao = calcula_nova_posicao heroi, direcao #At this call

    if !posicao_valida? mapa, nova_posicao
      next
    end
    mapa[heroi[0]][heroi[1]] = " "
    mapa[nova_posicao[0]][nova_posicao[1]] = "H"
  end
end

def inicia_fogefoge
  nome = da_boas_vindas
  joga nome
end

I was expecting that the "H" would move around through the blank spaces, but the "H" was repeated to a few directions.

OUTPUT:

bem vindo ao Foge-foge
Qual é o seu nome?
marcos
Começaremos o jogo para você, marcos
XXXXX
X H X
X X X
X X X
X   X
X  
XXX 
X  
X F X
XXXXX
Para onde deseja ir?
d
XXXXX
X H X
X X X
X X X
X   X
X  
XXX 
X  
X F X
XXXXX
Para onde deseja ir?
D
XXXXX
X HHX
X X X
X X X
X   X
X  
XXX 
X  
X F X
XXXXX
Para onde deseja ir?
S
XXXXX
X HHX
X X X
X X X
X   X
X  
XXX 
X  
X F X

If you uncomment the second reference at line 50 and then change line 51 to nova_posicao = calcula_nova_posicao heroi2, direcao then the code works.

英文:

I was following a tutorial on creating a little game in Ruby when I fell into a bug I couldn't understand. When I looked closely, I realized that the error was happening in line 51 of the fogefoge.rb on the call of nova_posicao = calcula_nova_posicao heroi, direcao.<br><br>
At this point of the tutorial, the heroe "H" should be able to move around with the keys "ASDW". I noticed that the array heroi at line 49 heroi = encontra_jogador mapa has been changed and that was introducing the bug in the application. After that I created another variable called heroi2 and the code worked.<br><br>
Why when I call line 51 of fogefoge.rb, the array is changing?<br><br>
From what I've been reading in Rubydocs, I just can't understand why this is happening.

File &quot;main.rb&quot;
require_relative &quot;fogefoge&quot;
inicia_fogefoge
File &quot;mapa1.txt&quot;
XXXXX
X H X
X X X
X X X
X   X
X  
XXX 
X  
X F X
XXXXX
File &quot;ui.rb&quot;
def da_boas_vindas
puts &quot;bem vindo ao Foge-foge&quot;
puts &quot;Qual &#233; o seu nome?&quot;
nome = gets.strip
puts &quot;\n\n\n\n\n&quot;
puts &quot;Come&#231;aremos o jogo para voc&#234;, #{nome}&quot;
nome
end
def desenha(mapa)
puts mapa
end
def pede_movimento
puts &quot;Para onde deseja ir?&quot;
movimento = gets.strip
end
#File &quot;fogefoge.rb&quot;
require_relative &quot;ui&quot;
def le_mapa(numero)
arquivo = &quot;mapa#{numero}.txt&quot;
texto = File.read(arquivo)
mapa = texto.split &quot;\n&quot;
#  p mapa
end
def encontra_jogador(mapa)
caractere_do_heroi = &quot;H&quot;
mapa.each_with_index do |linha_atual, linha|
coluna_do_heroi   = linha_atual.index caractere_do_heroi
if coluna_do_heroi
return [linha, coluna_do_heroi]
end
end
end
def calcula_nova_posicao(heroi, direcao)
case direcao
when &quot;W&quot;
heroi[0] -= 1
when &quot;S&quot;
heroi[0] += 1
when &quot;A&quot;
heroi[1] -= 1
when &quot;D&quot;
heroi[1] += 1
end
heroi
end
def posicao_valida?(mapa, posicao)
linhas = mapa.size
colunas = mapa[0].size
estourou_linhas = posicao[0] &lt; 0 || posicao[0] &gt;= linhas
estourou_colunas = posicao[1] &lt; 0 || posicao[1] &gt;= colunas
if estourou_linhas || estourou_colunas
return false
end
if mapa[posicao[0]][posicao[1]] == &quot;X&quot;
return false
end
true
end
def joga(nome)
mapa = le_mapa 1
while true
desenha mapa
direcao = pede_movimento
heroi = encontra_jogador mapa
#heroi2 = encontra_jogador mapa
nova_posicao = calcula_nova_posicao heroi, direcao #At this call
if !posicao_valida? mapa, nova_posicao
next
end
mapa[heroi[0]][heroi[1]] = &quot; &quot;
mapa[nova_posicao[0]][nova_posicao[1]] = &quot;H&quot;
end
end
def inicia_fogefoge
nome = da_boas_vindas
joga nome
end

I was expecting that the "H" would move around through the blank spaces, but the "H" was repeated to a few directions<br><br>
<br>
OUTPUT:
<br>

bem vindo ao Foge-foge
Qual &#233; o seu nome?
marcos
Come&#231;aremos o jogo para voc&#234;, marcos
XXXXX
X H X
X X X
X X X
X   X
X  
XXX 
X  
X F X
XXXXX
Para onde deseja ir?
d
XXXXX
X H X
X X X
X X X
X   X
X  
XXX 
X  
X F X
XXXXX
Para onde deseja ir?
D
XXXXX
X HHX
X X X
X X X
X   X
X  
XXX 
X  
X F X
XXXXX
Para onde deseja ir?
S
XXXXX
X HHX
X X X
X X X
X   X
X  
XXX 
X  
X F X

if you uncomment the second reference at line 50 and then change line 51 to nova_posicao = calcula_nova_posicao heroi2, direcao then the code work.

答案1

得分: 0

Your calcula_nova_posicao 实现会改变传入的 heroi 数组,然后返回它。因此,在调用后,nova_posicaoheroi 将相同(不仅包含相同的值,而且都引用同一个对象)。

以下是一个快速示例,说明如何验证这一点:(equal? 用于检查两个对象是否实际上是同一个对象)

a = [0, 0]
b = calcula_nova_posicao(a, "S")

a #=> [1, 0]  <- 请注意 "a" 已经改变!
b #=> [1, 0]

a.equal?(b) #=> true

为什么我在 Ruby 中调用一个传递数组引用的函数时,数组会发生更改?

这是 Ruby 中的一个非常基本的行为。某些对象是 可变的,也就是说,您可以通过向它们发送某些消息(所谓的 破坏性方法)来更改这些对象。可变对象的示例包括字符串、哈希和数组。不管消息是如何发送或由谁发送的,一旦接收到,对象就会自行更改。

以下是两个变量 ab 的示例,它们引用同一个字符串对象。如果向字符串发送 upcase! 消息,它将大写其字符,两个变量都将反映这种更改:(因为它们仍然引用相同已更改的对象)

a = "foo"
b = a

a.upcase!   # 或 b.upcase!

a #=> "FOO"
b #=> "FOO"

同样适用于方法参数。如果将可变对象传递给方法,方法可以像在方法外部一样更改它:

def my_method(s)
  s.upcase!
end

a = "foo"
my_method(a)

a #=> "FOO"

回到您的问题。您可能希望 calcula_nova_posicao 1) 返回一个 数组,2) 不更改传入的数组。后者对于清除地图上当前的 H 非常重要。但一般来说,不在方法内更改参数是一个好主意,因为它可能导致意外的结果和难以找到的错误。

以下是一个实现:

def calcula_nova_posicao(heroi, direcao)
  case direcao
  when "W"
    [heroi[0] - 1, heroi[1]]
  when "S"
    [heroi[0] + 1, heroi[1]]
  when "A"
    [heroi[0], heroi[1] - 1]
  when "D"
    [heroi[0], heroi[1] + 1]
  end
end

以下是另一种使用 数组分解 的方法:

def calcula_nova_posicao(heroi, direcao)
  y, x = heroi
  case direcao
  when "W" then y -= 1
  when "S" then y += 1
  when "A" then x -= 1
  when "D" then x += 1
  end
  [y, x]
end

您可能考虑为您的方法编写 测试

英文:

Your calcula_nova_posicao implementation alters the passed heroi array and then returns it. Therefore, your nova_posicao and heroi will be the same after the call (not only containing the same values, but both referring to the very same object).

Here's a quick example on how to verify this: (equal? checks if two objects are in fact the same object)

a = [0, 0]
b = calcula_nova_posicao(a, &quot;S&quot;)
a #=&gt; [1, 0]  &lt;- note that &quot;a&quot; has changed!
b #=&gt; [1, 0]
a.equal?(b) #=&gt; true

> why when I call a function passing a reference to an array, the array is changing in ruby?

This is a very fundamental behavior in Ruby. Some objects are mutable, i.e. you can alter such objects by sending certain messages to them (so-called destructive methods). Examples of mutable objects are strings, hashes and arrays. It doesn't matter how or where the (destructive) message is sent or who sends it. Once received, the object will alter itself.

Here's an example of two variables a and b referring to the same object, a string. If you send the upcase! message to the string, it will upcase its characters and both variables will reflect that change: (because they are still referring to the same object which now has changed)

a = &quot;foo&quot;
b = a
a.upcase!   # or b.upcase!
a #=&gt; &quot;FOO&quot;
b #=&gt; &quot;FOO&quot;

The same applies to method arguments. If you pass a mutable object into a method, the method can alter it, just like you can outside the method:

def my_method(s)
s.upcase!
end
a = &quot;foo&quot;
my_method(a)
a #=&gt; &quot;FOO&quot;

Back to your problem. You probably want calcula_nova_posicao to 1) return a new array and 2) leave the passed-in array unchanged. The latter is important to clear the current H from the map. But it's also a good idea in general to not change arguments within methods because it can lead to unexpected results and hard to find bugs.

Here's one implementation:

def calcula_nova_posicao(heroi, direcao)
case direcao
when &quot;W&quot;
[heroi[0] - 1, heroi[1]]
when &quot;S&quot;
[heroi[0] + 1, heroi[1]]
when &quot;A&quot;
[heroi[0], heroi[1] - 1]
when &quot;D&quot;
[heroi[0], heroi[1] + 1]
end
end

Here's another one using array decomposition:

def calcula_nova_posicao(heroi, direcao)
y, x = heroi
case direcao
when &quot;W&quot; then y -= 1
when &quot;S&quot; then y += 1
when &quot;A&quot; then x -= 1
when &quot;D&quot; then x += 1
end
[y, x]
end

You might consider writing tests for your methods.

huangapple
  • 本文由 发表于 2023年5月17日 08:32:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76267865.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定