2014-11-21 3 views
7

Voglio hash in un formato particolare quando una stringa viene visualizzato nel seguente formato:Come ottenere l'hash formattato desiderato da una stringa in modo efficiente con Ruby?

stringa data:

str = 'A 
A = B 
A = B = C 
A = B = D 
A = E = F 
G = H 
G = I 
G = J' 

# in un hash come questo (modello hash richiesto):

{ 
    "A" => { 
    "B" => { 
     "C" => nil, 
     "D" => nil 
    }, 
    "E" => { 
     "F" => nil 
    }, 
    }, 
    "G" => { 
    "H" => nil, 
    "I" => nil, 
    "J" => nil 
    } 
} 

ho provato molti modi, ma questo è il più vicino:

output = Hash.new 
line_hash = Hash.new 
str.each_line do |line| 
    arr = line.split("=") 
    e = arr.first.strip 
    line_hash[e] = {} 
    arr.each_with_index do |ele, i| 
    break unless arr[i+1] 
    line_hash[ele.strip] = arr[i+1] unless output.keys.include?(ele.strip) 
    end 
    output[e] = line_hash unless output.keys.include?(e) 
end 

risposta

9
str = "A\nA = B\nA = B = C\nA = B = D\nA = E = F\nG = H\nG = I\nG = J" 

curr = h = {} 

str.each_line { |l| 
    l.chomp.split(/\s*=\s*/m).each { |c| 
    curr = curr[c] ||= {}; 
    } 
    curr = h 
} 

puts h 
# => { 
# "A" => { 
# "B" => { 
#  "C" => {}, 
#  "D" => {} 
# }, 
# "E" => { 
#  "F" => {} 
# } 
# }, 
# "G" => { 
# "H" => {}, 
# "I" => {}, 
# "J" => {} 
# } 
# } 

Spero che mi scuserete per lasciare hash vuoti al posto dei valori nulli a foglie per amore della soluzione di chiarezza.

di annullare le foglie:

def leaves_nil! hash 
    hash.each { |k,v| v.empty? ? hash[k] = nil : leaves_nil!(hash[k]) } 
end 
+0

grazie per la risposta. Proverò a ottenere null invece. –

+0

Vedere l'aggiornamento per come annullare le foglie. – mudasobwa

+0

Grazie @mudasobwa –

2

è anche possibile ottenere che l'uscita da qualcosa come questa uscita

str = 'A 
A = B 
A = B = C 
A = B = D 
A = E = F 
G = H 
G = I 
G = J' 

curr = h = {} 
lines = str.split("\n").map{|t| t.split(/\s*=\s*/m) } 
lines.each do |line| 
    line.each { |c| curr = curr[c.strip] = curr[c.strip] || ((line.last == c) ? nil : {}); } 
    curr = h 
end 

#=> { 
#  "A" => { 
#   "B" => { 
#    "C" => nil, 
#    "D" => nil 
#   }, "E" => { 
#    "F" => nil 
#   } 
#  }, "G" => { 
#   "H" => nil, 
#   "I" => nil, 
#   "J" => nil 
#  } 
# } 
+1

Grazie - @ Yogesh –

+0

Sei sicuro di voler dire '= curr [c.strip] = curr [c.strip]'? –

+0

si @theTinMan, ne sono sicuro. –

1

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.

Problemi correlati