2014-11-21 4 views
7

Ich möchte in einem bestimmten Format Hash, wenn eine Zeichenfolge im folgende Format angezeigt:Wie man den gewünschten formatierten Hash von einem String auf effiziente Weise mit Ruby bekommt?

Gegeben string:

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

# In einer Hash-ähnlichen (erforderlich Hash-Muster):

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

ich habe versucht, viele Möglichkeiten, aber das ist in der Nähe:

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 

Antwort

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" => {} 
# } 
# } 

Ich hoffe, dass Sie mich für das Verlassen leeren Hashes statt Nullwerte bei Blättern aus Gründen der Klarheit der Lösung entschuldigen.

zunichte gemacht Blätter:

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

Dank für die Antwort. Ich werde stattdessen versuchen, null zu bekommen. –

+0

Siehe Update für wie Blätter zunichte zu machen. – mudasobwa

+0

Danke @mudasobwa –

2

Sie auch, dass die Ausgabe von so etwas wie dieses

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 

Ausgang

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

Danke - @Yogesh –

+0

Sind Sie sicher, dass Sie '= gemeint curr [c.strip] = curr [c.strip]'? –

+0

ja @theTinMan, bin ich mir sicher. –

1

bekommen Dies ist ein weiterer Weg, dass weniger Daten benötigt um den Hash zu erstellen. Wenn zum Beispiel die Linie

A = B = C = D 

vorhanden ist, gibt es keine Notwendigkeit für eine der folgenden:

A = B 
A = B = C 

und die Reihenfolge der Linien unwichtig ist.

-Code

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 

Beispiel

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

Erklärung

Für str oben:

a = str.lines 
    #=> ["A = B = C\n", "A = B = D\n", "A = E = F\n", 
    # "G = H\n", "G = I\n", "G = J\n"] 

Beachten Sie, dass String#lines im Gegensatz zu split(/'\n'/) die Newline-Zeichen behält. Sie an dieser Stelle zu halten war beabsichtigt; sie dienen einem wichtigen Zweck, wie unten gezeigt wird.

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({})> 

wir den Enumerator auf einem Array konvertieren, die Elemente zu sehen, die Array#each zum Block übergeben wird:

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 ruft nun each jedes Element in den Block passieren:

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"=>{}}} 

Der erste Wert, der Array#each in den Block übergibt, lautet:

["A = B = C\n", {}] 

die zerlegt wird, oder "disambiguiert" in seinen zwei Elemente und den Blockvariablen zugewiesen:

line = "A = B = C\n" 
h = {} 

wir jetzt den Code im Block auszuführen:

b = line.split(/\s*=\s*/) 
    #=> ["A", "B", "C\n"] 
b.reduce(h) { |g,w| 
    (w[-1] == '\n') ? g[w.chomp] = nil : g[w] ||= {} } 
    #=> {} 

Der Anfangswert für reduce ist der Hash h, den wir erstellen, der zunächst leer ist.Wenn h und "A" werden in den Block übergeben,

g = h #=> {} 
w = "A" 

so (unter Hinweis darauf, dass doppelte Anführungszeichen für "\n" benötigt werden)

w[-1] == "\n" 
    #=> "A" == '\n' 
    #=> false 

so führen wir

g[w] ||= {} 
    #=> g['A'] ||= {} 
    #=> g['A'] = g['A'] || {} 
    #=> g['A'] = nil || {} 
    #=> {} 

so jetzt

h #=> {"A"=>{}} 

g[w] => {} wird dann an reduce zurück zurück übergeben und die Blockgrößen für das zweite Element zu dem Block übergeben sind:

g = g["A"] #=> {} 
w = "B" 

Da

w[-1] == "\n" #=> false 

wir wieder

g[w] ||= {} 
#=> g["B"] ||=> {} => {} 

ausführen und jetzt

h #=> {"A"=>{"B"=>{}}} 

Schließlich [g["B"], "C\n"] in den Block geleitet, zerlegt und auf den Block Variablen zugewiesen:

g = g["B"] #=> {} 
w = "C\n" 

aber die Anwesenheit der Newline-Zeichen in w Ergebnissen in

w[-1] == "\n" #=> true 

uns sagen, es ist das letzte Wort in der Zeile, so müssen wir die Zeilenende-Zeichen und den Wert auf nil abzustreifen:

g[w.chomp] = nil 
    #=> g["C"] = nil 

in resultierende:

h #=> {"A"=>{"B"=>{"C"=>nil}}} 

den Zeichen newline Ende in der Zeichenfolge bereitgestellt, um die benötigten „Flag“ für die Verarbeitung des letzten Wortes in jeder Zeile anders als die anderen.

Die anderen Zeilen werden ähnlich verarbeitet.

Verwandte Themen