2011-03-16 13 views
26

Perl es bastante agradable acerca de los valores por defecto:¿Puedo crear una matriz en Ruby con valores predeterminados?

: [email protected]; perl -e '@foo; printf "%d\n", $foo[123]' 
0 
: [email protected]; perl -e '%foo; printf "%d\n", $foo{bar}' 
0 

Ruby puede hacer lo mismo, al menos para los hashes:

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

pero el mismo aparentemente no funciona para matrices:

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

¿Es posible proporcionar un valor predeterminado para las matrices, por lo que cuando se amplían automáticamente, se rellenan con 0 en lugar de cero?

Por supuesto que puedo solucionar este problema, pero a un costo de expresividad:

>> 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.+ 

Actualización 1: Uno de mis colegas señalaron que puedo usar #compact para resolver el problema #inject y #to_i para resolver el problema estándar elemento al índice:

>> 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] 

actualización 2: Gracias aAndrew Grimm para una solución al problema +=:

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

Actualización 3: esto está empezando a parecerse a golpe-a-mole!

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

Pero podemos lidiar con eso:

>> 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] 

ahora tengo que admitir (con timidez) que voy a subclase Array para evitar que saturan mi código:

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 

Gracias por todas las soluciones creativas. TIMTOWTDI de hecho!

+1

Si necesita una matriz dispersa, ¿qué hay de malo con el uso de un hash con claves enteras? – Simon

+0

Me pregunto si los desarrolladores de PHP se han quejado de que su estructura de datos matriz/hash no existe en Ruby. –

+0

Simon: Primero fui con un hash, pero me duelen algunos de los #maps y #injects que necesito hacer. –

Respuesta

15

Dado que Ruby devuelve nil para un elemento no existente (en contraposición a índice de salida de los límites de error tipo), sólo podría utilizar una "o":

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

se podría adoptar el enfoque de parches mono, pero es probable que no desee hacer esto en algo más grande que un guión 1-archivo:

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

O podría hacer 'a.fetch {" a default "}'. –

+0

Andrew: ¡increíble! 'def foo. [] (i); fetch (i) {0} end' –

+1

Andrew: también, no sabía acerca de la sintaxis 'def obj.method'. Mucho mejor que 'obj.instance_eval {def method ...}'! –

103

No auto extendida, pero inicializa a la longitud especificada con un valor predeterminado:

>> 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] 
+1

Este ejemplo no funciona cuando se ampliará automáticamente. – Gareve

+0

¿No desperdiciaría más memoria? – Kunok

1

Creo que una matriz es la abstracción incorrecta si desea extender automáticamente la matriz. Agregue otro nivel de abstracción.

Editar (de nuestra discusión): Lo importante es que el código para lograr su objetivo se encuentra en el lugar correcto (principio de responsabilidad individual), y ese lugar es no su "código de cliente", por lo tanto, la necesidad de una nueva clase.Extender la clase Array existente (a través de inheritance/mixin) es probablemente mejor que encapsular el comportamiento deseado en una nueva clase de entierly.

+1

Sin saber lo que estoy haciendo, juzgar si la abstracción es correcta parece bastante imposible. La extensión automática de matriz es una característica de Ruby, así que ¿por qué querría construir otra capa de abstracción para lograr lo mismo que me da el comportamiento predeterminado de Array? Ruby no es Java. ;) –

+0

De acuerdo. Lo que quiero decir es que es posible que desee encapsular el comportamiento extendido en una clase con un nombre que le indique qué hace esta clase en lugar de solo el comportamiento de parche de mono en una clase existente. Esto podría lograrse a través de la herencia/mixin o alguna otra técnica que tenga Ruby. –

+1

Tienes razón en que soy un programador de C++ aprendiendo algo de Ruby en lugar de un Ruby guru :) –

2

Otro enfoque sería reemplazando el método Array#[] y devolver el valor predeterminado si no hay ningún elemento

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

y

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

En realidad me gusta esta, aunque en lugar de parchear mono la clase Array, solo voy a instanciar la invalidación en la matriz específica que me importa. –

3

Si usted está tratando con números enteros se puede llamar to_i:

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

UPD

Oh, no he leído todos Tema :)

esta manera puede utilizar esto:

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

la que, en realidad, no es el más inteligente

+0

Gracias, esto era exactamente lo que necesitaba. – snowe

3

voy ponga la solución elegante de Johans por ahí: foo.compact.inject(:+)

+0

¡Gracias por el proxy! ;) –

Cuestiones relacionadas