2013-05-13 1 views
2

j'ai un tableau fait à partir de variables, et je veux effectuer la même opération sur chaque, et stocker le résultat dans la variable d'origine :Modifier les valeurs des variables dans le tableau

(one, two, three) = [1, 2, 3] 

[one, two, three].map!{|e| e += 1} 
# => [2, 3, 4] 

# But: 
[one, two, three] 
# => [1, 2, 3] 

# You have to: 

(one, two, three) = [one, two, three].map{|e| e += 1} 
# => [2, 3, 4] 

[one, two, three] 
# => [2, 3, 4] 

Cela ne semble être la «bonne façon» de le faire, mais je n'arrive pas à trouver cette «bonne façon». J'ai aussi des idées vagues sur ce qui se passe, mais je ne suis pas trop sûr, alors une explication serait appréciée.


Mon cas réel d'utilisation est que j'ai nommé paramètres et je suis e = File.new(e) if e.is_a? String

Répondre

4

Les nombres (tels que Fixnum) dans Ruby sont immuables. Vous ne pouvez pas modifier la valeur sous-jacente.

Une fois que vous affectez one = 1, il n'est pas possible de modifier la valeur de one sans nouvelle affectation. Quand vous faites one += 1. Vous affectez en fait la nouvelle valeur 2 à la variable one; c'est un tout nouvel objet.

Vous pouvez y voir plus clair en regardant le object_id (__id__ a.k.a.):

one = 1 
1.object_id  # => 2 
one.object_id # => 2 
one += 1 
one.object_id # => 5 
2.object_id  # => 5 

Maintenant, dans votre déclaration Array#map!, vous n'êtes pas réellement changer l'objet one. Une référence à cet objet est stockée dans le tableau; pas la variable réelle. Lorsque vous énumérez avec map!, l'objet renvoyé par le bloc est ensuite stocké dans l'emplacement de référence interne à la même position. Pensez à la première passe sur map! semblable au suivant:

one = 1 
one.object_id  # => 2 

arr = [one] 

arr[0].object_id # => 2 

arr[0] += 1 # You are re-setting the object at index 0 
       # not changing the original `one`'s value 

arr[0]   # => 2 
arr[0].object_id # => 5 

one    # => 1 
one.object_id  # => 2 

Étant donné que ces Fixnum objets sont immuables, il n'y a pas moyen de changer leur valeur. C'est pourquoi vous devez dé référence le résultat de votre map retour aux valeurs d'origine:

(one, two, three) = [1, 2, 3] 
one.object_id  # => 3 
two.object_id  # => 5 
three.object_id # => 7 

(one, two, three) = [one, two, three].map{|e| e += 1} 
one.object_id  # => 5 
two.object_id  # => 7 
three.object_id # => 9 
2

Essayez ceci:

a = [one, two, three] 
a.map!{|e| e += 1} 

Le problème est que [one, two, three] n'est pas une variable qui stocke une tableau, c'est un tout nouveau tableau chaque fois que vous l'écrivez. Une fois que vous avez défini a = [one, two, three], vous disposez d'une variable qui stocke cette valeur sur laquelle vous pouvez ensuite opérer.


Darshan a souligné dans les commentaires que cela ne modifie pas réellement les valeurs de l'une des variables d'origine, deux et trois, et il est correct. Mais il y a une façon de le faire:

["one", "two", "three"].each{ |e| eval "#{e} += 1" } 

Mais c'est assez laid, repose sur l'utilisation de chaînes dans le tableau au lieu des variables réelles, et est probablement bien pire que ce que vous avez déjà venu avec:

(one, two, three) = [one, two, three].map{|e| e += 1} 
+0

Cela ne fonctionne pas. 'one',' two' et 'three' restent inchangés. –

+0

Vous avez raison, Darshan. Mise à jour ma réponse –

0

Si vous voulez vraiment changer la valeur des variables qui font référence à Fixnum, alors ce que vous faites est le mieux que vous pouvez faire en Ruby.Cela dit, vous pourriez être mieux pas les stocker comme trois variables distinctes. Plutôt que d'avoir one, two et three, vous pouvez avoir a[0] par a[2] et passer a autour, ou h[:one] par h[:three] et passer h autour.

a = [1, 2, 3] 
a.map!{|e| e += 1} 
a # => [2, 3, 4] 

h = {:one=>1, :two=>2, :three=>3} 
h.each_key{|k| h[k] += 1} 
h # => {:one=>2, :two=>3, :three=>4} 

La deuxième option, en utilisant une table de hachage, est probablement plus proche de ce que vous voulez, parce que h[:some_name] est plus proche d'utiliser un nom de variable.

Questions connexes