2012-02-24 18 views
45

uno può dire perché il seguente:Rubino iniettare con iniziale essendo un hash

['a', 'b'].inject({}) {|m,e| m[e] = e } 

genera l'errore:

IndexError: string not matched 
     from (irb):11:in `[]=' 
     from (irb):11:in `block in irb_binding' 
     from (irb):11:in `each' 
     from (irb):11:in `inject' 
     from (irb):11 
     from C:/Ruby192/bin/irb:12:in `<main>' 

, mentre le seguenti opere?

a = {} 
a["str"] = "str" 

risposta

63

tuo blocco deve restituire l'hash accumulando:

['a', 'b'].inject({}) {|m,e| m[e] = e; m } 

Invece, è restituendo la stringa 'a' dopo il primo passaggio, che diventa m nel passaggio successivo e si finisce per chiamare il il metodo stringa []=.

+0

È assolutamente necessario includere la m alla fine? Ad esempio, se il blocco era '{| array, (k, v) | array << MyObject.new (k, v)} 'funzionerebbe? Considerando che 'array. <<' restituisce l'array. – Ziggy

+8

@Ziggy: sì, è necessario perché l'assegnazione 'hash [chiave] = valore' restituisce' valore', e hai bisogno di 'hash'. – tokland

43

Il blocco deve restituire l'accumulatore (l'hash), come detto da @Rob. Alcune alternative:

Con Hash#update:

hash = ['a', 'b'].inject({}) { |m, e| m.update(e => e) } 

Con Enumerable#each_with_object:

hash = ['a', 'b'].each_with_object({}) { |e, m| m[e] = e } 

Con Hash#[]:

hash = Hash[['a', 'b'].map { |e| [e, e] }] 

Con Enumerable#mash da Sfaccettature:

require 'facets' 
hash = ['a', 'b'].mash { |e| [e, e] } 

Con Array#to_h (Rubino> = 2.1):

hash = ['a', 'b'].map { |e| [e, e] }.to_h 
+3

Belle alternative. In particolare mi piace la distinzione tra la tecnica del poster originale e il tuo approccio da mappa a coppia-poi-crea-nuovo-hash: la domanda originale è essenzialmente iterativa - per ogni elemento, fai questa operazione sull'Hash - quindi iniettati sembra eccessivamente complesso (da qui il bug). Ma l'approccio di mappatura è più incentrato sull'insieme: crea questa matrice di singoli in una matrice di coppie, quindi crea quella matrice di coppie in un hash. –

19

Piuttosto che usare iniettare, si dovrebbe guardare in Enumerable#each_with_object.

Dove richiede di restituire l'oggetto in cui si è accumulato, each_with_object lo fa automaticamente.

Dalla documentazione:

Iterates the given block for each element with an arbitrary object given, and returns the initially given object.

If no block is given, returns an enumerator.

e.g.:

evens = (1..10).each_with_object([]) {|i, a| a << i*2 } 
#=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] 

Quindi, più vicino alla tua domanda:

[1] pry(main)> %w[a b].each_with_object({}) { |e,m| m[e] = e } 
=> {"a"=>"a", "b"=>"b"} 

noti che inject e each_with_object invertire l'ordine dei parametri fruttati.