Metodo Ruby Array # << non sta aggiornando l'array in hash

Ispirato da Come posso effettuare il marshalling di un hash con gli array? Mi chiedo quale sia la ragione per cui l’ Array#<< non funzionerà correttamente nel seguente codice:

 h = Hash.new{Array.new} #=> {} h[0] #=> [] h[0] < ["a"] h[0] #=> [] # why?! h[0] += ['a'] #=> ["a"] h[0] #=> ["a"] # as expected 

Ha a che fare con il fatto che << cambia la matrice sul posto, mentre la Array#+ crea una nuova istanza?

Se crei un Hash utilizzando il modulo di blocco di Hash.new , il blocco viene eseguito ogni volta che tenti di accedere a un elemento che in realtà non esiste. Quindi, guardiamo cosa succede:

 h = Hash.new { [] } h[0] << 'a' 

La prima cosa che viene valutata qui, è l'espressione

 h[0] 

Cosa succede quando viene valutato? Bene, il blocco viene eseguito:

 [] 

Non è molto eccitante: il blocco crea semplicemente un array vuoto e lo restituisce. Non fa nient'altro In particolare, non cambia in alcun modo h : h è ancora vuoto.

Successivamente, il messaggio << con un argomento 'a' viene inviato al risultato di h[0] che è il risultato del blocco, che è semplicemente un array vuoto:

 [] << 'a' 

Cosa fa questo? Aggiunge l'elemento 'a' a un array vuoto, ma poiché l'array non viene effettivamente assegnato a nessuna variabile, viene immediatamente raccolto e viene eliminato.

Ora, se valuti ancora h[0] :

 h[0] # => [] 

h è ancora vuoto, poiché non gli è mai stato assegnato nulla, quindi la chiave 0 è ancora inesistente, il che significa che il blocco viene eseguito di nuovo , il che significa che restituisce di nuovo un array vuoto (ma si noti che è completamente nuovo, diverso array vuoto ora).

 h[0] += ['a'] 

Che succede qui? Innanzitutto, l'operatore assegnato viene indirizzato a

 h[0] = h[0] + ['a'] 

Ora, la h[0] sul lato destro viene valutata. E cosa restituisce? Abbiamo già esaminato questo h[0] : h[0] non esiste, quindi il blocco viene eseguito, il blocco restituisce una matrice vuota. Ancora una volta, questo è un array completamente nuovo, terzo vuoto ora. Questo array vuoto riceve il messaggio + con l'argomento ['a'] , che fa sì che restituisca ancora un altro nuovo array che è l'array ['a'] . Questo array viene quindi assegnato a h[0] .

Infine, a questo punto:

 h[0] # => ['a'] 

Ora finalmente hai effettivamente messo qualcosa in h[0] quindi, ovviamente, ottieni ciò che hai inserito.

Quindi, per rispondere alla domanda che probabilmente hai avuto, perché non esci da ciò che hai inserito? In primo luogo, non hai messo niente!

Se in realtà vuoi assegnare l'hash all'interno del blocco, devi assegnare bene l'hash all'interno del blocco:

 h = Hash.new {|this_hash, nonexistent_key| this_hash[nonexistent_key] = [] } h[0] << 'a' h[0] # => ['a'] 

In realtà è abbastanza facile vedere cosa sta succedendo nel tuo esempio di codice, se guardi le identity framework degli oggetti coinvolti. Quindi puoi vedere che ogni volta che chiami h[0] , ottieni un array diverso .

Il problema nel codice è che h[0] << 'a' crea una nuova matrice e la distribuisce quando si indice con h[0] , ma non memorizza la matrice modificata in nessun punto dopo il << 'a' perché non c'è un incarico.

Nel frattempo h[0] += ['a'] funziona perché è equivalente a h[0] = h[0] + ['a'] . È il compito ( []= ) che fa la differenza.

Il primo caso può sembrare confuso, ma è utile quando si desidera ricevere un elemento predefinito invariato da un hash quando la chiave non viene trovata. Altrimenti potresti finire popolando l'Hash con un gran numero di valori inutilizzati solo indicizzandolo.

 h = Hash.new{ |a,b| a[b] = Array.new } h[0] << "hello world" #=> ["hello world"] h[0] #=> ["hello world"]