Questo è un altro modo che richiede meno di dati per costruire l'hash. Se, per esempio, la linea è presente
A = B = C = D
, non v'è alcuna necessità di uno dei seguenti:
A = B
A = B = C
e l'ordine delle righe è irrilevante.
Codice
def hashify(str)
str.lines.each_with_object({}) { |line, h|
line.split(/\s*=\s*/).reduce(h) { |g,w|
(w[-1] == "\n") ? g[w.chomp] = nil : g[w] ||= {} } }
end
Esempio
str =<<_
A = B = C
G = I
A = B = D
A = E = F
G = H
A = K
G = J
_
hashify(str)
#=> {"A"=>{"B"=>{"C"=>nil, "D"=>nil}, "E"=>{"F"=>nil}, "K"=>nil},
# "G"=>{"I"=>nil, "H"=>nil, "J"=>nil}}
Spiegazione
Per str
sopra:
a = str.lines
#=> ["A = B = C\n", "A = B = D\n", "A = E = F\n",
# "G = H\n", "G = I\n", "G = J\n"]
Si noti che String#lines, a differenza di split(/'\n'/)
, mantiene i caratteri di nuova riga. Mantenerli a questo punto era intenzionale; servono a uno scopo importante, come verrà mostrato di seguito.
enum = a.each_with_object({})
#=> #<Enumerator: ["A = B = C\n", "A = B = D\n", "A = E = F\n", "G = H\n",
# "G = I\n", "G = J\n"]:each_with_object({})>
Possiamo convertire l'enumeratore un array di vedere gli elementi del Array#each passerà al blocco:
enum.to_a
#=> [["A = B = C\n", {}], ["A = B = D\n", {}], ["A = E = F\n", {}],
# ["G = H\n", {}], ["G = I\n", {}], ["G = J\n", {}]]
enum
ora invoca each
per passare ogni elemento nel blocco:
enum.each { |line, h| line.split(/\s*=\s*/).reduce(h) { |g,w|
(w[-1] == '\n') ? g[w.chomp] = nil : g[w] ||= {} } }
#=> {"A"=>{"B"=>{"C\n"=>{}, "D\n"=>{}}, "E"=>{"F\n"=>{}}},
# "G"=>{"H\n"=>{}, "I\n"=>{}, "J\n"=>{}}}
Il primo valore che Array#each
passa nel blocco è:
["A = B = C\n", {}]
che viene decomposto o "disambiguare" in è due elementi e assegnati alle variabili di blocco:
line = "A = B = C\n"
h = {}
Ora eseguire il codice nel blocco:
b = line.split(/\s*=\s*/)
#=> ["A", "B", "C\n"]
b.reduce(h) { |g,w|
(w[-1] == '\n') ? g[w.chomp] = nil : g[w] ||= {} }
#=> {}
Il valore iniziale per è l'hash h
che stiamo costruendo, che è inizialmente vuoto.Quando h
e "A"
sono passati nel blocco,
g = h #=> {}
w = "A"
modo (notando che le doppie virgolette sono necessarie per "\n"
)
w[-1] == "\n"
#=> "A" == '\n'
#=> false
quindi eseguiamo
g[w] ||= {}
#=> g['A'] ||= {}
#=> g['A'] = g['A'] || {}
#=> g['A'] = nil || {}
#=> {}
così ora
h #=> {"A"=>{}}
g[w] => {}
viene quindi passato indietro tornare alla reduce
e le variabili di blocco per il secondo elemento passato al blocco sono:
g = g["A"] #=> {}
w = "B"
Da
w[-1] == "\n" #=> false
abbiamo nuovamente eseguiamo
g[w] ||= {}
#=> g["B"] ||=> {} => {}
e ora
h #=> {"A"=>{"B"=>{}}}
Infine, [g["B"], "C\n"]
viene passato nel blocco, decomposto e assegnati alle variabili del blocco:
g = g["B"] #=> {}
w = "C\n"
ma la presenza del carattere di nuova riga in w
risultati in
w[-1] == "\n" #=> true
ci dice che è l'ultima parola nella riga, quindi dobbiamo rimuovere il carattere di fine riga e impostare il valore su nil
:
g[w.chomp] = nil
#=> g["C"] = nil
conseguente:
h #=> {"A"=>{"B"=>{"C"=>nil}}}
Lasciando il carattere di nuova riga nella stringa fornito "bandiera" necessario per l'elaborazione dell'ultima parola di ogni riga diverso rispetto agli altri.
Le altre linee vengono elaborate in modo simile.
grazie per la risposta. Proverò a ottenere null invece. –
Vedere l'aggiornamento per come annullare le foglie. – mudasobwa
Grazie @mudasobwa –