2011-03-16 16 views
26

Perl è abbastanza bello su valori di default:Posso creare una matrice in Ruby con valori predefiniti?

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

rubino può fare lo stesso, almeno per gli hash:

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

ma lo stesso apparentemente non funziona per gli array:

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

È possibile fornire un valore predefinito per gli array, quindi quando sono auto-estesi, vengono riempiti con 0 anziché zero?

Certo che posso ovviare a questo, ma ad un costo di espressività:

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

Update 1: Uno dei miei colleghi ha sottolineato che posso usare #compact per risolvere il problema #inject e #to_i per risolvere l'elemento-at-index problema standard:

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

Aggiornamento 2: Grazie a Andrew Grimm per una soluzione 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] 

Update 3: questo sta cominciando a guardare come Whack-a-mole!

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

Ma possiamo fare con questo:

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

ora devo ammettere (timidamente) che io sottoclasse Array per evitare di ingombrare il mio codice:

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 

Grazie per tutte le soluzioni creative. TIMTOWTDI davvero!

+1

Se hai bisogno di un array sparse, cosa c'è di sbagliato nell'usare un hash con chiavi intere? – Simon

+0

Mi chiedo se qualche sviluppatore PHP si sia lamentato del fatto che la loro struttura di dati array/hash non esiste in Ruby. –

+0

Simon: Sono andato con un hash in primo luogo, ma c'è un problema con alcune delle #maps e #inject che devo fare. –

risposta

15

Dato che Ruby torna nil per un elemento non esistente (al contrario di index-out-of-bounds errore di tipo), si potrebbe utilizzare una "o":

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

si potrebbe prendere l'approccio di patch scimmia, ma probabilmente non vorrebbe farlo in qualcosa più grande di uno script 1-file:

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

Oppure potresti 'a.fetch {" un valore predefinito "}'. –

+0

Andrew: fantastico! 'def foo. [] (i); fetch (i) {0} end' –

+1

Andrew: inoltre, non conoscevo la sintassi 'def obj.method'. Molto più bello di 'obj.instance_eval {def method ...}'! –

103

Non auto estesa, ma inizializzato alla lunghezza specificata con un valore predefinito:

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

Questo esempio non funziona quando sarà auto-esteso. – Gareve

+0

Non sprecherebbe più memoria? – Kunok

1

Penso che un array sia l'astrazione sbagliata se si desidera estendere automaticamente l'array. Aggiungi un altro livello di astrazione.

Edit (dalla nostra discussione): La cosa importante è che il codice per raggiungere il tuo obiettivo si trova nel posto giusto (unico principio di responsabilità), e quel posto è non il "codice cliente", da qui la necessità di una nuova classe.Estendere la classe Array esistente (attraverso l'ereditarietà/mixin) è probabilmente meglio dell'incapsulamento del comportamento desiderato in una classe interamente nuova.

+1

Senza sapere cosa sto facendo, giudicare se l'astrazione sia corretta sembra abbastanza impossibile. L'auto-estensione delle matrici è una caratteristica di Ruby, quindi perché dovrei voler costruire un altro livello di astrazione per realizzare la stessa cosa che mi dà il comportamento predefinito dell'array? Ruby non è Java. ;) –

+0

concordato. Quello che sto ottenendo è che potresti voler incapsulare il comportamento esteso in una classe con un nome che ti dice cosa fa questa classe piuttosto che solo il comportamento della patch della scimmia su una classe esistente. Questo potrebbe essere ottenuto attraverso l'ereditarietà/mixin o qualche altra tecnica che ha il rubino. –

+1

Hai ragione anche se sono un programmatore C++ che sta imparando un po 'di Ruby piuttosto che un guru Ruby :) –

2

Un altro approccio sarebbe l'override del metodo Array#[] e restituire il valore predefinito se non v'è alcun elemento

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

e

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

In realtà mi piace questo, anche se piuttosto che applicare la patch alla classe Array, cercherò solo di eseguire l'override sull'array specifico a cui tengo. –

3

Se hai a che fare con i numeri interi è possibile chiamare to_i:

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

UPD

Oh, non ho letto tutti Soggetto :)

in modo da poter utilizzare questo:

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

che, in realtà, non è il più lussuoso

+0

Grazie, questo era esattamente ciò di cui avevo bisogno. – snowe

3

io Metti la soluzione elegante di Johans là fuori: foo.compact.inject(:+)

+0

Grazie per il proxy! ;) –

Problemi correlati