web-dev-qa-db-fra.com

Puis-je créer un tableau en Ruby avec des valeurs par défaut?

Perl est assez gentil avec les valeurs par défaut:

: jmglov@laurana; Perl -e '@foo; printf "%d\n", $foo[123]'
0
: jmglov@laurana; Perl -e '%foo; printf "%d\n", $foo{bar}'
0

Ruby peut faire la même chose, au moins pour les hashes:

>> foo = Hash.new(0)
=> {}
>> foo[:bar]
=> 0

Mais la même chose ne fonctionne apparemment pas pour les tableaux:

>> foo = Array.new(0)
=> []
>> foo[123]
=> nil
>> foo[124] = 0
=> 0
>> foo[456] = 0
=> 0
>> foo[455,456]
=> [nil, 0]

Est-il possible de fournir une valeur par défaut pour les tableaux, de sorte que lorsqu'ils sont auto-étendus, ils sont remplis avec 0 au lieu de zéro?

Bien sûr, je peux contourner cela, mais au prix de l'expressivité:

>> foo[457,458] = 890, 321
=> [890, 321]
>> foo[456] += 789
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.+
>> foo.inject(0) {|sum, i| sum += (i || 0) }
=> 1211
>> foo.inject(:+)
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.+

Mise à jour 1: Un de mes collègues a souligné que je peux utiliser #compact pour résoudre le problème #inject et #to_i pour résoudre le problème standard élément-à-index:

>> foo.include? nil
=> true
>> foo.compact.inject(:+)
=> 1211
>> foo[456,457]
=> [0, 890, 321]
>> foo[455..457]
=> [nil, 0, 890]
>> foo[455..457].map(&:to_i)
=> [0, 0, 890]

Mise à jour 2: Merci à Andrew Grimm pour une solution au problème +=:

>> foo = []
=> []
>> def foo.[](i)
>>   fetch(i) {0}
>> end
=> nil
>> foo[4]
=> 0
>> foo
=> []
>> foo[4] += 123
=> 123
>> foo
=> [nil, nil, nil, nil, 123]

Mise à jour 3: cela commence à ressembler à une taupe!

>> foo
=> [nil, nil, nil, nil, 123]
>> foo[-2..-1]
TypeError: can't convert Range into Integer

Mais nous pouvons nous en occuper:

>> def foo.[](index)
>>   if index.is_a? Range
>>     index.map {|i| self[i] }
>>   else
?>     fetch(index) { 0 }  # default to 0 if no element at index; will not cause auto-extension of array
>>   end
>> end
=> nil
>> foo
=> [nil, nil, nil, nil, 123]
>> foo[-2..-1]
=> [nil, 123]

Je dois maintenant admettre (penaud) que je vais sous-classer Array pour ne pas encombrer mon code:

class MyClass
  class ArrayWithDefault < Array
    def [](index)
      if index.is_a? Range
        index.map {|i| self[i] }
      else
        fetch(index) { 0 }  # default to 0 if no element at index; will not cause auto-extension of array
      end
    end
  end
end

Merci pour toutes les solutions créatives. TIMTOWTDI en effet!

32
Josh Glover

Étant donné que Ruby renvoie nil pour un élément non existant (par opposition à une erreur de type index-hors-des limites), vous pouvez simplement utiliser un "ou":

a = [1,2,3]
puts a[5]  # => nil
puts a[5] || "a default"  # => a default

Vous pouvez utiliser l'approche du patch de singe, mais vous ne voudriez probablement pas faire cela dans un script plus volumineux qu'un script à un fichier:

a = [1,2,3]
def a.[](index)
  self.at(index) || "a default"
end
puts a[5]   # => "a default"
16
Robert Brown

Non étendu automatiquement, mais initialisé à la longueur spécifiée avec une valeur par défaut:

>> Array.new(123, 0)  
=> [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
122
ludde

Si vous avez affaire à des entiers, vous pouvez appeler to_i:

foo = []
foo[100]
#=> nil
foo[100].to_i
#=> 0
foo[100] = 3
foo[100]
#=> 3

UPD

Oh, je n'ai pas lu tout le sujet :)

afin que vous puissiez utiliser ceci:

foo.inject{|a,b| a.to_i + b.to_i }

qui, en fait, pas le plus intelligent

3
fl00r

Je vais mettre la solution élégante de Johans là-bas: foo.compact.inject(:+)

3
ludde

Une autre approche serait de remplacer la méthode Array#[] et de renvoyer la valeur par défaut s'il n'y a pas d'élément

class Array         
  def [](index)
     self.at(index) ? self.at(index) : 0
  end
end

et

arr = [1,2,3]
puts arr[0]  # print 1
puts arr[5]  # print 0
2
RameshVel

Je pense qu'un tableau est une abstraction erronée si vous souhaitez étendre automatiquement le tableau. Ajoutez un autre niveau d'abstraction.

Edit (de notre discussion): / L'important est que le code pour atteindre votre objectif se trouve au bon endroit (principe de responsabilité unique) et que cet endroit est pas votre "code client", d'où la nécessité d'une nouvelle classe. L'extension de la classe Array existante (via héritage/mixin) est probablement préférable à l'encapsulation du comportement souhaité dans une nouvelle classe. 

1
Tobias Furuholm

Le moyen le plus simple serait:

new_array = Array.new(size, default_value)

Par exemple:

new_array = Array.new(5,"foo")
0
ThimiraR